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. */
6 #include <base/system.h>
8 #include <engine/storage.h>
9 #include <engine/shared/protocol.h>
13 #include "linereader.h"
15 const char *CConsole::CResult::GetString(unsigned Index
)
17 if (Index
< 0 || Index
>= m_NumArgs
)
19 return m_apArgs
[Index
];
22 int CConsole::CResult::GetInteger(unsigned Index
)
24 if (Index
< 0 || Index
>= m_NumArgs
)
26 return str_toint(m_apArgs
[Index
]);
29 float CConsole::CResult::GetFloat(unsigned Index
)
31 if (Index
< 0 || Index
>= m_NumArgs
)
33 return str_tofloat(m_apArgs
[Index
]);
36 const IConsole::CCommandInfo
*CConsole::CCommand::NextCommandInfo(int AccessLevel
, int FlagMask
) const
38 const CCommand
*pInfo
= m_pNext
;
41 if(pInfo
->m_Flags
&FlagMask
&& pInfo
->m_AccessLevel
>= AccessLevel
)
43 pInfo
= pInfo
->m_pNext
;
48 const IConsole::CCommandInfo
*CConsole::FirstCommandInfo(int AccessLevel
, int FlagMask
) const
50 for(const CCommand
*pCommand
= m_pFirstCommand
; pCommand
; pCommand
= pCommand
->m_pNext
)
52 if(pCommand
->m_Flags
&FlagMask
&& pCommand
->GetAccessLevel() >= AccessLevel
)
59 // the maximum number of tokens occurs in a string of length CONSOLE_MAX_STR_LENGTH with tokens size 1 separated by single spaces
62 int CConsole::ParseStart(CResult
*pResult
, const char *pString
, int Length
)
65 int Len
= sizeof(pResult
->m_aStringStorage
);
69 str_copy(pResult
->m_aStringStorage
, pString
, Length
);
70 pStr
= pResult
->m_aStringStorage
;
73 pStr
= str_skip_whitespaces(pStr
);
74 pResult
->m_pCommand
= pStr
;
75 pStr
= str_skip_to_whitespace(pStr
);
83 pResult
->m_pArgsStart
= pStr
;
87 int CConsole::ParseArgs(CResult
*pResult
, const char *pFormat
)
94 pStr
= pResult
->m_pArgsStart
;
109 pStr
= str_skip_whitespaces(pStr
);
111 if(!(*pStr
)) // error, non optional command needs value
123 pResult
->AddArgument(pStr
);
125 pDst
= pStr
; // we might have to process escape data
130 else if(pStr
[0] == '\\')
133 pStr
++; // skip due to escape
134 else if(pStr
[1] == '"')
135 pStr
++; // skip due to escape
137 else if(pStr
[0] == 0)
138 return 1; // return error
145 // write null termination
153 pResult
->AddArgument(pStr
);
155 if(Command
== 'r') // rest of the string
157 else if(Command
== 'i') // validate int
158 pStr
= str_skip_to_whitespace(pStr
);
159 else if(Command
== 'f') // validate float
160 pStr
= str_skip_to_whitespace(pStr
);
161 else if(Command
== 's') // validate string
162 pStr
= str_skip_to_whitespace(pStr
);
164 if(pStr
[0] != 0) // check for end of string
176 int CConsole::RegisterPrintCallback(int OutputLevel
, FPrintCallback pfnPrintCallback
, void *pUserData
)
178 if(m_NumPrintCB
== MAX_PRINT_CB
)
181 m_aPrintCB
[m_NumPrintCB
].m_OutputLevel
= clamp(OutputLevel
, (int)(OUTPUT_LEVEL_STANDARD
), (int)(OUTPUT_LEVEL_DEBUG
));
182 m_aPrintCB
[m_NumPrintCB
].m_pfnPrintCallback
= pfnPrintCallback
;
183 m_aPrintCB
[m_NumPrintCB
].m_pPrintCallbackUserdata
= pUserData
;
184 return m_NumPrintCB
++;
187 void CConsole::SetPrintOutputLevel(int Index
, int OutputLevel
)
189 if(Index
>= 0 && Index
< MAX_PRINT_CB
)
190 m_aPrintCB
[Index
].m_OutputLevel
= clamp(OutputLevel
, (int)(OUTPUT_LEVEL_STANDARD
), (int)(OUTPUT_LEVEL_DEBUG
));
193 void CConsole::Print(int Level
, const char *pFrom
, const char *pStr
)
195 dbg_msg(pFrom
,"%s", pStr
);
196 for(int i
= 0; i
< m_NumPrintCB
; ++i
)
198 if(Level
<= m_aPrintCB
[i
].m_OutputLevel
&& m_aPrintCB
[i
].m_pfnPrintCallback
)
201 str_format(aBuf
, sizeof(aBuf
), "[%s]: %s", pFrom
, pStr
);
202 m_aPrintCB
[i
].m_pfnPrintCallback(aBuf
, m_aPrintCB
[i
].m_pPrintCallbackUserdata
);
207 bool CConsole::LineIsValid(const char *pStr
)
209 if(!pStr
|| *pStr
== 0)
215 const char *pEnd
= pStr
;
216 const char *pNextPart
= 0;
223 else if(*pEnd
== '\\') // escape sequences
230 if(*pEnd
== ';') // command separator
235 else if(*pEnd
== '#') // comment, no need to do anything more
242 if(ParseStart(&Result
, pStr
, (pEnd
-pStr
) + 1) != 0)
245 CCommand
*pCommand
= FindCommand(Result
.m_pCommand
, m_FlagMask
);
246 if(!pCommand
|| ParseArgs(&Result
, pCommand
->m_pParams
))
251 while(pStr
&& *pStr
);
256 void CConsole::ExecuteLineStroked(int Stroke
, const char *pStr
)
261 const char *pEnd
= pStr
;
262 const char *pNextPart
= 0;
269 else if(*pEnd
== '\\') // escape sequences
276 if(*pEnd
== ';') // command separator
281 else if(*pEnd
== '#') // comment, no need to do anything more
288 if(ParseStart(&Result
, pStr
, (pEnd
-pStr
) + 1) != 0)
291 if(!*Result
.m_pCommand
)
294 CCommand
*pCommand
= FindCommand(Result
.m_pCommand
, m_FlagMask
);
298 if(pCommand
->GetAccessLevel() >= m_AccessLevel
)
300 int IsStrokeCommand
= 0;
301 if(Result
.m_pCommand
[0] == '+')
303 // insert the stroke direction token
304 Result
.AddArgument(m_paStrokeStr
[Stroke
]);
308 if(Stroke
|| IsStrokeCommand
)
310 if(ParseArgs(&Result
, pCommand
->m_pParams
))
313 str_format(aBuf
, sizeof(aBuf
), "Invalid arguments... Usage: %s %s", pCommand
->m_pName
, pCommand
->m_pParams
);
314 Print(OUTPUT_LEVEL_STANDARD
, "Console", aBuf
);
316 else if(m_StoreCommands
&& pCommand
->m_Flags
&CFGFLAG_STORE
)
318 m_ExecutionQueue
.AddEntry();
319 m_ExecutionQueue
.m_pLast
->m_pfnCommandCallback
= pCommand
->m_pfnCallback
;
320 m_ExecutionQueue
.m_pLast
->m_pCommandUserData
= pCommand
->m_pUserData
;
321 m_ExecutionQueue
.m_pLast
->m_Result
= Result
;
324 pCommand
->m_pfnCallback(&Result
, pCommand
->m_pUserData
);
330 str_format(aBuf
, sizeof(aBuf
), "Access for command %s denied.", Result
.m_pCommand
);
331 Print(OUTPUT_LEVEL_STANDARD
, "Console", aBuf
);
337 str_format(aBuf
, sizeof(aBuf
), "No such command: %s.", Result
.m_pCommand
);
338 Print(OUTPUT_LEVEL_STANDARD
, "Console", aBuf
);
345 void CConsole::PossibleCommands(const char *pStr
, int FlagMask
, bool Temp
, FPossibleCallback pfnCallback
, void *pUser
)
347 for(CCommand
*pCommand
= m_pFirstCommand
; pCommand
; pCommand
= pCommand
->m_pNext
)
349 if(pCommand
->m_Flags
&FlagMask
&& pCommand
->m_Temp
== Temp
)
351 if(str_find_nocase(pCommand
->m_pName
, pStr
))
352 pfnCallback(pCommand
->m_pName
, pUser
);
357 CConsole::CCommand
*CConsole::FindCommand(const char *pName
, int FlagMask
)
359 for(CCommand
*pCommand
= m_pFirstCommand
; pCommand
; pCommand
= pCommand
->m_pNext
)
361 if(pCommand
->m_Flags
&FlagMask
)
363 if(str_comp_nocase(pCommand
->m_pName
, pName
) == 0)
371 void CConsole::ExecuteLine(const char *pStr
)
373 CConsole::ExecuteLineStroked(1, pStr
); // press it
374 CConsole::ExecuteLineStroked(0, pStr
); // then release it
378 void CConsole::ExecuteFile(const char *pFilename
)
380 // make sure that this isn't being executed already
381 for(CExecFile
*pCur
= m_pFirstExec
; pCur
; pCur
= pCur
->m_pPrev
)
382 if(str_comp(pFilename
, pCur
->m_pFilename
) == 0)
386 m_pStorage
= Kernel()->RequestInterface
<IStorage
>();
390 // push this one to the stack
392 CExecFile
*pPrev
= m_pFirstExec
;
393 ThisFile
.m_pFilename
= pFilename
;
394 ThisFile
.m_pPrev
= m_pFirstExec
;
395 m_pFirstExec
= &ThisFile
;
398 IOHANDLE File
= m_pStorage
->OpenFile(pFilename
, IOFLAG_READ
, IStorage::TYPE_ALL
);
406 str_format(aBuf
, sizeof(aBuf
), "executing '%s'", pFilename
);
407 Print(IConsole::OUTPUT_LEVEL_STANDARD
, "console", aBuf
);
410 while((pLine
= lr
.Get()))
417 str_format(aBuf
, sizeof(aBuf
), "failed to open '%s'", pFilename
);
418 Print(IConsole::OUTPUT_LEVEL_STANDARD
, "console", aBuf
);
421 m_pFirstExec
= pPrev
;
424 void CConsole::Con_Echo(IResult
*pResult
, void *pUserData
)
426 ((CConsole
*)pUserData
)->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "Console", pResult
->GetString(0));
429 void CConsole::Con_Exec(IResult
*pResult
, void *pUserData
)
431 ((CConsole
*)pUserData
)->ExecuteFile(pResult
->GetString(0));
434 void CConsole::ConModCommandAccess(IResult
*pResult
, void *pUser
)
436 CConsole
* pConsole
= static_cast<CConsole
*>(pUser
);
438 CCommand
*pCommand
= pConsole
->FindCommand(pResult
->GetString(0), CFGFLAG_SERVER
);
441 if(pResult
->NumArguments() == 2)
443 pCommand
->SetAccessLevel(pResult
->GetInteger(1));
444 str_format(aBuf
, sizeof(aBuf
), "moderator access for '%s' is now %s", pResult
->GetString(0), pCommand
->GetAccessLevel() ? "enabled" : "disabled");
447 str_format(aBuf
, sizeof(aBuf
), "moderator access for '%s' is %s", pResult
->GetString(0), pCommand
->GetAccessLevel() ? "enabled" : "disabled");
450 str_format(aBuf
, sizeof(aBuf
), "No such command: '%s'.", pResult
->GetString(0));
452 pConsole
->Print(OUTPUT_LEVEL_STANDARD
, "Console", aBuf
);
455 void CConsole::ConModCommandStatus(IResult
*pResult
, void *pUser
)
457 CConsole
* pConsole
= static_cast<CConsole
*>(pUser
);
459 mem_zero(aBuf
, sizeof(aBuf
));
462 for(CCommand
*pCommand
= pConsole
->m_pFirstCommand
; pCommand
; pCommand
= pCommand
->m_pNext
)
464 if(pCommand
->m_Flags
&pConsole
->m_FlagMask
&& pCommand
->GetAccessLevel() == ACCESS_LEVEL_MOD
)
466 int Length
= str_length(pCommand
->m_pName
);
467 if(Used
+ Length
+ 2 < (int)(sizeof(aBuf
)))
472 str_append(aBuf
, ", ", sizeof(aBuf
));
474 str_append(aBuf
, pCommand
->m_pName
, sizeof(aBuf
));
479 pConsole
->Print(OUTPUT_LEVEL_STANDARD
, "Console", aBuf
);
480 mem_zero(aBuf
, sizeof(aBuf
));
481 str_copy(aBuf
, pCommand
->m_pName
, sizeof(aBuf
));
487 pConsole
->Print(OUTPUT_LEVEL_STANDARD
, "Console", aBuf
);
490 struct CIntVariableData
492 IConsole
*m_pConsole
;
498 struct CStrVariableData
500 IConsole
*m_pConsole
;
505 static void IntVariableCommand(IConsole::IResult
*pResult
, void *pUserData
)
507 CIntVariableData
*pData
= (CIntVariableData
*)pUserData
;
509 if(pResult
->NumArguments())
511 int Val
= pResult
->GetInteger(0);
514 if(pData
->m_Min
!= pData
->m_Max
)
516 if (Val
< pData
->m_Min
)
518 if (pData
->m_Max
!= 0 && Val
> pData
->m_Max
)
522 *(pData
->m_pVariable
) = Val
;
527 str_format(aBuf
, sizeof(aBuf
), "Value: %d", *(pData
->m_pVariable
));
528 pData
->m_pConsole
->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "Console", aBuf
);
532 static void StrVariableCommand(IConsole::IResult
*pResult
, void *pUserData
)
534 CStrVariableData
*pData
= (CStrVariableData
*)pUserData
;
536 if(pResult
->NumArguments())
538 const char *pString
= pResult
->GetString(0);
539 if(!str_utf8_check(pString
))
545 int Size
= str_utf8_encode(Temp
, static_cast<const unsigned char>(*pString
++));
546 if(Length
+Size
< pData
->m_MaxSize
)
548 mem_copy(pData
->m_pStr
+Length
, &Temp
, Size
);
554 pData
->m_pStr
[Length
] = 0;
557 str_copy(pData
->m_pStr
, pString
, pData
->m_MaxSize
);
562 str_format(aBuf
, sizeof(aBuf
), "Value: %s", pData
->m_pStr
);
563 pData
->m_pConsole
->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "Console", aBuf
);
567 CConsole::CConsole(int FlagMask
)
569 m_FlagMask
= FlagMask
;
570 m_AccessLevel
= ACCESS_LEVEL_ADMIN
;
572 m_TempCommands
.Reset();
573 m_StoreCommands
= true;
574 m_paStrokeStr
[0] = "0";
575 m_paStrokeStr
[1] = "1";
576 m_ExecutionQueue
.Reset();
579 mem_zero(m_aPrintCB
, sizeof(m_aPrintCB
));
584 // register some basic commands
585 Register("echo", "r", CFGFLAG_SERVER
|CFGFLAG_CLIENT
, Con_Echo
, this, "Echo the text");
586 Register("exec", "r", CFGFLAG_SERVER
|CFGFLAG_CLIENT
, Con_Exec
, this, "Execute the specified file");
588 Register("mod_command", "s?i", CFGFLAG_SERVER
, ConModCommandAccess
, this, "Specify command accessibility for moderators");
589 Register("mod_status", "", CFGFLAG_SERVER
, ConModCommandStatus
, this, "List all commands which are accessible for moderators");
591 // TODO: this should disappear
592 #define MACRO_CONFIG_INT(Name,ScriptName,Def,Min,Max,Flags,Desc) \
594 static CIntVariableData Data = { this, &g_Config.m_##Name, Min, Max }; \
595 Register(#ScriptName, "?i", Flags, IntVariableCommand, &Data, Desc); \
598 #define MACRO_CONFIG_STR(Name,ScriptName,Len,Def,Flags,Desc) \
600 static CStrVariableData Data = { this, g_Config.m_##Name, Len }; \
601 Register(#ScriptName, "?r", Flags, StrVariableCommand, &Data, Desc); \
604 #include "config_variables.h"
606 #undef MACRO_CONFIG_INT
607 #undef MACRO_CONFIG_STR
610 void CConsole::ParseArguments(int NumArgs
, const char **ppArguments
)
612 for(int i
= 0; i
< NumArgs
; i
++)
614 // check for scripts to execute
615 if(ppArguments
[i
][0] == '-' && ppArguments
[i
][1] == 'f' && ppArguments
[i
][2] == 0)
618 ExecuteFile(ppArguments
[i
+1]);
621 else if(!str_comp("-s", ppArguments
[i
]) || !str_comp("--silent", ppArguments
[i
]))
628 // search arguments for overrides
629 ExecuteLine(ppArguments
[i
]);
634 void CConsole::AddCommandSorted(CCommand
*pCommand
)
636 if(!m_pFirstCommand
|| str_comp(pCommand
->m_pName
, m_pFirstCommand
->m_pName
) < 0)
638 if(m_pFirstCommand
&& m_pFirstCommand
->m_pNext
)
639 pCommand
->m_pNext
= m_pFirstCommand
;
641 pCommand
->m_pNext
= 0;
642 m_pFirstCommand
= pCommand
;
646 for(CCommand
*p
= m_pFirstCommand
; p
; p
= p
->m_pNext
)
648 if(!p
->m_pNext
|| str_comp(pCommand
->m_pName
, p
->m_pNext
->m_pName
) < 0)
650 pCommand
->m_pNext
= p
->m_pNext
;
651 p
->m_pNext
= pCommand
;
658 void CConsole::Register(const char *pName
, const char *pParams
,
659 int Flags
, FCommandCallback pfnFunc
, void *pUser
, const char *pHelp
)
661 CCommand
*pCommand
= new(mem_alloc(sizeof(CCommand
), sizeof(void*))) CCommand
;
662 pCommand
->m_pfnCallback
= pfnFunc
;
663 pCommand
->m_pUserData
= pUser
;
665 pCommand
->m_pName
= pName
;
666 pCommand
->m_pHelp
= pHelp
;
667 pCommand
->m_pParams
= pParams
;
669 pCommand
->m_Flags
= Flags
;
670 pCommand
->m_Temp
= false;
672 AddCommandSorted(pCommand
);
675 void CConsole::RegisterTemp(const char *pName
, const char *pParams
, int Flags
, const char *pHelp
)
680 pCommand
= m_pRecycleList
;
681 str_copy(const_cast<char *>(pCommand
->m_pName
), pName
, TEMPCMD_NAME_LENGTH
);
682 str_copy(const_cast<char *>(pCommand
->m_pHelp
), pHelp
, TEMPCMD_HELP_LENGTH
);
683 str_copy(const_cast<char *>(pCommand
->m_pParams
), pParams
, TEMPCMD_PARAMS_LENGTH
);
685 m_pRecycleList
= m_pRecycleList
->m_pNext
;
689 pCommand
= new(m_TempCommands
.Allocate(sizeof(CCommand
))) CCommand
;
690 char *pMem
= static_cast<char *>(m_TempCommands
.Allocate(TEMPCMD_NAME_LENGTH
));
691 str_copy(pMem
, pName
, TEMPCMD_NAME_LENGTH
);
692 pCommand
->m_pName
= pMem
;
693 pMem
= static_cast<char *>(m_TempCommands
.Allocate(TEMPCMD_HELP_LENGTH
));
694 str_copy(pMem
, pHelp
, TEMPCMD_HELP_LENGTH
);
695 pCommand
->m_pHelp
= pMem
;
696 pMem
= static_cast<char *>(m_TempCommands
.Allocate(TEMPCMD_PARAMS_LENGTH
));
697 str_copy(pMem
, pParams
, TEMPCMD_PARAMS_LENGTH
);
698 pCommand
->m_pParams
= pMem
;
701 pCommand
->m_pfnCallback
= 0;
702 pCommand
->m_pUserData
= 0;
703 pCommand
->m_Flags
= Flags
;
704 pCommand
->m_Temp
= true;
706 AddCommandSorted(pCommand
);
709 void CConsole::DeregisterTemp(const char *pName
)
714 CCommand
*pRemoved
= 0;
716 // remove temp entry from command list
717 if(m_pFirstCommand
->m_Temp
&& str_comp(m_pFirstCommand
->m_pName
, pName
) == 0)
719 pRemoved
= m_pFirstCommand
;
720 m_pFirstCommand
= m_pFirstCommand
->m_pNext
;
724 for(CCommand
*pCommand
= m_pFirstCommand
; pCommand
->m_pNext
; pCommand
= pCommand
->m_pNext
)
725 if(pCommand
->m_pNext
->m_Temp
&& str_comp(pCommand
->m_pNext
->m_pName
, pName
) == 0)
727 pRemoved
= pCommand
->m_pNext
;
728 pCommand
->m_pNext
= pCommand
->m_pNext
->m_pNext
;
733 // add to recycle list
736 pRemoved
->m_pNext
= m_pRecycleList
;
737 m_pRecycleList
= pRemoved
;
741 void CConsole::DeregisterTempAll()
743 // set non temp as first one
744 for(; m_pFirstCommand
&& m_pFirstCommand
->m_Temp
; m_pFirstCommand
= m_pFirstCommand
->m_pNext
);
746 // remove temp entries from command list
747 for(CCommand
*pCommand
= m_pFirstCommand
; pCommand
&& pCommand
->m_pNext
; pCommand
= pCommand
->m_pNext
)
749 CCommand
*pNext
= pCommand
->m_pNext
;
752 for(; pNext
&& pNext
->m_Temp
; pNext
= pNext
->m_pNext
);
753 pCommand
->m_pNext
= pNext
;
757 m_TempCommands
.Reset();
761 void CConsole::Con_Chain(IResult
*pResult
, void *pUserData
)
763 CChain
*pInfo
= (CChain
*)pUserData
;
764 pInfo
->m_pfnChainCallback(pResult
, pInfo
->m_pUserData
, pInfo
->m_pfnCallback
, pInfo
->m_pCallbackUserData
);
767 void CConsole::Chain(const char *pName
, FChainCommandCallback pfnChainFunc
, void *pUser
)
769 CCommand
*pCommand
= FindCommand(pName
, m_FlagMask
);
774 str_format(aBuf
, sizeof(aBuf
), "failed to chain '%s'", pName
);
775 Print(IConsole::OUTPUT_LEVEL_DEBUG
, "console", aBuf
);
779 CChain
*pChainInfo
= (CChain
*)mem_alloc(sizeof(CChain
), sizeof(void*));
782 pChainInfo
->m_pfnChainCallback
= pfnChainFunc
;
783 pChainInfo
->m_pUserData
= pUser
;
784 pChainInfo
->m_pfnCallback
= pCommand
->m_pfnCallback
;
785 pChainInfo
->m_pCallbackUserData
= pCommand
->m_pUserData
;
788 pCommand
->m_pfnCallback
= Con_Chain
;
789 pCommand
->m_pUserData
= pChainInfo
;
792 void CConsole::StoreCommands(bool Store
)
796 for(CExecutionQueue::CQueueEntry
*pEntry
= m_ExecutionQueue
.m_pFirst
; pEntry
; pEntry
= pEntry
->m_pNext
)
797 pEntry
->m_pfnCommandCallback(&pEntry
->m_Result
, pEntry
->m_pCommandUserData
);
798 m_ExecutionQueue
.Reset();
800 m_StoreCommands
= Store
;
804 const IConsole::CCommandInfo
*CConsole::GetCommandInfo(const char *pName
, int FlagMask
, bool Temp
)
806 for(CCommand
*pCommand
= m_pFirstCommand
; pCommand
; pCommand
= pCommand
->m_pNext
)
808 if(pCommand
->m_Flags
&FlagMask
&& pCommand
->m_Temp
== Temp
)
810 if(str_comp_nocase(pCommand
->m_pName
, pName
) == 0)
819 extern IConsole
*CreateConsole(int FlagMask
) { return new CConsole(FlagMask
); }