core/CConCmdManager.cpp
author David Anderson <dvander@alliedmods.net>
Fri Feb 16 04:16:31 2007 +0000 (2007-02-16)
changeset 502 4530ea065a8b
parent 500 868e9b19a872
child 503 c4b8f43bded1
permissions -rw-r--r--
fixed a serious potential bug with swapping plugins to debug mode, leaving stale function pointers without plugin removal
     1 #include "CConCmdManager.h"
     2 #include "sm_srvcmds.h"
     3 
     4 CConCmdManager g_ConCmds;
     5 
     6 SH_DECL_HOOK0_void(ConCommand, Dispatch, SH_NOATTRIB, false);
     7 SH_DECL_HOOK1_void(IServerGameClients, SetCommandClient, SH_NOATTRIB, false, int);
     8 
     9 struct PlCmdInfo
    10 {
    11 	ConCmdInfo *pInfo;
    12 	CmdType type;
    13 };
    14 
    15 typedef List<PlCmdInfo> CmdList;
    16 
    17 char *sm_strdup(const char *str)
    18 {
    19 	char *ptr = new char[strlen(str)+1];
    20 	strcpy(ptr, str);
    21 	return ptr;
    22 }
    23 
    24 CConCmdManager::CConCmdManager()
    25 {
    26 	m_pCmds = sm_trie_create();
    27 }
    28 
    29 CConCmdManager::~CConCmdManager()
    30 {
    31 	sm_trie_destroy(m_pCmds);
    32 }
    33 
    34 void CConCmdManager::OnSourceModAllInitialized()
    35 {
    36 	g_PluginSys.AddPluginsListener(this);
    37 	g_RootMenu.AddRootConsoleCommand("cmds", "List console commands", this);
    38 	SH_ADD_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &CConCmdManager::SetCommandClient, false);
    39 }
    40 
    41 void CConCmdManager::OnSourceModShutdown()
    42 {
    43 	/* All commands should already be removed by the time we're done */
    44 	SH_REMOVE_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &CConCmdManager::SetCommandClient, false);
    45 	g_RootMenu.RemoveRootConsoleCommand("cmds", this);
    46 	g_PluginSys.RemovePluginsListener(this);
    47 }
    48 
    49 void CConCmdManager::OnPluginLoaded(IPlugin *plugin)
    50 {
    51 	/* Nothing yet... */
    52 }
    53 
    54 void CConCmdManager::OnPluginDestroyed(IPlugin *plugin)
    55 {
    56 	CmdList *pList;
    57 	List<ConCmdInfo *> removed;
    58 	if (plugin->GetProperty("CommandList", (void **)&pList, true))
    59 	{
    60 		CmdList::iterator iter;
    61 		for (iter=pList->begin();
    62 			 iter!=pList->end();
    63 			 iter++)
    64 		{
    65 			PlCmdInfo &cmd = (*iter);
    66 			if (cmd.type == Cmd_Server
    67 				|| cmd.type == Cmd_Console)
    68 			{
    69 				ConCmdInfo *pInfo = cmd.pInfo;
    70 				/* See if this is being removed */
    71 				if (removed.find(pInfo) != removed.end())
    72 				{
    73 					continue;
    74 				}
    75 				/* See if there are still hooks */
    76 				if (pInfo->srvhooks
    77 					&& pInfo->srvhooks->GetFunctionCount())
    78 				{
    79 					continue;
    80 				}
    81 				if (pInfo->conhooks
    82 					&& pInfo->conhooks->GetFunctionCount())
    83 				{
    84 					continue;
    85 				}
    86 				/* Remove the command */
    87 				RemoveConCmd(pInfo);
    88 				removed.push_back(pInfo);
    89 			}
    90 		}
    91 		delete pList;
    92 	}
    93 }
    94 
    95 void CommandCallback()
    96 {
    97 	g_ConCmds.InternalDispatch();
    98 }
    99 
   100 void CConCmdManager::SetCommandClient(int client)
   101 {
   102 	m_CmdClient = client + 1;
   103 }
   104 
   105 ResultType CConCmdManager::DispatchClientCommand(int client, ResultType type)
   106 {
   107 	const char *cmd = engine->Cmd_Argv(0);
   108 	int args = engine->Cmd_Argc() - 1;
   109 
   110 	ConCmdInfo *pInfo;
   111 	if (sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   112 	{
   113 		cell_t result = type;
   114 		if (pInfo->conhooks && pInfo->conhooks->GetFunctionCount())
   115 		{
   116 			pInfo->conhooks->PushCell(client);
   117 			pInfo->conhooks->PushCell(args);
   118 			pInfo->conhooks->Execute(&result);
   119 		}
   120 		if (result >= Pl_Stop)
   121 		{
   122 			return Pl_Stop;
   123 		}
   124 		type = (ResultType)result;
   125 	}
   126 
   127 	return type;
   128 }
   129 
   130 void CConCmdManager::InternalDispatch()
   131 {
   132 	/**
   133 	 * Note: Console commands will EITHER go through IServerGameDLL::ClientCommand,
   134 	 * OR this dispatch.  They will NEVER go through both.
   135 	 * -- 
   136 	 * Whether or not it goes through the callback is determined by FCVAR_GAMEDLL
   137 	 */
   138 
   139 	const char *cmd = engine->Cmd_Argv(0);
   140 	ConCmdInfo *pInfo;
   141 	if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   142 	{
   143 		return;
   144 	}
   145 
   146 	cell_t result = Pl_Continue;
   147 	int args = engine->Cmd_Argc() - 1;
   148 
   149 	if (m_CmdClient == 0)
   150 	{
   151 		if (pInfo->srvhooks && pInfo->srvhooks->GetFunctionCount())
   152 		{
   153 			pInfo->srvhooks->PushCell(args);
   154 			pInfo->srvhooks->Execute(&result);
   155 		}
   156 	
   157 		/* Check if there's an early stop */
   158 		if (result >= Pl_Stop)
   159 		{
   160 			if (!pInfo->sourceMod)
   161 			{
   162 				RETURN_META(MRES_SUPERCEDE);
   163 			}
   164 			return;
   165 		}
   166 	}
   167 
   168 	/* Execute console command hooks */
   169 	if (pInfo->conhooks && pInfo->conhooks->GetFunctionCount())
   170 	{
   171 		pInfo->conhooks->PushCell(m_CmdClient);
   172 		pInfo->conhooks->PushCell(args);
   173 		pInfo->conhooks->Execute(&result);
   174 	}
   175 
   176 	if (result >= Pl_Handled)
   177 	{
   178 		if (!pInfo->sourceMod)
   179 		{
   180 			RETURN_META(MRES_SUPERCEDE);
   181 		}
   182 		return;
   183 	}
   184 }
   185 
   186 void CConCmdManager::AddConsoleCommand(IPluginFunction *pFunction, 
   187 									   const char *name, 
   188 									   const char *description, 
   189 									   int flags)
   190 {
   191 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   192 
   193 	if (!pInfo->conhooks)
   194 	{
   195 		pInfo->conhooks = g_Forwards.CreateForwardEx(NULL, ET_Hook, 2, NULL, Param_Cell, Param_Cell);
   196 	}
   197 
   198 	pInfo->conhooks->AddFunction(pFunction);
   199 
   200 	/* Add to the plugin */
   201 	CmdList *pList;
   202 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   203 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   204 	{
   205 		pList = new CmdList();
   206 		pPlugin->SetProperty("CommandList", pList);
   207 	}
   208 	PlCmdInfo info;
   209 	info.pInfo = pInfo;
   210 	info.type = Cmd_Console;
   211 	pList->push_back(info);
   212 }
   213 
   214 void CConCmdManager::AddServerCommand(IPluginFunction *pFunction, 
   215 									  const char *name, 
   216 									  const char *description, 
   217 									  int flags)
   218 
   219 {
   220 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   221 
   222 	if (!pInfo->srvhooks)
   223 	{
   224 		pInfo->srvhooks = g_Forwards.CreateForwardEx(NULL, ET_Hook, 1, NULL, Param_Cell);
   225 	}
   226 
   227 	pInfo->srvhooks->AddFunction(pFunction);
   228 
   229 	/* Add to the plugin */
   230 	CmdList *pList;
   231 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   232 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   233 	{
   234 		pList = new CmdList();
   235 		pPlugin->SetProperty("CommandList", pList);
   236 	}
   237 	PlCmdInfo info;
   238 	info.pInfo = pInfo;
   239 	info.type = Cmd_Server;
   240 	pList->push_back(info);
   241 }
   242 
   243 void CConCmdManager::AddToCmdList(ConCmdInfo *info)
   244 {
   245 	List<ConCmdInfo *>::iterator iter = m_CmdList.begin();
   246 	ConCmdInfo *pInfo;
   247 	bool inserted = false;
   248 	const char *orig;
   249 
   250 	if (info->pCmd)
   251 	{
   252 		orig = info->pCmd->GetName();
   253 	}
   254 
   255 	/* Insert this into the help list, SORTED alphabetically. */
   256 	while (iter != m_CmdList.end())
   257 	{
   258 		const char *cmd = NULL;
   259 		pInfo = (*iter);
   260 		if (pInfo->pCmd)
   261 		{
   262 			cmd = pInfo->pCmd->GetName();
   263 		}
   264 		if (strcmp(orig, cmd) < 0)
   265 		{
   266 			m_CmdList.insert(iter, info);
   267 			inserted = true;
   268 			break;
   269 		}
   270 		iter++;
   271 	}
   272 
   273 	if (!inserted)
   274 	{
   275 		m_CmdList.push_back(info);
   276 	}
   277 }
   278 
   279 void CConCmdManager::RemoveConCmd(ConCmdInfo *info)
   280 {
   281 	/* Remove console-specific information */
   282 	if (info->pCmd)
   283 	{
   284 		/* Remove from the trie */
   285 		sm_trie_delete(m_pCmds, info->pCmd->GetName());
   286 
   287 		if (info->sourceMod)
   288 		{
   289 			/* Unlink from SourceMM */
   290 			g_SMAPI->UnregisterConCmdBase(g_PLAPI, info->pCmd);
   291 			/* Delete the command's memory */
   292 			char *new_help = const_cast<char *>(info->pCmd->GetHelpText());
   293 			char *new_name = const_cast<char *>(info->pCmd->GetName());
   294 			delete [] new_help;
   295 			delete [] new_name;
   296 			delete info->pCmd;
   297 		} else {
   298 			/* Remove the external hook */
   299 			SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, info->pCmd, CommandCallback, false);
   300 		}
   301 	}
   302 	
   303 	/* Remove from list */
   304 	m_CmdList.remove(info);
   305 	
   306 	/* Free forwards */
   307 	if (info->srvhooks)
   308 	{
   309 		g_Forwards.ReleaseForward(info->srvhooks);
   310 	}
   311 }
   312 
   313 ConCmdInfo *CConCmdManager::AddOrFindCommand(const char *name, const char *description, int flags)
   314 {
   315 	ConCmdInfo *pInfo;
   316 	if (!sm_trie_retrieve(m_pCmds, name, (void **)&pInfo))
   317 	{
   318 		pInfo = new ConCmdInfo();
   319 		/* Find the commandopan */
   320 		ConCommandBase *pBase = icvar->GetCommands();
   321 		ConCommand *pCmd = NULL;
   322 		while (pBase)
   323 		{
   324 			if (pBase->IsCommand()
   325 				&& (strcmp(pBase->GetName(), name) == 0))
   326 			{
   327 				pCmd = (ConCommand *)pBase;
   328 				break;
   329 			}
   330 			pBase = const_cast<ConCommandBase *>(pBase->GetNext());
   331 		}
   332 
   333 		if (!pCmd)
   334 		{
   335 			/* Note that we have to duplicate because the source might not be 
   336 			 * a static string, and these expect static memory.
   337 			 */
   338 			if (!description)
   339 			{
   340 				description = "";
   341 			}
   342 			char *new_name = sm_strdup(name);
   343 			char *new_help = sm_strdup(description);
   344 			pCmd = new ConCommand(new_name, CommandCallback, new_help, flags);
   345 			pInfo->sourceMod = true;
   346 		} else {
   347 			SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, pCmd, CommandCallback, false);
   348 		}
   349 
   350 		pInfo->pCmd = pCmd;
   351 
   352 		sm_trie_insert(m_pCmds, name, pInfo);
   353 		AddToCmdList(pInfo);
   354 	}
   355 
   356 	return pInfo;
   357 }
   358 
   359 void CConCmdManager::OnRootConsoleCommand(const char *command, unsigned int argcount)
   360 {
   361 	if (argcount >= 3)
   362 	{
   363 		const char *text = engine->Cmd_Argv(2);
   364 		int id = atoi(text);
   365 		CPlugin *pPlugin = g_PluginSys.GetPluginByOrder(id);
   366 
   367 		if (!pPlugin)
   368 		{
   369 			g_RootMenu.ConsolePrint("[SM] Plugin index %d not found.", id);
   370 			return;
   371 		}
   372 
   373 		CmdList *pList;
   374 		if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   375 		{
   376 			g_RootMenu.ConsolePrint("[SM] No commands found for %s", pPlugin->GetFilename());
   377 			return;
   378 		}
   379 		if (!pList->size())
   380 		{
   381 			g_RootMenu.ConsolePrint("[SM] No commands found for %s", pPlugin->GetFilename());
   382 			return;
   383 		}
   384 
   385 		CmdList::iterator iter;
   386 		const char *type = NULL;
   387 		const char *name;
   388 		const char *help;
   389 		g_RootMenu.ConsolePrint(" %-17.16s %-8.7s %s", "[Name]", "[Type]", "[Help]");
   390 		for (iter=pList->begin();
   391 			 iter!=pList->end();
   392 			 iter++)
   393 		{
   394 			PlCmdInfo &cmd = (*iter);
   395 			if (cmd.type == Cmd_Server)
   396 			{
   397 				type = "server";
   398 			} else if (cmd.type == Cmd_Console) {
   399 				type = "console";
   400 			} else if (cmd.type == Cmd_Client) {
   401 				type = "client";
   402 			}
   403 			name = cmd.pInfo->pCmd->GetName();
   404 			help = cmd.pInfo->pCmd->GetHelpText();
   405 			g_RootMenu.ConsolePrint(" %-17.16s %-12.11s %s", name, type, help);		
   406 		}
   407 
   408 		return;
   409 	}
   410 
   411 	g_RootMenu.ConsolePrint("[SM] Usage: sm cmds <plugin #>");
   412 }