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
22const char *CConsole::CResult::GetString(unsigned Index) const
23{
24 if(Index >= m_NumArgs)
25 return "";
26 return m_apArgs[Index];
27}
28
29int 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
36float 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
43ColorHSLA 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
82const 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
94const 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
107int 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
132int 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
246char 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
272char *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
281LEVEL 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
296int 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
305LOG_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
313void 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
327void CConsole::SetTeeHistorianCommandCallback(FTeeHistorianCommandCallback pfnCallback, void *pUser)
328{
329 m_pfnTeeHistorianCommandCallback = pfnCallback;
330 m_pTeeHistorianCommandUserdata = pUser;
331}
332
333void CConsole::SetUnknownCommandCallback(FUnknownCommandCallback pfnCallback, void *pUser)
334{
335 m_pfnUnknownCommandCallback = pfnCallback;
336 m_pUnknownCommandUserdata = pUser;
337}
338
339void 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
357bool 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
405void 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
562int 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
579CConsole::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
593void 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
599void 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
607bool 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 IOHANDLE File = m_pStorage->OpenFile(pFilename, Flags: IOFLAG_READ | IOFLAG_SKIP_BOM, Type: StorageType);
626
627 bool Success = false;
628 char aBuf[32 + IO_MAX_PATH_LENGTH];
629 if(File)
630 {
631 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "executing '%s'", pFilename);
632 Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "console", pStr: aBuf);
633
634 CLineReader Reader;
635 Reader.Init(File);
636
637 char *pLine;
638 while((pLine = Reader.Get()))
639 ExecuteLine(pStr: pLine, ClientId);
640
641 io_close(io: File);
642 Success = true;
643 }
644 else if(LogFailure)
645 {
646 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "failed to open '%s'", pFilename);
647 Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "console", pStr: aBuf);
648 }
649
650 m_pFirstExec = pPrev;
651 return Success;
652}
653
654void CConsole::Con_Echo(IResult *pResult, void *pUserData)
655{
656 ((CConsole *)pUserData)->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "console", pStr: pResult->GetString(Index: 0));
657}
658
659void CConsole::Con_Exec(IResult *pResult, void *pUserData)
660{
661 ((CConsole *)pUserData)->ExecuteFile(pFilename: pResult->GetString(Index: 0), ClientId: -1, LogFailure: true, StorageType: IStorage::TYPE_ALL);
662}
663
664void CConsole::ConCommandAccess(IResult *pResult, void *pUser)
665{
666 CConsole *pConsole = static_cast<CConsole *>(pUser);
667 char aBuf[CMDLINE_LENGTH + 64];
668 CCommand *pCommand = pConsole->FindCommand(pName: pResult->GetString(Index: 0), FlagMask: CFGFLAG_SERVER);
669 if(pCommand)
670 {
671 if(pResult->NumArguments() == 2)
672 {
673 pCommand->SetAccessLevel(pResult->GetInteger(Index: 1));
674 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "moderator access for '%s' is now %s", pResult->GetString(Index: 0), pCommand->GetAccessLevel() ? "enabled" : "disabled");
675 pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console", pStr: aBuf);
676 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");
677 pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console", pStr: aBuf);
678 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");
679 }
680 else
681 {
682 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "moderator access for '%s' is %s", pResult->GetString(Index: 0), pCommand->GetAccessLevel() ? "enabled" : "disabled");
683 pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console", pStr: aBuf);
684 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");
685 pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console", pStr: aBuf);
686 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");
687 }
688 }
689 else
690 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "No such command: '%s'.", pResult->GetString(Index: 0));
691
692 pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "console", pStr: aBuf);
693}
694
695void CConsole::ConCommandStatus(IResult *pResult, void *pUser)
696{
697 CConsole *pConsole = static_cast<CConsole *>(pUser);
698 char aBuf[240];
699 mem_zero(block: aBuf, size: sizeof(aBuf));
700 int Used = 0;
701
702 for(CCommand *pCommand = pConsole->m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext)
703 {
704 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))
705 {
706 int Length = str_length(str: pCommand->m_pName);
707 if(Used + Length + 2 < (int)(sizeof(aBuf)))
708 {
709 if(Used > 0)
710 {
711 Used += 2;
712 str_append(dst&: aBuf, src: ", ");
713 }
714 str_append(dst&: aBuf, src: pCommand->m_pName);
715 Used += Length;
716 }
717 else
718 {
719 pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "chatresp", pStr: aBuf);
720 mem_zero(block: aBuf, size: sizeof(aBuf));
721 str_copy(dst&: aBuf, src: pCommand->m_pName);
722 Used = Length;
723 }
724 }
725 }
726 if(Used > 0)
727 pConsole->Print(Level: OUTPUT_LEVEL_STANDARD, pFrom: "chatresp", pStr: aBuf);
728}
729
730void CConsole::ConUserCommandStatus(IResult *pResult, void *pUser)
731{
732 CConsole *pConsole = static_cast<CConsole *>(pUser);
733 CResult Result;
734 Result.m_pCommand = "access_status";
735 char aBuf[4];
736 str_from_int(value: (int)IConsole::ACCESS_LEVEL_USER, dst&: aBuf);
737 Result.AddArgument(pArg: aBuf);
738
739 CConsole::ConCommandStatus(pResult: &Result, pUser: pConsole);
740}
741
742void CConsole::TraverseChain(FCommandCallback *ppfnCallback, void **ppUserData)
743{
744 while(*ppfnCallback == Con_Chain)
745 {
746 CChain *pChainInfo = static_cast<CChain *>(*ppUserData);
747 *ppfnCallback = pChainInfo->m_pfnCallback;
748 *ppUserData = pChainInfo->m_pCallbackUserData;
749 }
750}
751
752CConsole::CConsole(int FlagMask)
753{
754 m_FlagMask = FlagMask;
755 m_AccessLevel = ACCESS_LEVEL_ADMIN;
756 m_pRecycleList = 0;
757 m_TempCommands.Reset();
758 m_StoreCommands = true;
759 m_apStrokeStr[0] = "0";
760 m_apStrokeStr[1] = "1";
761 m_ExecutionQueue.Reset();
762 m_pFirstCommand = 0;
763 m_pFirstExec = 0;
764 m_pfnTeeHistorianCommandCallback = 0;
765 m_pTeeHistorianCommandUserdata = 0;
766
767 m_pStorage = 0;
768
769 // register some basic commands
770 Register(pName: "echo", pParams: "r[text]", Flags: CFGFLAG_SERVER, pfnFunc: Con_Echo, pUser: this, pHelp: "Echo the text");
771 Register(pName: "exec", pParams: "r[file]", Flags: CFGFLAG_SERVER | CFGFLAG_CLIENT, pfnFunc: Con_Exec, pUser: this, pHelp: "Execute the specified file");
772
773 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)");
774 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");
775 Register(pName: "cmdlist", pParams: "", Flags: CFGFLAG_SERVER | CFGFLAG_CHAT, pfnFunc: ConUserCommandStatus, pUser: this, pHelp: "List all commands which are accessible for users");
776
777 // DDRace
778
779 m_Cheated = false;
780}
781
782CConsole::~CConsole()
783{
784 CCommand *pCommand = m_pFirstCommand;
785 while(pCommand)
786 {
787 CCommand *pNext = pCommand->m_pNext;
788 {
789 FCommandCallback pfnCallback = pCommand->m_pfnCallback;
790 void *pUserData = pCommand->m_pUserData;
791 CChain *pChain = nullptr;
792 while(pfnCallback == Con_Chain)
793 {
794 pChain = static_cast<CChain *>(pUserData);
795 pfnCallback = pChain->m_pfnCallback;
796 pUserData = pChain->m_pCallbackUserData;
797 delete pChain;
798 }
799 }
800 // Temp commands are on m_TempCommands heap, so don't delete them
801 if(!pCommand->m_Temp)
802 delete pCommand;
803 pCommand = pNext;
804 }
805}
806
807void CConsole::Init()
808{
809 m_pStorage = Kernel()->RequestInterface<IStorage>();
810}
811
812void CConsole::ParseArguments(int NumArgs, const char **ppArguments)
813{
814 for(int i = 0; i < NumArgs; i++)
815 {
816 // check for scripts to execute
817 if(ppArguments[i][0] == '-' && ppArguments[i][1] == 'f' && ppArguments[i][2] == 0)
818 {
819 if(NumArgs - i > 1)
820 ExecuteFile(pFilename: ppArguments[i + 1], ClientId: -1, LogFailure: true, StorageType: IStorage::TYPE_ABSOLUTE);
821 i++;
822 }
823 else if(!str_comp(a: "-s", b: ppArguments[i]) || !str_comp(a: "--silent", b: ppArguments[i]))
824 {
825 // skip silent param
826 continue;
827 }
828 else
829 {
830 // search arguments for overrides
831 ExecuteLine(pStr: ppArguments[i]);
832 }
833 }
834}
835
836void CConsole::AddCommandSorted(CCommand *pCommand)
837{
838 if(!m_pFirstCommand || str_comp(a: pCommand->m_pName, b: m_pFirstCommand->m_pName) <= 0)
839 {
840 if(m_pFirstCommand && m_pFirstCommand->m_pNext)
841 pCommand->m_pNext = m_pFirstCommand;
842 else
843 pCommand->m_pNext = 0;
844 m_pFirstCommand = pCommand;
845 }
846 else
847 {
848 for(CCommand *p = m_pFirstCommand; p; p = p->m_pNext)
849 {
850 if(!p->m_pNext || str_comp(a: pCommand->m_pName, b: p->m_pNext->m_pName) <= 0)
851 {
852 pCommand->m_pNext = p->m_pNext;
853 p->m_pNext = pCommand;
854 break;
855 }
856 }
857 }
858}
859
860void CConsole::Register(const char *pName, const char *pParams,
861 int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp)
862{
863 CCommand *pCommand = FindCommand(pName, FlagMask: Flags);
864 bool DoAdd = false;
865 if(pCommand == 0)
866 {
867 pCommand = new CCommand();
868 DoAdd = true;
869 }
870 pCommand->m_pfnCallback = pfnFunc;
871 pCommand->m_pUserData = pUser;
872
873 pCommand->m_pName = pName;
874 pCommand->m_pHelp = pHelp;
875 pCommand->m_pParams = pParams;
876
877 pCommand->m_Flags = Flags;
878 pCommand->m_Temp = false;
879
880 if(DoAdd)
881 AddCommandSorted(pCommand);
882
883 if(pCommand->m_Flags & CFGFLAG_CHAT)
884 pCommand->SetAccessLevel(ACCESS_LEVEL_USER);
885}
886
887void CConsole::RegisterTemp(const char *pName, const char *pParams, int Flags, const char *pHelp)
888{
889 CCommand *pCommand;
890 if(m_pRecycleList)
891 {
892 pCommand = m_pRecycleList;
893 str_copy(dst: const_cast<char *>(pCommand->m_pName), src: pName, dst_size: TEMPCMD_NAME_LENGTH);
894 str_copy(dst: const_cast<char *>(pCommand->m_pHelp), src: pHelp, dst_size: TEMPCMD_HELP_LENGTH);
895 str_copy(dst: const_cast<char *>(pCommand->m_pParams), src: pParams, dst_size: TEMPCMD_PARAMS_LENGTH);
896
897 m_pRecycleList = m_pRecycleList->m_pNext;
898 }
899 else
900 {
901 pCommand = new(m_TempCommands.Allocate(Size: sizeof(CCommand))) CCommand;
902 char *pMem = static_cast<char *>(m_TempCommands.Allocate(Size: TEMPCMD_NAME_LENGTH));
903 str_copy(dst: pMem, src: pName, dst_size: TEMPCMD_NAME_LENGTH);
904 pCommand->m_pName = pMem;
905 pMem = static_cast<char *>(m_TempCommands.Allocate(Size: TEMPCMD_HELP_LENGTH));
906 str_copy(dst: pMem, src: pHelp, dst_size: TEMPCMD_HELP_LENGTH);
907 pCommand->m_pHelp = pMem;
908 pMem = static_cast<char *>(m_TempCommands.Allocate(Size: TEMPCMD_PARAMS_LENGTH));
909 str_copy(dst: pMem, src: pParams, dst_size: TEMPCMD_PARAMS_LENGTH);
910 pCommand->m_pParams = pMem;
911 }
912
913 pCommand->m_pfnCallback = 0;
914 pCommand->m_pUserData = 0;
915 pCommand->m_Flags = Flags;
916 pCommand->m_Temp = true;
917
918 AddCommandSorted(pCommand);
919}
920
921void CConsole::DeregisterTemp(const char *pName)
922{
923 if(!m_pFirstCommand)
924 return;
925
926 CCommand *pRemoved = 0;
927
928 // remove temp entry from command list
929 if(m_pFirstCommand->m_Temp && str_comp(a: m_pFirstCommand->m_pName, b: pName) == 0)
930 {
931 pRemoved = m_pFirstCommand;
932 m_pFirstCommand = m_pFirstCommand->m_pNext;
933 }
934 else
935 {
936 for(CCommand *pCommand = m_pFirstCommand; pCommand->m_pNext; pCommand = pCommand->m_pNext)
937 if(pCommand->m_pNext->m_Temp && str_comp(a: pCommand->m_pNext->m_pName, b: pName) == 0)
938 {
939 pRemoved = pCommand->m_pNext;
940 pCommand->m_pNext = pCommand->m_pNext->m_pNext;
941 break;
942 }
943 }
944
945 // add to recycle list
946 if(pRemoved)
947 {
948 pRemoved->m_pNext = m_pRecycleList;
949 m_pRecycleList = pRemoved;
950 }
951}
952
953void CConsole::DeregisterTempAll()
954{
955 // set non temp as first one
956 for(; m_pFirstCommand && m_pFirstCommand->m_Temp; m_pFirstCommand = m_pFirstCommand->m_pNext)
957 ;
958
959 // remove temp entries from command list
960 for(CCommand *pCommand = m_pFirstCommand; pCommand && pCommand->m_pNext; pCommand = pCommand->m_pNext)
961 {
962 CCommand *pNext = pCommand->m_pNext;
963 if(pNext->m_Temp)
964 {
965 for(; pNext && pNext->m_Temp; pNext = pNext->m_pNext)
966 ;
967 pCommand->m_pNext = pNext;
968 }
969 }
970
971 m_TempCommands.Reset();
972 m_pRecycleList = 0;
973}
974
975void CConsole::Con_Chain(IResult *pResult, void *pUserData)
976{
977 CChain *pInfo = (CChain *)pUserData;
978 pInfo->m_pfnChainCallback(pResult, pInfo->m_pUserData, pInfo->m_pfnCallback, pInfo->m_pCallbackUserData);
979}
980
981void CConsole::Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser)
982{
983 CCommand *pCommand = FindCommand(pName, FlagMask: m_FlagMask);
984
985 if(!pCommand)
986 {
987 char aBuf[256];
988 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "failed to chain '%s'", pName);
989 Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "console", pStr: aBuf);
990 return;
991 }
992
993 CChain *pChainInfo = new CChain();
994
995 // store info
996 pChainInfo->m_pfnChainCallback = pfnChainFunc;
997 pChainInfo->m_pUserData = pUser;
998 pChainInfo->m_pfnCallback = pCommand->m_pfnCallback;
999 pChainInfo->m_pCallbackUserData = pCommand->m_pUserData;
1000
1001 // chain
1002 pCommand->m_pfnCallback = Con_Chain;
1003 pCommand->m_pUserData = pChainInfo;
1004}
1005
1006void CConsole::StoreCommands(bool Store)
1007{
1008 if(!Store)
1009 {
1010 for(CExecutionQueue::CQueueEntry *pEntry = m_ExecutionQueue.m_pFirst; pEntry; pEntry = pEntry->m_pNext)
1011 pEntry->m_pCommand->m_pfnCallback(&pEntry->m_Result, pEntry->m_pCommand->m_pUserData);
1012 m_ExecutionQueue.Reset();
1013 }
1014 m_StoreCommands = Store;
1015}
1016
1017const IConsole::CCommandInfo *CConsole::GetCommandInfo(const char *pName, int FlagMask, bool Temp)
1018{
1019 for(CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext)
1020 {
1021 if(pCommand->m_Flags & FlagMask && pCommand->m_Temp == Temp)
1022 {
1023 if(str_comp_nocase(a: pCommand->m_pName, b: pName) == 0)
1024 return pCommand;
1025 }
1026 }
1027
1028 return 0;
1029}
1030
1031std::unique_ptr<IConsole> CreateConsole(int FlagMask) { return std::make_unique<CConsole>(args&: FlagMask); }
1032
1033int CConsole::CResult::GetVictim() const
1034{
1035 return m_Victim;
1036}
1037
1038void CConsole::CResult::ResetVictim()
1039{
1040 m_Victim = VICTIM_NONE;
1041}
1042
1043bool CConsole::CResult::HasVictim() const
1044{
1045 return m_Victim != VICTIM_NONE;
1046}
1047
1048void CConsole::CResult::SetVictim(int Victim)
1049{
1050 m_Victim = clamp<int>(val: Victim, lo: VICTIM_NONE, hi: MAX_CLIENTS - 1);
1051}
1052
1053void CConsole::CResult::SetVictim(const char *pVictim)
1054{
1055 if(!str_comp(a: pVictim, b: "me"))
1056 m_Victim = VICTIM_ME;
1057 else if(!str_comp(a: pVictim, b: "all"))
1058 m_Victim = VICTIM_ALL;
1059 else
1060 m_Victim = clamp<int>(val: str_toint(str: pVictim), lo: 0, hi: MAX_CLIENTS - 1);
1061}
1062