dvander@2807: /** dvander@2807: * vim: set ts=4 sw=4 tw=99 noet : dvander@2807: * ============================================================================= dvander@2807: * SourceMod dvander@2807: * Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved. dvander@2807: * ============================================================================= dvander@2807: * dvander@2807: * This program is free software; you can redistribute it and/or modify it under dvander@2807: * the terms of the GNU General Public License, version 3.0, as published by the dvander@2807: * Free Software Foundation. dvander@2807: * dvander@2807: * This program is distributed in the hope that it will be useful, but WITHOUT dvander@2807: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS dvander@2807: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more dvander@2807: * details. dvander@2807: * dvander@2807: * You should have received a copy of the GNU General Public License along with dvander@2807: * this program. If not, see . dvander@2807: * dvander@2807: * As a special exception, AlliedModders LLC gives you permission to link the dvander@2807: * code of this program (as well as its derivative works) to "Half-Life 2," the dvander@2807: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software dvander@2807: * by the Valve Corporation. You must obey the GNU General Public License in dvander@2807: * all respects for all other code used. Additionally, AlliedModders LLC grants dvander@2807: * this exception to all derivative works. AlliedModders LLC defines further dvander@2807: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), dvander@2807: * or . dvander@2807: * dvander@2807: * Version: $Id$ dvander@2807: */ dvander@2807: dvander@2807: /** dvander@2807: * On SourceHook v4.3 or lower, there are no DVP hooks. Very sad, right? dvander@2841: * Only do this on newer versions. For the older code, we'll do an incredibly dvander@2807: * hacky detour. dvander@2807: * dvander@2807: * The idea of the "non-hacky" (yeah... no) code is that every unique dvander@2807: * ConCommand vtable gets its own DVP hook. We watch for unloading and dvander@2807: * loading commands to remove stale hooks from SH. dvander@2807: */ dvander@2807: dvander@2807: #include "sourcemod.h" dvander@2807: #include "sourcemm_api.h" dvander@2807: #include "Logger.h" dvander@2807: #include "compat_wrappers.h" dvander@2807: #include "ConsoleDetours.h" dvander@3010: #include dvander@2807: #include "sm_stringutil.h" dvander@2807: #include "ConCmdManager.h" dvander@2807: #include "HalfLife2.h" dvander@2807: #include "ConCommandBaseIterator.h" dvander@2841: #include "ShareSys.h" dvander@2807: ds@3027: #if defined PLATFORM_POSIX dvander@2807: # include dvander@2807: # include dvander@2807: # include dvander@2807: # include dvander@2807: #endif dvander@2807: dvander@2807: #if SH_IMPL_VERSION >= 5 dvander@2807: # if SOURCE_ENGINE >= SE_ORANGEBOX dvander@2807: SH_DECL_EXTERN1_void(ConCommand, Dispatch, SH_NOATTRIB, false, const CCommand &); dvander@2807: # else dvander@2807: SH_DECL_EXTERN0_void(ConCommand, Dispatch, SH_NOATTRIB, false); dvander@2807: # endif dvander@2843: #else dvander@2843: # if SH_IMPL_VERSION >= 4 dvander@2843: extern int __SourceHook_FHVPAddConCommandDispatch(void *,bool,class fastdelegate::FastDelegate0,bool); dvander@2843: extern int __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0); dvander@2843: # else dvander@2843: extern bool __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0); dvander@2843: # endif dvander@2807: extern bool __SourceHook_FHRemoveConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0); dvander@2807: #endif dvander@2807: dvander@2807: #if SH_IMPL_VERSION >= 4 dvander@2807: dvander@2807: class GenericCommandHooker : public IConCommandLinkListener dvander@2807: { dvander@2807: struct HackInfo dvander@2807: { dvander@2807: void **vtable; dvander@2807: int hook; dvander@2807: unsigned int refcount; dvander@2807: }; dvander@2807: CVector vtables; dvander@2807: bool enabled; dvander@2807: SourceHook::MemFuncInfo dispatch; dvander@2807: dvander@2807: inline void **GetVirtualTable(ConCommandBase *pBase) dvander@2807: { dvander@2807: return *reinterpret_cast(reinterpret_cast(pBase) + dvander@2807: dispatch.thisptroffs + dvander@2807: dispatch.vtbloffs); dvander@2807: } dvander@2807: dvander@2807: inline bool FindVtable(void **ptr, size_t& index) dvander@2807: { dvander@2807: for (size_t i = 0; i < vtables.size(); i++) dvander@2807: { dvander@2807: if (vtables[i].vtable == ptr) dvander@2807: { dvander@2807: index = i; dvander@2807: return true; dvander@2807: } dvander@2807: } dvander@2807: return false; dvander@2807: } dvander@2807: dvander@2807: void MakeHookable(ConCommandBase *pBase) dvander@2807: { dvander@2807: if (!pBase->IsCommand()) dvander@2807: return; dvander@2807: dvander@2807: ConCommand *cmd = (ConCommand*)pBase; dvander@2807: void **vtable = GetVirtualTable(cmd); dvander@2807: dvander@2807: size_t index; dvander@2807: if (!FindVtable(vtable, index)) dvander@2807: { dvander@2807: HackInfo hack; dvander@2807: hack.vtable = vtable; dvander@2807: hack.hook = SH_ADD_VPHOOK(ConCommand, Dispatch, cmd, SH_MEMBER(this, &GenericCommandHooker::Dispatch), false); dvander@2807: hack.refcount = 1; dvander@2807: vtables.push_back(hack); dvander@2807: } dvander@2807: else dvander@2807: { dvander@2807: vtables[index].refcount++; dvander@2807: } dvander@2807: } dvander@2807: dvander@2807: # if SOURCE_ENGINE >= SE_ORANGEBOX dvander@2807: void Dispatch(const CCommand& args) dvander@2807: # else dvander@2807: void Dispatch() dvander@2807: # endif dvander@2807: { dvander@2807: cell_t res = ConsoleDetours::Dispatch(META_IFACEPTR(ConCommand) dvander@2807: # if SOURCE_ENGINE >= SE_ORANGEBOX dvander@2807: , args dvander@2807: # endif dvander@2807: ); dvander@2807: if (res >= Pl_Handled) dvander@2807: RETURN_META(MRES_SUPERCEDE); dvander@2807: } dvander@2807: dvander@2807: void ReparseCommandList() dvander@2807: { dvander@2807: for (size_t i = 0; i < vtables.size(); i++) dvander@2807: vtables[i].refcount = 0; dvander@2807: for (ConCommandBaseIterator iter; iter.IsValid(); iter.Next()) dvander@2807: MakeHookable(iter.Get()); dvander@2807: CVector::iterator iter = vtables.begin(); dvander@2807: while (iter != vtables.end()) dvander@2807: { dvander@2807: if ((*iter).refcount) dvander@2844: { dvander@2844: iter++; dvander@2807: continue; dvander@2844: } dvander@2807: /* Damn it. This event happens AFTER the plugin has unloaded! dvander@2807: * There's two options. Remove the hook now and hope SH's memory dvander@2807: * protection will prevent a crash. Otherwise, we can wait until dvander@2807: * the server shuts down and more likely crash then. dvander@2807: * dvander@2807: * This situation only arises if: dvander@2807: * 1) Someone has used AddCommandFilter() dvander@2807: * 2) ... on a Dark Messiah server (mm:s new api) dvander@2807: * 3) ... and another MM:S plugin that uses ConCommands has unloaded. dvander@2807: * dvander@2807: * Even though the impact is really small, we'll wait until the dvander@2807: * server shuts down, so normal operation isn't interrupted. dvander@2807: * dvander@2807: * See bug 4018. dvander@2807: */ dvander@2807: iter = vtables.erase(iter); dvander@2807: } dvander@2807: } dvander@2807: dvander@2807: void UnhookCommand(ConCommandBase *pBase) dvander@2807: { dvander@2807: if (!pBase->IsCommand()) dvander@2807: return; dvander@2807: dvander@2807: ConCommand *cmd = (ConCommand*)pBase; dvander@2807: void **vtable = GetVirtualTable(cmd); dvander@2807: dvander@2807: size_t index; dvander@2807: if (!FindVtable(vtable, index)) dvander@2807: { dvander@2807: g_Logger.LogError("Console detour tried to unhook command \"%s\" but it wasn't found", pBase->GetName()); dvander@2807: return; dvander@2807: } dvander@2807: dvander@2807: assert(vtables[index].refcount > 0); dvander@2807: vtables[index].refcount--; dvander@2807: if (vtables[index].refcount == 0) dvander@2807: { dvander@2807: SH_REMOVE_HOOK_ID(vtables[index].hook); dvander@2807: vtables.erase(vtables.iterAt(index)); dvander@2807: } dvander@2807: } dvander@2807: dvander@2807: public: dvander@2807: GenericCommandHooker() : enabled(false) dvander@2807: { dvander@2807: } dvander@2807: dvander@2807: bool Enable() dvander@2807: { dvander@2807: SourceHook::GetFuncInfo(&ConCommand::Dispatch, dispatch); dvander@2807: dvander@2807: if (dispatch.thisptroffs < 0) dvander@2807: { dvander@2807: g_Logger.LogError("Command filter could not determine ConCommand layout"); dvander@2807: return false; dvander@2807: } dvander@2807: dvander@2807: for (ConCommandBaseIterator iter; iter.IsValid(); iter.Next()) dvander@2807: MakeHookable(iter.Get()); dvander@2807: dvander@2807: if (!vtables.size()) dvander@2807: { dvander@2807: g_Logger.LogError("Command filter could not find any cvars!"); dvander@2807: return false; dvander@2807: } dvander@2807: dvander@2807: enabled = true; dvander@2807: dvander@2807: return true; dvander@2807: } dvander@2807: dvander@2807: void Disable() dvander@2807: { dvander@2807: for (size_t i = 0; i < vtables.size(); i++) dvander@2807: SH_REMOVE_HOOK_ID(vtables[i].hook); dvander@2807: vtables.clear(); dvander@2807: } dvander@2807: dvander@2807: virtual void OnLinkConCommand(ConCommandBase *pBase) dvander@2807: { dvander@2807: if (!enabled) dvander@2807: return; dvander@2807: MakeHookable(pBase); dvander@2807: } dvander@2807: dvander@2807: virtual void OnUnlinkConCommand(ConCommandBase *pBase) dvander@2807: { dvander@2807: if (!enabled) dvander@2807: return; dvander@2807: if (pBase == NULL) dvander@2807: ReparseCommandList(); dvander@2807: else dvander@2807: UnhookCommand(pBase); dvander@2807: } dvander@2807: }; dvander@2807: dvander@2807: /** dvander@2807: * END DVP HOOK VERSION dvander@2807: */ dvander@2807: dvander@2807: #else /* SH_IMPL_VERSION >= 4 */ dvander@2807: dvander@2807: /** dvander@2807: * BEGIN ENGINE DETOUR VERSION dvander@2807: */ dvander@2807: dvander@2807: # include dvander@2807: # include dvander@2807: dvander@2807: class GenericCommandHooker dvander@2807: { dvander@2807: struct Patch dvander@2807: { dvander@2807: Patch() : applied(false) dvander@2807: { dvander@2807: } dvander@2807: dvander@2807: bool applied; dvander@2807: void *base; dvander@2807: unsigned char search[16]; dvander@2807: size_t search_len; dvander@2807: size_t offset; dvander@2807: size_t saveat; dvander@2807: int regparam; dvander@2807: void *detour; dvander@2807: }; dvander@2807: dvander@2807: Patch ces; dvander@2807: Patch cgc; dvander@2807: dvander@2807: public: dvander@2842: static void DelayedActivation(void *inparam) dvander@2842: { dvander@2842: GenericCommandHooker *cdtrs = reinterpret_cast(inparam); dvander@2842: /* Safe to re-enter because the frame queue is lock+swapped. */ dvander@2842: if ((!cdtrs->ces.applied || !cdtrs->cgc.applied) && dvander@2842: g_HL2.PeekCommandStack() != NULL) dvander@2842: { dvander@2842: g_SourceMod.AddFrameAction(GenericCommandHooker::DelayedActivation, cdtrs); dvander@2842: return; dvander@2842: } dvander@2842: dvander@2842: if (!cdtrs->ces.applied) dvander@2842: cdtrs->ApplyPatch(&cdtrs->ces); dvander@2842: if (!cdtrs->cgc.applied) dvander@2842: cdtrs->ApplyPatch(&cdtrs->cgc); dvander@2842: } dvander@2842: dvander@2807: bool Enable() dvander@2807: { dvander@2807: const char *platform = NULL; dvander@2807: # if defined(PLATFORM_WINDOWS) dvander@2807: platform = "Windows"; dvander@2807: # else dvander@2807: void *addrInBase = (void *)g_SMAPI->GetEngineFactory(false); dvander@2807: Dl_info info; dvander@2807: if (!dladdr(addrInBase, &info)) dvander@2807: { dvander@2807: g_Logger.LogError("Command filter could not find engine name"); dvander@2807: return false; dvander@2807: } dvander@2807: if (strstr(info.dli_fname, "engine_i486")) dvander@2807: { dvander@2807: platform = "Linux_486"; dvander@2807: } dvander@2807: else if (strstr(info.dli_fname, "engine_i686")) dvander@2807: { dvander@2807: platform = "Linux_686"; dvander@2807: } dvander@2807: else if (strstr(info.dli_fname, "engine_amd")) dvander@2807: { dvander@2807: platform = "Linux_AMD"; dvander@2807: } dvander@2807: else dvander@2807: { dvander@2807: g_Logger.LogError("Command filter could not determine engine (%s)", info.dli_fname); dvander@2807: return false; dvander@2807: } dvander@2807: # endif dvander@2807: dvander@2807: if (!PrepPatch("Cmd_ExecuteString", "CES", platform, &ces)) dvander@2807: return false; dvander@2807: if (!PrepPatch("CGameClient::ExecuteString", "CGC", platform, &cgc)) dvander@2807: return false; dvander@2807: dvander@2842: if (g_HL2.PeekCommandStack() != NULL) dvander@2842: { dvander@2842: g_SourceMod.AddFrameAction(GenericCommandHooker::DelayedActivation, this); dvander@2842: return true; dvander@2842: } dvander@2842: dvander@2807: ApplyPatch(&ces); dvander@2807: ApplyPatch(&cgc); dvander@2807: dvander@2807: return true; dvander@2807: } dvander@2807: dvander@2807: void Disable() dvander@2807: { dvander@2807: UndoPatch(&ces); dvander@2807: UndoPatch(&cgc); dvander@2807: } dvander@2807: dvander@2807: private: dvander@2807: dvander@2807: # if !defined PLATFORM_WINDOWS dvander@2807: # define PAGE_READWRITE PROT_READ|PROT_WRITE dvander@2807: # define PAGE_EXECUTE_READ PROT_READ|PROT_EXEC dvander@2807: dvander@2807: static inline uintptr_t AddrToPage(uintptr_t address) dvander@2807: { dvander@2807: return (address & ~(uintptr_t(sysconf(_SC_PAGE_SIZE) - 1))); dvander@2807: } dvander@2807: dvander@2807: # endif dvander@2807: dvander@2807: void Protect(void *addr, size_t length, int prot) dvander@2807: { dvander@2807: # if defined PLATFORM_WINDOWS dvander@2807: DWORD ignore; dvander@2807: VirtualProtect(addr, length, prot, &ignore); dvander@2807: # else dvander@2807: uintptr_t startPage = AddrToPage(uintptr_t(addr)); dvander@2807: length += (uintptr_t(addr) - startPage); dvander@2807: mprotect((void*)startPage, length, prot); dvander@2807: # endif dvander@2807: } dvander@2807: dvander@2807: void UndoPatch(Patch *patch) dvander@2807: { dvander@2807: if (!patch->applied || patch->detour == NULL) dvander@2807: return; dvander@2807: dvander@2807: g_pSourcePawn->FreePageMemory(patch->detour); dvander@2807: dvander@2807: unsigned char *source = (unsigned char *)patch->base + patch->offset; dvander@2807: Protect(source, patch->search_len, PAGE_READWRITE); dvander@2807: for (size_t i = 0; i < patch->search_len; i++) dvander@2807: source[i] = patch->search[i]; dvander@2807: Protect(source, patch->search_len, PAGE_EXECUTE_READ); dvander@2807: } dvander@2807: dvander@2807: void ApplyPatch(Patch *patch) dvander@2807: { dvander@2807: assert(!patch->applied); dvander@2807: dvander@2807: size_t length = 0; dvander@2807: void *callback = (void*)&ConsoleDetours::Dispatch; dvander@2807: dvander@2807: /* Bogus assignment to make compiler is doing the right thing. */ dvander@2807: patch->detour = callback; dvander@2807: dvander@2807: /* Assemgle the detour. */ dvander@2807: JitWriter writer; dvander@2807: writer.outbase = NULL; dvander@2807: writer.outptr = NULL; dvander@2807: do dvander@2807: { dvander@2807: /* Need a specific register, or value on stack? */ dvander@2807: if (patch->regparam != -1) dvander@2807: IA32_Push_Reg(&writer, patch->regparam); dvander@2807: /* Call real function. */ dvander@2807: IA32_Write_Jump32_Abs(&writer, IA32_Call_Imm32(&writer, 0), callback); dvander@2807: /* Restore stack. */ dvander@2807: if (patch->regparam != -1) dvander@2807: IA32_Pop_Reg(&writer, patch->regparam); dvander@2807: /* Copy any saved bytes */ dvander@2807: if (patch->saveat) dvander@2807: { dvander@2807: for (size_t i = patch->saveat; i < patch->search_len; i++) dvander@2807: { dvander@2807: writer.write_ubyte(patch->search[i]); dvander@2807: } dvander@2807: } dvander@2807: /* Jump back to the caller. */ dvander@2807: unsigned char *target = (unsigned char *)patch->base + patch->offset + patch->search_len; dvander@2807: IA32_Jump_Imm32_Abs(&writer, target); dvander@2807: /* Assemble, if we can. */ dvander@2807: if (writer.outbase == NULL) dvander@2807: { dvander@2807: length = writer.outptr - writer.outbase; dvander@2807: patch->detour = g_pSourcePawn->AllocatePageMemory(length); dvander@2807: if (patch->detour == NULL) dvander@2807: { dvander@2807: g_Logger.LogError("Ran out of memory!"); dvander@2807: return; dvander@2807: } dvander@2807: g_pSourcePawn->SetReadWrite(patch->detour); dvander@2807: writer.outbase = (jitcode_t)patch->detour; dvander@2807: writer.outptr = writer.outbase; dvander@2807: } dvander@2807: else dvander@2807: { dvander@2807: break; dvander@2807: } dvander@2807: } while (true); dvander@2807: dvander@2807: g_pSourcePawn->SetReadExecute(patch->detour); dvander@2807: dvander@2807: unsigned char *source = (unsigned char *)patch->base + patch->offset; dvander@2807: Protect(source, 6, PAGE_READWRITE); dvander@2807: source[0] = 0xFF; dvander@2807: source[1] = 0x25; dvander@2807: *(void **)&source[2] = &patch->detour; dvander@2807: Protect(source, 6, PAGE_EXECUTE_READ); dvander@2807: dvander@2807: patch->applied = true; dvander@2807: } dvander@2807: dvander@2807: bool PrepPatch(const char *signature, const char *name, const char *platform, Patch *patch) dvander@2807: { dvander@2807: /* Get the base address of the function. */ dvander@2807: if (!g_pGameConf->GetMemSig(signature, &patch->base) || patch->base == NULL) dvander@2807: { dvander@2807: g_Logger.LogError("Command filter could not find signature: %s", signature); dvander@2807: return false; dvander@2807: } dvander@2807: dvander@2807: const char *value; dvander@2807: char keyname[255]; dvander@2807: dvander@2807: /* Get the verification bytes that will be written over. */ dvander@2807: UTIL_Format(keyname, sizeof(keyname), "%s_Patch_%s", name, platform); dvander@2807: if ((value = g_pGameConf->GetKeyValue(keyname)) == NULL) dvander@2807: { dvander@2807: g_Logger.LogError("Command filter could not find key: %s", keyname); dvander@2807: return false; dvander@2807: } dvander@2807: patch->search_len = UTIL_DecodeHexString(patch->search, sizeof(patch->search), value); dvander@2807: if (patch->search_len < 6) dvander@2807: { dvander@2807: g_Logger.LogError("Error decoding %s value, or not long enough", keyname); dvander@2807: return false; dvander@2807: } dvander@2807: dvander@2807: /* Get the offset into the function. */ dvander@2807: UTIL_Format(keyname, sizeof(keyname), "%s_Offset_%s", name, platform); dvander@2807: if ((value = g_pGameConf->GetKeyValue(keyname)) == NULL) dvander@2807: { dvander@2807: g_Logger.LogError("Command filter could not find key: %s", keyname); dvander@2807: return false; dvander@2807: } dvander@2807: patch->offset = atoi(value); dvander@2807: if (patch->offset > 20000) dvander@2807: { dvander@2807: g_Logger.LogError("Command filter %s value is bogus", keyname); dvander@2807: return false; dvander@2807: } dvander@2807: dvander@2807: /* Get the number of bytes to save from what was written over. */ dvander@2807: patch->saveat = 0; dvander@2807: UTIL_Format(keyname, sizeof(keyname), "%s_Save_%s", name, platform); dvander@2807: if ((value = g_pGameConf->GetKeyValue(keyname)) != NULL) dvander@2807: { dvander@2807: patch->saveat = atoi(value); dvander@2807: if (patch->saveat >= patch->search_len) dvander@2807: { dvander@2807: g_Logger.LogError("Command filter %s value is too large", keyname); dvander@2807: return false; dvander@2807: } dvander@2807: } dvander@2807: dvander@2807: /* Get register for parameter, if any. */ dvander@2807: patch->regparam = -1; dvander@2807: UTIL_Format(keyname, sizeof(keyname), "%s_Reg_%s", name, platform); dvander@2807: if ((value = g_pGameConf->GetKeyValue(keyname)) != NULL) dvander@2807: { dvander@2807: patch->regparam = atoi(value); dvander@2807: } dvander@2807: dvander@2807: /* Everything loaded from gamedata, make sure the patch will succeed. */ dvander@2807: unsigned char *address = (unsigned char *)patch->base + patch->offset; dvander@2807: for (size_t i = 0; i < patch->search_len; i++) dvander@2807: { dvander@2807: if (address[i] != patch->search[i]) dvander@2807: { dvander@2807: g_Logger.LogError("Command filter %s has changed (byte %x is not %x sub-offset %d)", dvander@2807: name, address[i], patch->search[i], i); dvander@2807: return false; dvander@2807: } dvander@2807: } dvander@2807: dvander@2807: return true; dvander@2807: } dvander@2807: }; dvander@2807: dvander@2807: static bool dummy_hook_set = false; dvander@2807: void DummyHook() dvander@2807: { dvander@2807: if (dummy_hook_set) dvander@2807: { dvander@2807: dummy_hook_set = false; dvander@2807: RETURN_META(MRES_SUPERCEDE); dvander@2807: } dvander@2807: } dvander@2807: dvander@2807: #endif dvander@2807: dvander@2807: /** dvander@2807: * BEGIN THE ACTUALLY GENERIC CODE. dvander@2807: */ dvander@2807: dvander@2841: #define FEATURECAP_COMMANDLISTENER "command listener" dvander@2841: dvander@2807: static GenericCommandHooker s_GenericHooker; dvander@2807: ConsoleDetours g_ConsoleDetours; dvander@2807: dvander@2841: ConsoleDetours::ConsoleDetours() : status(FeatureStatus_Unknown) dvander@2807: { dvander@2807: } dvander@2807: dvander@2807: void ConsoleDetours::OnSourceModAllInitialized() dvander@2807: { dvander@2807: m_pForward = g_Forwards.CreateForwardEx("OnAnyCommand", ET_Hook, 3, NULL, Param_Cell, dvander@2807: Param_String, Param_Cell); dvander@2841: g_ShareSys.AddCapabilityProvider(NULL, this, FEATURECAP_COMMANDLISTENER); dvander@2807: } dvander@2807: dvander@2807: void ConsoleDetours::OnSourceModShutdown() dvander@2807: { dvander@2807: List::iterator iter = m_Listeners.begin(); dvander@2807: while (iter != m_Listeners.end()) dvander@2807: { dvander@2807: Listener *listener = (*iter); dvander@2807: g_Forwards.ReleaseForward(listener->forward); dvander@2807: delete listener; dvander@2807: iter = m_Listeners.erase(iter); dvander@2807: } dvander@2807: dvander@2807: g_Forwards.ReleaseForward(m_pForward); dvander@2807: s_GenericHooker.Disable(); dvander@2807: } dvander@2807: dvander@2841: FeatureStatus ConsoleDetours::GetFeatureStatus(FeatureType type, const char *name) dvander@2807: { dvander@2841: return GetStatus(); dvander@2841: } dvander@2841: dvander@2841: FeatureStatus ConsoleDetours::GetStatus() dvander@2841: { dvander@2841: if (status == FeatureStatus_Unknown) dvander@2841: { dvander@2841: status = s_GenericHooker.Enable() ? FeatureStatus_Available : FeatureStatus_Unavailable; dvander@2841: } dvander@2841: return status; dvander@2807: } dvander@2807: dvander@2807: bool ConsoleDetours::AddListener(IPluginFunction *fun, const char *command) dvander@2807: { dvander@2841: if (GetStatus() != FeatureStatus_Available) dvander@2807: return false; dvander@2807: dvander@2807: if (command == NULL) dvander@2807: { dvander@2807: m_pForward->AddFunction(fun); dvander@2807: } dvander@2807: else dvander@2807: { dvander@2807: const char *str = UTIL_ToLowerCase(command); dvander@2807: Listener *listener; dvander@2807: Listener **plistener = m_CmdLookup.retrieve(str); dvander@2807: if (plistener == NULL) dvander@2807: { dvander@2807: listener = new Listener; dvander@2807: listener->forward = g_Forwards.CreateForwardEx(NULL, ET_Hook, 3, NULL, Param_Cell, dvander@2807: Param_String, Param_Cell); dvander@2807: m_CmdLookup.insert(str, listener); dvander@2807: } dvander@2807: else dvander@2807: { dvander@2807: listener = *plistener; dvander@2807: } dvander@2807: listener->forward->AddFunction(fun); dvander@2807: delete [] str; dvander@2807: } dvander@2807: dvander@2807: return true; dvander@2807: } dvander@2807: dvander@2807: bool ConsoleDetours::RemoveListener(IPluginFunction *fun, const char *command) dvander@2807: { dvander@2807: if (command == NULL) dvander@2807: { dvander@2807: return m_pForward->RemoveFunction(fun); dvander@2807: } dvander@2807: else dvander@2807: { dvander@2807: const char *str = UTIL_ToLowerCase(command); dvander@2807: Listener *listener; dvander@2807: Listener **plistener = m_CmdLookup.retrieve(str); dvander@2807: delete [] str; dvander@2807: if (plistener == NULL) dvander@2807: return false; dvander@2807: listener = *plistener; dvander@2807: return listener->forward->RemoveFunction(fun); dvander@2807: } dvander@2807: } dvander@2807: dvander@2807: cell_t ConsoleDetours::InternalDispatch(int client, const CCommand& args) dvander@2807: { dvander@2807: char name[255]; dvander@2807: const char *realname = args.Arg(0); dvander@2807: size_t len = strlen(realname); dvander@3476: dvander@3476: // Disallow command strings that are too long, for now. dvander@3476: if (len >= sizeof(name) - 1) dvander@3476: return Pl_Continue; dvander@3476: dvander@2807: for (size_t i = 0; i < len; i++) dvander@2807: { dvander@2807: if (realname[i] >= 'A' && realname[i] <= 'Z') dvander@2807: name[i] = tolower(realname[i]); dvander@2807: else dvander@2807: name[i] = realname[i]; dvander@2807: } dvander@2807: name[len] = '\0'; dvander@2807: dvander@2807: cell_t result = Pl_Continue; dvander@2807: m_pForward->PushCell(client); dvander@2807: m_pForward->PushString(name); dvander@2807: m_pForward->PushCell(args.ArgC() - 1); dvander@2807: m_pForward->Execute(&result, NULL); dvander@2807: dvander@2807: /* Don't let plugins block this. */ dvander@2807: if (strcmp(name, "sm") == 0) dvander@2807: result = Pl_Continue; dvander@2807: dvander@2807: if (result >= Pl_Stop) dvander@2807: return result; dvander@2807: dvander@2807: Listener **plistener = m_CmdLookup.retrieve(name); dvander@2807: if (plistener == NULL) dvander@2807: return result; dvander@2807: Listener *listener = *plistener; dvander@2807: if (listener->forward->GetFunctionCount() == 0) dvander@2807: return result; dvander@2807: dvander@2807: cell_t result2 = Pl_Continue; dvander@2807: listener->forward->PushCell(client); dvander@2807: listener->forward->PushString(name); dvander@2807: listener->forward->PushCell(args.ArgC() - 1); dvander@2807: listener->forward->Execute(&result2, NULL); dvander@2807: dvander@2807: if (result2 > result) dvander@2807: result = result2; dvander@2807: dvander@2807: /* "sm" should not have flown through the above. */ dvander@2807: assert(strcmp(name, "sm") != 0 || result == Pl_Continue); dvander@2807: dvander@2807: return result; dvander@2807: } dvander@2807: dvander@2807: #if SOURCE_ENGINE >= SE_ORANGEBOX dvander@2807: cell_t ConsoleDetours::Dispatch(ConCommand *pBase, const CCommand& args) dvander@2807: { dvander@2807: #else dvander@2807: cell_t ConsoleDetours::Dispatch(ConCommand *pBase) dvander@2807: { dvander@2807: CCommand args; dvander@2807: #endif dvander@2807: g_HL2.PushCommandStack(&args); dvander@2807: cell_t res = g_ConsoleDetours.InternalDispatch(g_ConCmds.GetCommandClient(), args); dvander@2807: g_HL2.PopCommandStack(); dvander@2807: dvander@2807: #if SH_IMPL_VERSION < 4 dvander@2807: if (res >= Pl_Handled) dvander@2807: { dvander@2807: /* See bug 4020 - we can't optimize this because looking at the vtable dvander@2807: * is probably more expensive, since there's no SH_GET_ORIG_VFNPTR_ENTRY. dvander@2807: */ kyle@3819: SH_ADD_HOOK(ConCommand, Dispatch, pBase, SH_STATIC(DummyHook), false); dvander@2807: dummy_hook_set = true; dvander@2807: pBase->Dispatch(); kyle@3819: SH_REMOVE_HOOK(ConCommand, Dispatch, pBase, SH_STATIC(DummyHook), false); dvander@2807: } dvander@2807: else dvander@2807: { dvander@2807: /* Make sure the command gets invoked. See bug 4019 on making this better. */ dvander@2807: pBase->Dispatch(); dvander@2807: } dvander@2807: #endif dvander@2807: dvander@2807: return res; dvander@2807: }