1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ |
2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */ |
3 | |
4 | #include <base/color.h> |
5 | #include <base/log.h> |
6 | #include <base/math.h> |
7 | #include <base/system.h> |
8 | |
9 | #include <engine/client/checksum.h> |
10 | #include <engine/shared/protocol.h> |
11 | #include <engine/storage.h> |
12 | |
13 | #include "config.h" |
14 | #include "console.h" |
15 | #include "linereader.h" |
16 | |
17 | #include <iterator> // std::size |
18 | #include <new> |
19 | |
20 | // todo: rework this |
21 | |
22 | const char *CConsole::CResult::GetString(unsigned Index) const |
23 | { |
24 | if(Index >= m_NumArgs) |
25 | return "" ; |
26 | return m_apArgs[Index]; |
27 | } |
28 | |
29 | int CConsole::CResult::GetInteger(unsigned Index) const |
30 | { |
31 | if(Index >= m_NumArgs) |
32 | return 0; |
33 | return str_toint(str: m_apArgs[Index]); |
34 | } |
35 | |
36 | float CConsole::CResult::GetFloat(unsigned Index) const |
37 | { |
38 | if(Index >= m_NumArgs) |
39 | return 0.0f; |
40 | return str_tofloat(str: m_apArgs[Index]); |
41 | } |
42 | |
43 | ColorHSLA CConsole::CResult::GetColor(unsigned Index, bool Light) const |
44 | { |
45 | if(Index >= m_NumArgs) |
46 | return ColorHSLA(0, 0, 0); |
47 | |
48 | const char *pStr = m_apArgs[Index]; |
49 | if(str_isallnum(str: pStr) || ((pStr[0] == '-' || pStr[0] == '+') && str_isallnum(str: pStr + 1))) // Teeworlds Color (Packed HSL) |
50 | { |
51 | const ColorHSLA Hsla = ColorHSLA(str_toulong_base(str: pStr, base: 10), true); |
52 | if(Light) |
53 | return Hsla.UnclampLighting(); |
54 | return Hsla; |
55 | } |
56 | else if(*pStr == '$') // Hex RGB/RGBA |
57 | { |
58 | return color_cast<ColorHSLA>(rgb: color_parse<ColorRGBA>(pStr: pStr + 1).value_or(u: ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f))); |
59 | } |
60 | else if(!str_comp_nocase(a: pStr, b: "red" )) |
61 | return ColorHSLA(0.0f / 6.0f, 1, .5f); |
62 | else if(!str_comp_nocase(a: pStr, b: "yellow" )) |
63 | return ColorHSLA(1.0f / 6.0f, 1, .5f); |
64 | else if(!str_comp_nocase(a: pStr, b: "green" )) |
65 | return ColorHSLA(2.0f / 6.0f, 1, .5f); |
66 | else if(!str_comp_nocase(a: pStr, b: "cyan" )) |
67 | return ColorHSLA(3.0f / 6.0f, 1, .5f); |
68 | else if(!str_comp_nocase(a: pStr, b: "blue" )) |
69 | return ColorHSLA(4.0f / 6.0f, 1, .5f); |
70 | else if(!str_comp_nocase(a: pStr, b: "magenta" )) |
71 | return ColorHSLA(5.0f / 6.0f, 1, .5f); |
72 | else if(!str_comp_nocase(a: pStr, b: "white" )) |
73 | return ColorHSLA(0, 0, 1); |
74 | else if(!str_comp_nocase(a: pStr, b: "gray" )) |
75 | return ColorHSLA(0, 0, .5f); |
76 | else if(!str_comp_nocase(a: pStr, b: "black" )) |
77 | return ColorHSLA(0, 0, 0); |
78 | |
79 | return ColorHSLA(0, 0, 0); |
80 | } |
81 | |
82 | const IConsole::CCommandInfo *CConsole::CCommand::NextCommandInfo(int AccessLevel, int FlagMask) const |
83 | { |
84 | const CCommand *pInfo = m_pNext; |
85 | while(pInfo) |
86 | { |
87 | if(pInfo->m_Flags & FlagMask && pInfo->m_AccessLevel >= AccessLevel) |
88 | break; |
89 | pInfo = pInfo->m_pNext; |
90 | } |
91 | return pInfo; |
92 | } |
93 | |
94 | const IConsole::CCommandInfo *CConsole::FirstCommandInfo(int AccessLevel, int FlagMask) const |
95 | { |
96 | for(const CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) |
97 | { |
98 | if(pCommand->m_Flags & FlagMask && pCommand->GetAccessLevel() >= AccessLevel) |
99 | return pCommand; |
100 | } |
101 | |
102 | return 0; |
103 | } |
104 | |
105 | // the maximum number of tokens occurs in a string of length CONSOLE_MAX_STR_LENGTH with tokens size 1 separated by single spaces |
106 | |
107 | int CConsole::ParseStart(CResult *pResult, const char *pString, int Length) |
108 | { |
109 | char *pStr; |
110 | int Len = sizeof(pResult->m_aStringStorage); |
111 | if(Length < Len) |
112 | Len = Length; |
113 | |
114 | str_copy(dst: pResult->m_aStringStorage, src: pString, dst_size: Len); |
115 | pStr = pResult->m_aStringStorage; |
116 | |
117 | // get command |
118 | pStr = str_skip_whitespaces(str: pStr); |
119 | pResult->m_pCommand = pStr; |
120 | pStr = str_skip_to_whitespace(str: pStr); |
121 | |
122 | if(*pStr) |
123 | { |
124 | pStr[0] = 0; |
125 | pStr++; |
126 | } |
127 | |
128 | pResult->m_pArgsStart = pStr; |
129 | return 0; |
130 | } |
131 | |
132 | int CConsole::ParseArgs(CResult *pResult, const char *pFormat) |
133 | { |
134 | char Command = *pFormat; |
135 | char *pStr; |
136 | int Optional = 0; |
137 | int Error = 0; |
138 | |
139 | pResult->ResetVictim(); |
140 | |
141 | pStr = pResult->m_pArgsStart; |
142 | |
143 | while(true) |
144 | { |
145 | if(!Command) |
146 | break; |
147 | |
148 | if(Command == '?') |
149 | Optional = 1; |
150 | else |
151 | { |
152 | pStr = str_skip_whitespaces(str: pStr); |
153 | |
154 | if(!(*pStr)) // error, non optional command needs value |
155 | { |
156 | if(!Optional) |
157 | { |
158 | Error = 1; |
159 | break; |
160 | } |
161 | |
162 | while(Command) |
163 | { |
164 | if(Command == 'v') |
165 | { |
166 | pResult->SetVictim(CResult::VICTIM_ME); |
167 | break; |
168 | } |
169 | Command = NextParam(pFormat); |
170 | } |
171 | break; |
172 | } |
173 | |
174 | // add token |
175 | if(*pStr == '"') |
176 | { |
177 | char *pDst; |
178 | pStr++; |
179 | pResult->AddArgument(pArg: pStr); |
180 | |
181 | pDst = pStr; // we might have to process escape data |
182 | while(true) |
183 | { |
184 | if(pStr[0] == '"') |
185 | break; |
186 | else if(pStr[0] == '\\') |
187 | { |
188 | if(pStr[1] == '\\') |
189 | pStr++; // skip due to escape |
190 | else if(pStr[1] == '"') |
191 | pStr++; // skip due to escape |
192 | } |
193 | else if(pStr[0] == 0) |
194 | return 1; // return error |
195 | |
196 | *pDst = *pStr; |
197 | pDst++; |
198 | pStr++; |
199 | } |
200 | |
201 | // write null termination |
202 | *pDst = 0; |
203 | |
204 | pStr++; |
205 | } |
206 | else |
207 | { |
208 | char *pVictim = 0; |
209 | |
210 | pResult->AddArgument(pArg: pStr); |
211 | if(Command == 'v') |
212 | { |
213 | pVictim = pStr; |
214 | } |
215 | |
216 | if(Command == 'r') // rest of the string |
217 | break; |
218 | else if(Command == 'v') // validate victim |
219 | pStr = str_skip_to_whitespace(str: pStr); |
220 | else if(Command == 'i') // validate int |
221 | pStr = str_skip_to_whitespace(str: pStr); |
222 | else if(Command == 'f') // validate float |
223 | pStr = str_skip_to_whitespace(str: pStr); |
224 | else if(Command == 's') // validate string |
225 | pStr = str_skip_to_whitespace(str: pStr); |
226 | |
227 | if(pStr[0] != 0) // check for end of string |
228 | { |
229 | pStr[0] = 0; |
230 | pStr++; |
231 | } |
232 | |
233 | if(pVictim) |
234 | { |
235 | pResult->SetVictim(pVictim); |
236 | } |
237 | } |
238 | } |
239 | // fetch next command |
240 | Command = NextParam(pFormat); |
241 | } |
242 | |
243 | return Error; |
244 | } |
245 | |
246 | char CConsole::NextParam(const char *&pFormat) |
247 | { |
248 | if(*pFormat) |
249 | { |
250 | pFormat++; |
251 | |
252 | if(*pFormat == '[') |
253 | { |
254 | // skip bracket contents |
255 | for(; *pFormat != ']'; pFormat++) |
256 | { |
257 | if(!*pFormat) |
258 | return *pFormat; |
259 | } |
260 | |
261 | // skip ']' |
262 | pFormat++; |
263 | |
264 | // skip space if there is one |
265 | if(*pFormat == ' ') |
266 | pFormat++; |
267 | } |
268 | } |
269 | return *pFormat; |
270 | } |
271 | |
272 | char *CConsole::Format(char *pBuf, int Size, const char *pFrom, const char *pStr) |
273 | { |
274 | char aTimeBuf[80]; |
275 | str_timestamp_format(buffer: aTimeBuf, buffer_size: sizeof(aTimeBuf), FORMAT_TIME); |
276 | |
277 | str_format(buffer: pBuf, buffer_size: Size, format: "[%s][%s]: %s" , aTimeBuf, pFrom, pStr); |
278 | return pBuf; |
279 | } |
280 | |
281 | LEVEL IConsole::ToLogLevel(int Level) |
282 | { |
283 | switch(Level) |
284 | { |
285 | case IConsole::OUTPUT_LEVEL_STANDARD: |
286 | return LEVEL_INFO; |
287 | case IConsole::OUTPUT_LEVEL_ADDINFO: |
288 | return LEVEL_DEBUG; |
289 | case IConsole::OUTPUT_LEVEL_DEBUG: |
290 | return LEVEL_TRACE; |
291 | } |
292 | dbg_assert(0, "invalid log level" ); |
293 | return LEVEL_INFO; |
294 | } |
295 | |
296 | int IConsole::ToLogLevelFilter(int Level) |
297 | { |
298 | if(!(-3 <= Level && Level <= 2)) |
299 | { |
300 | dbg_assert(0, "invalid log level filter" ); |
301 | } |
302 | return Level + 2; |
303 | } |
304 | |
305 | LOG_COLOR ColorToLogColor(ColorRGBA Color) |
306 | { |
307 | return LOG_COLOR{ |
308 | .r: (uint8_t)(Color.r * 255.0), |
309 | .g: (uint8_t)(Color.g * 255.0), |
310 | .b: (uint8_t)(Color.b * 255.0)}; |
311 | } |
312 | |
313 | void CConsole::Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor) const |
314 | { |
315 | LEVEL LogLevel = IConsole::ToLogLevel(Level); |
316 | // if console colors are not enabled or if the color is pure white, use default terminal color |
317 | if(g_Config.m_ConsoleEnableColors && mem_comp(a: &PrintColor, b: &gs_ConsoleDefaultColor, size: sizeof(ColorRGBA)) != 0) |
318 | { |
319 | log_log_color(level: LogLevel, color: ColorToLogColor(Color: PrintColor), sys: pFrom, fmt: "%s" , pStr); |
320 | } |
321 | else |
322 | { |
323 | log_log(level: LogLevel, sys: pFrom, fmt: "%s" , pStr); |
324 | } |
325 | } |
326 | |
327 | void CConsole::SetTeeHistorianCommandCallback(FTeeHistorianCommandCallback pfnCallback, void *pUser) |
328 | { |
329 | m_pfnTeeHistorianCommandCallback = pfnCallback; |
330 | m_pTeeHistorianCommandUserdata = pUser; |
331 | } |
332 | |
333 | void CConsole::SetUnknownCommandCallback(FUnknownCommandCallback pfnCallback, void *pUser) |
334 | { |
335 | m_pfnUnknownCommandCallback = pfnCallback; |
336 | m_pUnknownCommandUserdata = pUser; |
337 | } |
338 | |
339 | void CConsole::InitChecksum(CChecksumData *pData) const |
340 | { |
341 | pData->m_NumCommands = 0; |
342 | for(CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) |
343 | { |
344 | if(pData->m_NumCommands < (int)(std::size(pData->m_aCommandsChecksum))) |
345 | { |
346 | FCommandCallback pfnCallback = pCommand->m_pfnCallback; |
347 | void *pUserData = pCommand->m_pUserData; |
348 | TraverseChain(ppfnCallback: &pfnCallback, ppUserData: &pUserData); |
349 | int CallbackBits = (uintptr_t)pfnCallback & 0xfff; |
350 | int *pTarget = &pData->m_aCommandsChecksum[pData->m_NumCommands]; |
351 | *pTarget = ((uint8_t)pCommand->m_pName[0]) | ((uint8_t)pCommand->m_pName[1] << 8) | (CallbackBits << 16); |
352 | } |
353 | pData->m_NumCommands += 1; |
354 | } |
355 | } |
356 | |
357 | bool CConsole::LineIsValid(const char *pStr) |
358 | { |
359 | if(!pStr || *pStr == 0) |
360 | return false; |
361 | |
362 | do |
363 | { |
364 | CResult Result; |
365 | const char *pEnd = pStr; |
366 | const char *pNextPart = 0; |
367 | int InString = 0; |
368 | |
369 | while(*pEnd) |
370 | { |
371 | if(*pEnd == '"') |
372 | InString ^= 1; |
373 | else if(*pEnd == '\\') // escape sequences |
374 | { |
375 | if(pEnd[1] == '"') |
376 | pEnd++; |
377 | } |
378 | else if(!InString) |
379 | { |
380 | if(*pEnd == ';') // command separator |
381 | { |
382 | pNextPart = pEnd + 1; |
383 | break; |
384 | } |
385 | else if(*pEnd == '#') // comment, no need to do anything more |
386 | break; |
387 | } |
388 | |
389 | pEnd++; |
390 | } |
391 | |
392 | if(ParseStart(pResult: &Result, pString: pStr, Length: (pEnd - pStr) + 1) != 0) |
393 | return false; |
394 | |
395 | CCommand *pCommand = FindCommand(pName: Result.m_pCommand, FlagMask: m_FlagMask); |
396 | if(!pCommand || ParseArgs(pResult: &Result, pFormat: pCommand->m_pParams)) |
397 | return false; |
398 | |
399 | pStr = pNextPart; |
400 | } while(pStr && *pStr); |
401 | |
402 | return true; |
403 | } |
404 | |
405 | void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientId, bool InterpretSemicolons) |
406 | { |
407 | const char *pWithoutPrefix = str_startswith(str: pStr, prefix: "mc;" ); |
408 | if(pWithoutPrefix) |
409 | { |
410 | InterpretSemicolons = true; |
411 | pStr = pWithoutPrefix; |
412 | } |
413 | while(pStr && *pStr) |
414 | { |
415 | CResult Result; |
416 | Result.m_ClientId = ClientId; |
417 | const char *pEnd = pStr; |
418 | const char *pNextPart = 0; |
419 | int InString = 0; |
420 | |
421 | while(*pEnd) |
422 | { |
423 | if(*pEnd == '"') |
424 | InString ^= 1; |
425 | else if(*pEnd == '\\') // escape sequences |
426 | { |
427 | if(pEnd[1] == '"') |
428 | pEnd++; |
429 | } |
430 | else if(!InString && InterpretSemicolons) |
431 | { |
432 | if(*pEnd == ';') // command separator |
433 | { |
434 | pNextPart = pEnd + 1; |
435 | break; |
436 | } |
437 | else if(*pEnd == '#') // comment, no need to do anything more |
438 | break; |
439 | } |
440 | |
441 | pEnd++; |
442 | } |
443 | |
444 | if(ParseStart(pResult: &Result, pString: pStr, Length: (pEnd - pStr) + 1) != 0) |
445 | return; |
446 | |
447 | if(!*Result.m_pCommand) |
448 | return; |
449 | |
450 | CCommand *pCommand; |
451 | if(ClientId == IConsole::CLIENT_ID_GAME) |
452 | pCommand = FindCommand(pName: Result.m_pCommand, FlagMask: m_FlagMask | CFGFLAG_GAME); |
453 | else |
454 | pCommand = FindCommand(pName: Result.m_pCommand, FlagMask: m_FlagMask); |
455 | |
456 | if(pCommand) |
457 | { |
458 | if(ClientId == IConsole::CLIENT_ID_GAME && !(pCommand->m_Flags & CFGFLAG_GAME)) |
459 | { |
460 | if(Stroke) |
461 | { |
462 | char aBuf[CMDLINE_LENGTH + 64]; |
463 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Command '%s' cannot be executed from a map." , Result.m_pCommand); |
464 | Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: aBuf); |
465 | } |
466 | } |
467 | else if(ClientId == IConsole::CLIENT_ID_NO_GAME && pCommand->m_Flags & CFGFLAG_GAME) |
468 | { |
469 | if(Stroke) |
470 | { |
471 | char aBuf[CMDLINE_LENGTH + 64]; |
472 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Command '%s' cannot be executed from a non-map config file." , Result.m_pCommand); |
473 | Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: aBuf); |
474 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Hint: Put the command in '%s.cfg' instead of '%s.map.cfg' " , g_Config.m_SvMap, g_Config.m_SvMap); |
475 | Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: aBuf); |
476 | } |
477 | } |
478 | else if(pCommand->GetAccessLevel() >= m_AccessLevel) |
479 | { |
480 | int IsStrokeCommand = 0; |
481 | if(Result.m_pCommand[0] == '+') |
482 | { |
483 | // insert the stroke direction token |
484 | Result.AddArgument(pArg: m_apStrokeStr[Stroke]); |
485 | IsStrokeCommand = 1; |
486 | } |
487 | |
488 | if(Stroke || IsStrokeCommand) |
489 | { |
490 | if(ParseArgs(pResult: &Result, pFormat: pCommand->m_pParams)) |
491 | { |
492 | char aBuf[TEMPCMD_NAME_LENGTH + TEMPCMD_PARAMS_LENGTH + 32]; |
493 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Invalid arguments. Usage: %s %s" , pCommand->m_pName, pCommand->m_pParams); |
494 | Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "chatresp" , pStr: aBuf); |
495 | } |
496 | else if(m_StoreCommands && pCommand->m_Flags & CFGFLAG_STORE) |
497 | { |
498 | m_ExecutionQueue.AddEntry(); |
499 | m_ExecutionQueue.m_pLast->m_pCommand = pCommand; |
500 | m_ExecutionQueue.m_pLast->m_Result = Result; |
501 | } |
502 | else |
503 | { |
504 | if(pCommand->m_Flags & CMDFLAG_TEST && !g_Config.m_SvTestingCommands) |
505 | { |
506 | Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: "Test commands aren't allowed, enable them with 'sv_test_cmds 1' in your initial config." ); |
507 | return; |
508 | } |
509 | |
510 | if(m_pfnTeeHistorianCommandCallback && !(pCommand->m_Flags & CFGFLAG_NONTEEHISTORIC)) |
511 | { |
512 | m_pfnTeeHistorianCommandCallback(ClientId, m_FlagMask, pCommand->m_pName, &Result, m_pTeeHistorianCommandUserdata); |
513 | } |
514 | |
515 | if(Result.GetVictim() == CResult::VICTIM_ME) |
516 | Result.SetVictim(ClientId); |
517 | |
518 | if(Result.HasVictim() && Result.GetVictim() == CResult::VICTIM_ALL) |
519 | { |
520 | for(int i = 0; i < MAX_CLIENTS; i++) |
521 | { |
522 | Result.SetVictim(i); |
523 | pCommand->m_pfnCallback(&Result, pCommand->m_pUserData); |
524 | } |
525 | } |
526 | else |
527 | { |
528 | pCommand->m_pfnCallback(&Result, pCommand->m_pUserData); |
529 | } |
530 | |
531 | if(pCommand->m_Flags & CMDFLAG_TEST) |
532 | m_Cheated = true; |
533 | } |
534 | } |
535 | } |
536 | else if(Stroke) |
537 | { |
538 | char aBuf[CMDLINE_LENGTH + 32]; |
539 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Access for command %s denied." , Result.m_pCommand); |
540 | Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: aBuf); |
541 | } |
542 | } |
543 | else if(Stroke) |
544 | { |
545 | // Pass the original string to the unknown command callback instead of the parsed command, as the latter |
546 | // ends at the first whitespace, which breaks for unknown commands (filenames) containing spaces. |
547 | if(!m_pfnUnknownCommandCallback(pStr, m_pUnknownCommandUserdata)) |
548 | { |
549 | char aBuf[CMDLINE_LENGTH + 32]; |
550 | if(m_FlagMask & CFGFLAG_CHAT) |
551 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "No such command: %s. Use /cmdlist for a list of all commands." , Result.m_pCommand); |
552 | else |
553 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "No such command: %s." , Result.m_pCommand); |
554 | Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "chatresp" , pStr: aBuf); |
555 | } |
556 | } |
557 | |
558 | pStr = pNextPart; |
559 | } |
560 | } |
561 | |
562 | int CConsole::PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser) |
563 | { |
564 | int Index = 0; |
565 | for(CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) |
566 | { |
567 | if(pCommand->m_Flags & FlagMask && pCommand->m_Temp == Temp) |
568 | { |
569 | if(str_find_nocase(haystack: pCommand->m_pName, needle: pStr)) |
570 | { |
571 | pfnCallback(Index, pCommand->m_pName, pUser); |
572 | Index++; |
573 | } |
574 | } |
575 | } |
576 | return Index; |
577 | } |
578 | |
579 | CConsole::CCommand *CConsole::FindCommand(const char *pName, int FlagMask) |
580 | { |
581 | for(CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) |
582 | { |
583 | if(pCommand->m_Flags & FlagMask) |
584 | { |
585 | if(str_comp_nocase(a: pCommand->m_pName, b: pName) == 0) |
586 | return pCommand; |
587 | } |
588 | } |
589 | |
590 | return 0x0; |
591 | } |
592 | |
593 | void CConsole::ExecuteLine(const char *pStr, int ClientId, bool InterpretSemicolons) |
594 | { |
595 | CConsole::ExecuteLineStroked(Stroke: 1, pStr, ClientId, InterpretSemicolons); // press it |
596 | CConsole::ExecuteLineStroked(Stroke: 0, pStr, ClientId, InterpretSemicolons); // then release it |
597 | } |
598 | |
599 | void CConsole::ExecuteLineFlag(const char *pStr, int FlagMask, int ClientId, bool InterpretSemicolons) |
600 | { |
601 | int Temp = m_FlagMask; |
602 | m_FlagMask = FlagMask; |
603 | ExecuteLine(pStr, ClientId, InterpretSemicolons); |
604 | m_FlagMask = Temp; |
605 | } |
606 | |
607 | bool CConsole::ExecuteFile(const char *pFilename, int ClientId, bool LogFailure, int StorageType) |
608 | { |
609 | // make sure that this isn't being executed already |
610 | for(CExecFile *pCur = m_pFirstExec; pCur; pCur = pCur->m_pPrev) |
611 | if(str_comp(a: pFilename, b: pCur->m_pFilename) == 0) |
612 | return false; |
613 | |
614 | if(!m_pStorage) |
615 | return false; |
616 | |
617 | // push this one to the stack |
618 | CExecFile ThisFile; |
619 | CExecFile *pPrev = m_pFirstExec; |
620 | ThisFile.m_pFilename = pFilename; |
621 | ThisFile.m_pPrev = m_pFirstExec; |
622 | m_pFirstExec = &ThisFile; |
623 | |
624 | // exec the file |
625 | CLineReader LineReader; |
626 | bool Success = false; |
627 | char aBuf[32 + IO_MAX_PATH_LENGTH]; |
628 | if(LineReader.OpenFile(File: m_pStorage->OpenFile(pFilename, Flags: IOFLAG_READ, Type: StorageType))) |
629 | { |
630 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "executing '%s'" , pFilename); |
631 | Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: aBuf); |
632 | |
633 | while(const char *pLine = LineReader.Get()) |
634 | { |
635 | ExecuteLine(pStr: pLine, ClientId); |
636 | } |
637 | |
638 | Success = true; |
639 | } |
640 | else if(LogFailure) |
641 | { |
642 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "failed to open '%s'" , pFilename); |
643 | Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: aBuf); |
644 | } |
645 | |
646 | m_pFirstExec = pPrev; |
647 | return Success; |
648 | } |
649 | |
650 | void CConsole::Con_Echo(IResult *pResult, void *pUserData) |
651 | { |
652 | ((CConsole *)pUserData)->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: pResult->GetString(Index: 0)); |
653 | } |
654 | |
655 | void CConsole::Con_Exec(IResult *pResult, void *pUserData) |
656 | { |
657 | ((CConsole *)pUserData)->ExecuteFile(pFilename: pResult->GetString(Index: 0), ClientId: -1, LogFailure: true, StorageType: IStorage::TYPE_ALL); |
658 | } |
659 | |
660 | void CConsole::ConCommandAccess(IResult *pResult, void *pUser) |
661 | { |
662 | CConsole *pConsole = static_cast<CConsole *>(pUser); |
663 | char aBuf[CMDLINE_LENGTH + 64]; |
664 | CCommand *pCommand = pConsole->FindCommand(pName: pResult->GetString(Index: 0), FlagMask: CFGFLAG_SERVER); |
665 | if(pCommand) |
666 | { |
667 | if(pResult->NumArguments() == 2) |
668 | { |
669 | pCommand->SetAccessLevel(pResult->GetInteger(Index: 1)); |
670 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "moderator access for '%s' is now %s" , pResult->GetString(Index: 0), pCommand->GetAccessLevel() ? "enabled" : "disabled" ); |
671 | pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: aBuf); |
672 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "helper access for '%s' is now %s" , pResult->GetString(Index: 0), pCommand->GetAccessLevel() >= ACCESS_LEVEL_HELPER ? "enabled" : "disabled" ); |
673 | pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: aBuf); |
674 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "user access for '%s' is now %s" , pResult->GetString(Index: 0), pCommand->GetAccessLevel() >= ACCESS_LEVEL_USER ? "enabled" : "disabled" ); |
675 | } |
676 | else |
677 | { |
678 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "moderator access for '%s' is %s" , pResult->GetString(Index: 0), pCommand->GetAccessLevel() ? "enabled" : "disabled" ); |
679 | pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: aBuf); |
680 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "helper access for '%s' is %s" , pResult->GetString(Index: 0), pCommand->GetAccessLevel() >= ACCESS_LEVEL_HELPER ? "enabled" : "disabled" ); |
681 | pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: aBuf); |
682 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "user access for '%s' is %s" , pResult->GetString(Index: 0), pCommand->GetAccessLevel() >= ACCESS_LEVEL_USER ? "enabled" : "disabled" ); |
683 | } |
684 | } |
685 | else |
686 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "No such command: '%s'." , pResult->GetString(Index: 0)); |
687 | |
688 | pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console" , pStr: aBuf); |
689 | } |
690 | |
691 | void CConsole::ConCommandStatus(IResult *pResult, void *pUser) |
692 | { |
693 | CConsole *pConsole = static_cast<CConsole *>(pUser); |
694 | char aBuf[240]; |
695 | mem_zero(block: aBuf, size: sizeof(aBuf)); |
696 | int Used = 0; |
697 | |
698 | for(CCommand *pCommand = pConsole->m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) |
699 | { |
700 | if(pCommand->m_Flags & pConsole->m_FlagMask && pCommand->GetAccessLevel() >= clamp(val: pResult->GetInteger(Index: 0), lo: (int)ACCESS_LEVEL_ADMIN, hi: (int)ACCESS_LEVEL_USER)) |
701 | { |
702 | int Length = str_length(str: pCommand->m_pName); |
703 | if(Used + Length + 2 < (int)(sizeof(aBuf))) |
704 | { |
705 | if(Used > 0) |
706 | { |
707 | Used += 2; |
708 | str_append(dst&: aBuf, src: ", " ); |
709 | } |
710 | str_append(dst&: aBuf, src: pCommand->m_pName); |
711 | Used += Length; |
712 | } |
713 | else |
714 | { |
715 | pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "chatresp" , pStr: aBuf); |
716 | mem_zero(block: aBuf, size: sizeof(aBuf)); |
717 | str_copy(dst&: aBuf, src: pCommand->m_pName); |
718 | Used = Length; |
719 | } |
720 | } |
721 | } |
722 | if(Used > 0) |
723 | pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "chatresp" , pStr: aBuf); |
724 | } |
725 | |
726 | void CConsole::ConUserCommandStatus(IResult *pResult, void *pUser) |
727 | { |
728 | CConsole *pConsole = static_cast<CConsole *>(pUser); |
729 | CResult Result; |
730 | Result.m_pCommand = "access_status" ; |
731 | char aBuf[4]; |
732 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d" , (int)IConsole::ACCESS_LEVEL_USER); |
733 | Result.AddArgument(pArg: aBuf); |
734 | |
735 | CConsole::ConCommandStatus(pResult: &Result, pUser: pConsole); |
736 | } |
737 | |
738 | void CConsole::TraverseChain(FCommandCallback *ppfnCallback, void **ppUserData) |
739 | { |
740 | while(*ppfnCallback == Con_Chain) |
741 | { |
742 | CChain *pChainInfo = static_cast<CChain *>(*ppUserData); |
743 | *ppfnCallback = pChainInfo->m_pfnCallback; |
744 | *ppUserData = pChainInfo->m_pCallbackUserData; |
745 | } |
746 | } |
747 | |
748 | CConsole::CConsole(int FlagMask) |
749 | { |
750 | m_FlagMask = FlagMask; |
751 | m_AccessLevel = ACCESS_LEVEL_ADMIN; |
752 | m_pRecycleList = 0; |
753 | m_TempCommands.Reset(); |
754 | m_StoreCommands = true; |
755 | m_apStrokeStr[0] = "0" ; |
756 | m_apStrokeStr[1] = "1" ; |
757 | m_ExecutionQueue.Reset(); |
758 | m_pFirstCommand = 0; |
759 | m_pFirstExec = 0; |
760 | m_pfnTeeHistorianCommandCallback = 0; |
761 | m_pTeeHistorianCommandUserdata = 0; |
762 | |
763 | m_pStorage = 0; |
764 | |
765 | // register some basic commands |
766 | Register(pName: "echo" , pParams: "r[text]" , Flags: CFGFLAG_SERVER, pfnFunc: Con_Echo, pUser: this, pHelp: "Echo the text" ); |
767 | Register(pName: "exec" , pParams: "r[file]" , Flags: CFGFLAG_SERVER | CFGFLAG_CLIENT, pfnFunc: Con_Exec, pUser: this, pHelp: "Execute the specified file" ); |
768 | |
769 | Register(pName: "access_level" , pParams: "s[command] ?i[accesslevel]" , Flags: CFGFLAG_SERVER, pfnFunc: ConCommandAccess, pUser: this, pHelp: "Specify command accessibility (admin = 0, moderator = 1, helper = 2, all = 3)" ); |
770 | Register(pName: "access_status" , pParams: "i[accesslevel]" , Flags: CFGFLAG_SERVER, pfnFunc: ConCommandStatus, pUser: this, pHelp: "List all commands which are accessible for admin = 0, moderator = 1, helper = 2, all = 3" ); |
771 | Register(pName: "cmdlist" , pParams: "" , Flags: CFGFLAG_SERVER | CFGFLAG_CHAT, pfnFunc: ConUserCommandStatus, pUser: this, pHelp: "List all commands which are accessible for users" ); |
772 | |
773 | // DDRace |
774 | |
775 | m_Cheated = false; |
776 | } |
777 | |
778 | CConsole::~CConsole() |
779 | { |
780 | CCommand *pCommand = m_pFirstCommand; |
781 | while(pCommand) |
782 | { |
783 | CCommand *pNext = pCommand->m_pNext; |
784 | { |
785 | FCommandCallback pfnCallback = pCommand->m_pfnCallback; |
786 | void *pUserData = pCommand->m_pUserData; |
787 | CChain *pChain = nullptr; |
788 | while(pfnCallback == Con_Chain) |
789 | { |
790 | pChain = static_cast<CChain *>(pUserData); |
791 | pfnCallback = pChain->m_pfnCallback; |
792 | pUserData = pChain->m_pCallbackUserData; |
793 | delete pChain; |
794 | } |
795 | } |
796 | // Temp commands are on m_TempCommands heap, so don't delete them |
797 | if(!pCommand->m_Temp) |
798 | delete pCommand; |
799 | pCommand = pNext; |
800 | } |
801 | } |
802 | |
803 | void CConsole::Init() |
804 | { |
805 | m_pStorage = Kernel()->RequestInterface<IStorage>(); |
806 | } |
807 | |
808 | void CConsole::ParseArguments(int NumArgs, const char **ppArguments) |
809 | { |
810 | for(int i = 0; i < NumArgs; i++) |
811 | { |
812 | // check for scripts to execute |
813 | if(ppArguments[i][0] == '-' && ppArguments[i][1] == 'f' && ppArguments[i][2] == 0) |
814 | { |
815 | if(NumArgs - i > 1) |
816 | ExecuteFile(pFilename: ppArguments[i + 1], ClientId: -1, LogFailure: true, StorageType: IStorage::TYPE_ABSOLUTE); |
817 | i++; |
818 | } |
819 | else if(!str_comp(a: "-s" , b: ppArguments[i]) || !str_comp(a: "--silent" , b: ppArguments[i])) |
820 | { |
821 | // skip silent param |
822 | continue; |
823 | } |
824 | else |
825 | { |
826 | // search arguments for overrides |
827 | ExecuteLine(pStr: ppArguments[i]); |
828 | } |
829 | } |
830 | } |
831 | |
832 | void CConsole::AddCommandSorted(CCommand *pCommand) |
833 | { |
834 | if(!m_pFirstCommand || str_comp(a: pCommand->m_pName, b: m_pFirstCommand->m_pName) <= 0) |
835 | { |
836 | if(m_pFirstCommand && m_pFirstCommand->m_pNext) |
837 | pCommand->m_pNext = m_pFirstCommand; |
838 | else |
839 | pCommand->m_pNext = 0; |
840 | m_pFirstCommand = pCommand; |
841 | } |
842 | else |
843 | { |
844 | for(CCommand *p = m_pFirstCommand; p; p = p->m_pNext) |
845 | { |
846 | if(!p->m_pNext || str_comp(a: pCommand->m_pName, b: p->m_pNext->m_pName) <= 0) |
847 | { |
848 | pCommand->m_pNext = p->m_pNext; |
849 | p->m_pNext = pCommand; |
850 | break; |
851 | } |
852 | } |
853 | } |
854 | } |
855 | |
856 | void CConsole::Register(const char *pName, const char *pParams, |
857 | int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp) |
858 | { |
859 | CCommand *pCommand = FindCommand(pName, FlagMask: Flags); |
860 | bool DoAdd = false; |
861 | if(pCommand == 0) |
862 | { |
863 | pCommand = new CCommand(); |
864 | DoAdd = true; |
865 | } |
866 | pCommand->m_pfnCallback = pfnFunc; |
867 | pCommand->m_pUserData = pUser; |
868 | |
869 | pCommand->m_pName = pName; |
870 | pCommand->m_pHelp = pHelp; |
871 | pCommand->m_pParams = pParams; |
872 | |
873 | pCommand->m_Flags = Flags; |
874 | pCommand->m_Temp = false; |
875 | |
876 | if(DoAdd) |
877 | AddCommandSorted(pCommand); |
878 | |
879 | if(pCommand->m_Flags & CFGFLAG_CHAT) |
880 | pCommand->SetAccessLevel(ACCESS_LEVEL_USER); |
881 | } |
882 | |
883 | void CConsole::RegisterTemp(const char *pName, const char *pParams, int Flags, const char *pHelp) |
884 | { |
885 | CCommand *pCommand; |
886 | if(m_pRecycleList) |
887 | { |
888 | pCommand = m_pRecycleList; |
889 | str_copy(dst: const_cast<char *>(pCommand->m_pName), src: pName, dst_size: TEMPCMD_NAME_LENGTH); |
890 | str_copy(dst: const_cast<char *>(pCommand->m_pHelp), src: pHelp, dst_size: TEMPCMD_HELP_LENGTH); |
891 | str_copy(dst: const_cast<char *>(pCommand->m_pParams), src: pParams, dst_size: TEMPCMD_PARAMS_LENGTH); |
892 | |
893 | m_pRecycleList = m_pRecycleList->m_pNext; |
894 | } |
895 | else |
896 | { |
897 | pCommand = new(m_TempCommands.Allocate(Size: sizeof(CCommand))) CCommand; |
898 | char *pMem = static_cast<char *>(m_TempCommands.Allocate(Size: TEMPCMD_NAME_LENGTH)); |
899 | str_copy(dst: pMem, src: pName, dst_size: TEMPCMD_NAME_LENGTH); |
900 | pCommand->m_pName = pMem; |
901 | pMem = static_cast<char *>(m_TempCommands.Allocate(Size: TEMPCMD_HELP_LENGTH)); |
902 | str_copy(dst: pMem, src: pHelp, dst_size: TEMPCMD_HELP_LENGTH); |
903 | pCommand->m_pHelp = pMem; |
904 | pMem = static_cast<char *>(m_TempCommands.Allocate(Size: TEMPCMD_PARAMS_LENGTH)); |
905 | str_copy(dst: pMem, src: pParams, dst_size: TEMPCMD_PARAMS_LENGTH); |
906 | pCommand->m_pParams = pMem; |
907 | } |
908 | |
909 | pCommand->m_pfnCallback = 0; |
910 | pCommand->m_pUserData = 0; |
911 | pCommand->m_Flags = Flags; |
912 | pCommand->m_Temp = true; |
913 | |
914 | AddCommandSorted(pCommand); |
915 | } |
916 | |
917 | void CConsole::DeregisterTemp(const char *pName) |
918 | { |
919 | if(!m_pFirstCommand) |
920 | return; |
921 | |
922 | CCommand *pRemoved = 0; |
923 | |
924 | // remove temp entry from command list |
925 | if(m_pFirstCommand->m_Temp && str_comp(a: m_pFirstCommand->m_pName, b: pName) == 0) |
926 | { |
927 | pRemoved = m_pFirstCommand; |
928 | m_pFirstCommand = m_pFirstCommand->m_pNext; |
929 | } |
930 | else |
931 | { |
932 | for(CCommand *pCommand = m_pFirstCommand; pCommand->m_pNext; pCommand = pCommand->m_pNext) |
933 | if(pCommand->m_pNext->m_Temp && str_comp(a: pCommand->m_pNext->m_pName, b: pName) == 0) |
934 | { |
935 | pRemoved = pCommand->m_pNext; |
936 | pCommand->m_pNext = pCommand->m_pNext->m_pNext; |
937 | break; |
938 | } |
939 | } |
940 | |
941 | // add to recycle list |
942 | if(pRemoved) |
943 | { |
944 | pRemoved->m_pNext = m_pRecycleList; |
945 | m_pRecycleList = pRemoved; |
946 | } |
947 | } |
948 | |
949 | void CConsole::DeregisterTempAll() |
950 | { |
951 | // set non temp as first one |
952 | for(; m_pFirstCommand && m_pFirstCommand->m_Temp; m_pFirstCommand = m_pFirstCommand->m_pNext) |
953 | ; |
954 | |
955 | // remove temp entries from command list |
956 | for(CCommand *pCommand = m_pFirstCommand; pCommand && pCommand->m_pNext; pCommand = pCommand->m_pNext) |
957 | { |
958 | CCommand *pNext = pCommand->m_pNext; |
959 | if(pNext->m_Temp) |
960 | { |
961 | for(; pNext && pNext->m_Temp; pNext = pNext->m_pNext) |
962 | ; |
963 | pCommand->m_pNext = pNext; |
964 | } |
965 | } |
966 | |
967 | m_TempCommands.Reset(); |
968 | m_pRecycleList = 0; |
969 | } |
970 | |
971 | void CConsole::Con_Chain(IResult *pResult, void *pUserData) |
972 | { |
973 | CChain *pInfo = (CChain *)pUserData; |
974 | pInfo->m_pfnChainCallback(pResult, pInfo->m_pUserData, pInfo->m_pfnCallback, pInfo->m_pCallbackUserData); |
975 | } |
976 | |
977 | void CConsole::Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser) |
978 | { |
979 | CCommand *pCommand = FindCommand(pName, FlagMask: m_FlagMask); |
980 | |
981 | if(!pCommand) |
982 | { |
983 | char aBuf[256]; |
984 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "failed to chain '%s'" , pName); |
985 | Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "console" , pStr: aBuf); |
986 | return; |
987 | } |
988 | |
989 | CChain *pChainInfo = new CChain(); |
990 | |
991 | // store info |
992 | pChainInfo->m_pfnChainCallback = pfnChainFunc; |
993 | pChainInfo->m_pUserData = pUser; |
994 | pChainInfo->m_pfnCallback = pCommand->m_pfnCallback; |
995 | pChainInfo->m_pCallbackUserData = pCommand->m_pUserData; |
996 | |
997 | // chain |
998 | pCommand->m_pfnCallback = Con_Chain; |
999 | pCommand->m_pUserData = pChainInfo; |
1000 | } |
1001 | |
1002 | void CConsole::StoreCommands(bool Store) |
1003 | { |
1004 | if(!Store) |
1005 | { |
1006 | for(CExecutionQueue::CQueueEntry *pEntry = m_ExecutionQueue.m_pFirst; pEntry; pEntry = pEntry->m_pNext) |
1007 | pEntry->m_pCommand->m_pfnCallback(&pEntry->m_Result, pEntry->m_pCommand->m_pUserData); |
1008 | m_ExecutionQueue.Reset(); |
1009 | } |
1010 | m_StoreCommands = Store; |
1011 | } |
1012 | |
1013 | const IConsole::CCommandInfo *CConsole::GetCommandInfo(const char *pName, int FlagMask, bool Temp) |
1014 | { |
1015 | for(CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) |
1016 | { |
1017 | if(pCommand->m_Flags & FlagMask && pCommand->m_Temp == Temp) |
1018 | { |
1019 | if(str_comp_nocase(a: pCommand->m_pName, b: pName) == 0) |
1020 | return pCommand; |
1021 | } |
1022 | } |
1023 | |
1024 | return 0; |
1025 | } |
1026 | |
1027 | std::unique_ptr<IConsole> CreateConsole(int FlagMask) { return std::make_unique<CConsole>(args&: FlagMask); } |
1028 | |
1029 | int CConsole::CResult::GetVictim() const |
1030 | { |
1031 | return m_Victim; |
1032 | } |
1033 | |
1034 | void CConsole::CResult::ResetVictim() |
1035 | { |
1036 | m_Victim = VICTIM_NONE; |
1037 | } |
1038 | |
1039 | bool CConsole::CResult::HasVictim() const |
1040 | { |
1041 | return m_Victim != VICTIM_NONE; |
1042 | } |
1043 | |
1044 | void CConsole::CResult::SetVictim(int Victim) |
1045 | { |
1046 | m_Victim = clamp<int>(val: Victim, lo: VICTIM_NONE, hi: MAX_CLIENTS - 1); |
1047 | } |
1048 | |
1049 | void CConsole::CResult::SetVictim(const char *pVictim) |
1050 | { |
1051 | if(!str_comp(a: pVictim, b: "me" )) |
1052 | m_Victim = VICTIM_ME; |
1053 | else if(!str_comp(a: pVictim, b: "all" )) |
1054 | m_Victim = VICTIM_ALL; |
1055 | else |
1056 | m_Victim = clamp<int>(val: str_toint(str: pVictim), lo: 0, hi: MAX_CLIENTS - 1); |
1057 | } |
1058 | |