/* ======== SourceHook ======== * Copyright (C) 2004-2005 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng * * Author(s): Pavol "PM OnoTo" Marko * Contributors: Scott "Damaged Soul" Ehlert * ============================ */ /** * @file sourcehook.cpp * @brief Contains the implementation of the SourceHook API */ #if defined __GNUC__ #include #endif #include "sourcehook_impl.h" #include "sh_memory.h" namespace SourceHook { template<> int SourceHook::HashFunction(const int & k) { return k; } template<> int SourceHook::Compare(const int & k1, const int & k2) { if (k1 == k2) return 0; if (k1 > k2) return 1; if (k1 < k2) return -1; return 0; } CSourceHookImpl::CSourceHookImpl() { } CSourceHookImpl::~CSourceHookImpl() { } int CSourceHookImpl::GetIfaceVersion() { return SH_IFACE_VERSION; } int CSourceHookImpl::GetImplVersion() { return SH_IMPL_VERSION; } bool CSourceHookImpl::IsPluginInUse(Plugin plug) { // Iterate through all hook managers which are in this plugin // Iterate through their vfnptrs, ifaces, hooks // If a hook from an other plugin is found, return true // Return false otherwise #define TMP_CHECK_LIST(name) \ for (hook_iter = iface_iter->name.m_List.begin(); hook_iter != iface_iter->name.m_List.end(); ++hook_iter) \ if (hook_iter->plug == plug) \ return true; for (HookManContList::iterator hmcl_iter = m_HookMans.begin(); hmcl_iter != m_HookMans.end(); ++hmcl_iter) { for (CHookManagerContainer::iterator hmil_iter = hmcl_iter->begin(); hmil_iter != hmcl_iter->end(); ++hmil_iter) { if (hmil_iter->m_Plug != plug) continue; for (CHookManagerInfo::VfnPtrListIter vfnptr_iter = hmil_iter->m_VfnPtrs.begin(); vfnptr_iter != hmil_iter->m_VfnPtrs.end(); ++vfnptr_iter) { for (CVfnPtr::IfaceListIter iface_iter = vfnptr_iter->m_Ifaces.begin(); iface_iter != vfnptr_iter->m_Ifaces.end(); ++iface_iter) { List::iterator hook_iter; TMP_CHECK_LIST(m_PreHooks); TMP_CHECK_LIST(m_PostHooks); } } } } #undef TMP_CHECK_LIST return false; } void CSourceHookImpl::UnloadPlugin(Plugin plug) { // 1) Manually remove all hooks by this plugin // 2) Manually remove all hook mans by this plugin List hookstoremove; List hookmanstoremove; HookManInfoList::iterator hmil_iter; #define TMP_CHECK_LIST(name, ispost) \ for (hook_iter = iface_iter->name.m_List.begin(); hook_iter != iface_iter->name.m_List.end(); ++hook_iter) \ if (hook_iter->plug == plug) \ hookstoremove.push_back(RemoveHookInfo(hook_iter->plug, iface_iter->m_Ptr, \ hook_iter->thisptr_offs, hmil_iter->m_Func, hook_iter->handler, ispost)); \ for (HookManContList::iterator hmcl_iter = m_HookMans.begin(); hmcl_iter != m_HookMans.end(); ++hmcl_iter) { for (CHookManagerContainer::iterator hmil_iter = hmcl_iter->begin(); hmil_iter != hmcl_iter->end(); ++hmil_iter) { if (hmil_iter->m_Plug == plug) hookmanstoremove.push_back(RemoveHookManInfo(plug, hmil_iter->m_Func)); for (CHookManagerInfo::VfnPtrListIter vfnptr_iter = hmil_iter->m_VfnPtrs.begin(); vfnptr_iter != hmil_iter->m_VfnPtrs.end(); ++vfnptr_iter) { for (CVfnPtr::IfaceListIter iface_iter = vfnptr_iter->m_Ifaces.begin(); iface_iter != vfnptr_iter->m_Ifaces.end(); ++iface_iter) { List::iterator hook_iter; TMP_CHECK_LIST(m_PreHooks, false); TMP_CHECK_LIST(m_PostHooks, true); } } } } #undef TMP_CHECK_LIST for (List::iterator rmiter = hookstoremove.begin(); rmiter != hookstoremove.end(); ++rmiter) RemoveHook(*rmiter); for (List::iterator rmiter = hookmanstoremove.begin(); rmiter != hookmanstoremove.end(); ++rmiter) RemoveHookManager(*rmiter); } void CSourceHookImpl::RemoveHookManager(Plugin plug, HookManagerPubFunc pubFunc) { // Moo! CHookManagerInfo tmp; if (pubFunc(HA_GetInfo, &tmp) != 0) return; HookManContList::iterator hmcl_iter = m_HookMans.find( CHookManagerContainer::HMCI(tmp.m_Proto, tmp.m_VtblOffs, tmp.m_VtblIdx)); if (hmcl_iter == m_HookMans.end()) { // Moo? return; } CHookManagerContainer::iterator hmil_iter = hmcl_iter->find( CHookManagerInfo::Descriptor(plug, pubFunc)); if (hmil_iter == hmcl_iter->end()) { // Moo? return; } bool stillInUse = false; List hookstoremove; // Iterate through all of the hook manager's hooks. Remove all hooks from the hookman's plugin. #define TMP_CHECK_LIST(name, ispost) \ for (hook_iter = iface_iter->name.m_List.begin(); hook_iter != iface_iter->name.m_List.end(); ++hook_iter) \ if (hook_iter->plug == plug) \ hookstoremove.push_back(RemoveHookInfo(hook_iter->plug, iface_iter->m_Ptr, \ hook_iter->thisptr_offs, hmil_iter->m_Func, hook_iter->handler, ispost)); \ else \ stillInUse = true; \ for (CHookManagerInfo::VfnPtrListIter vfnptr_iter = hmil_iter->m_VfnPtrs.begin(); vfnptr_iter != hmil_iter->m_VfnPtrs.end(); ++vfnptr_iter) { for (CVfnPtr::IfaceListIter iface_iter = vfnptr_iter->m_Ifaces.begin(); iface_iter != vfnptr_iter->m_Ifaces.end(); ++iface_iter) { List::iterator hook_iter; TMP_CHECK_LIST(m_PreHooks, false); TMP_CHECK_LIST(m_PostHooks, true); } } #undef TMP_CHECK_LIST for (List::iterator rmiter = hookstoremove.begin(); rmiter != hookstoremove.end(); ++rmiter) RemoveHook(*rmiter); CHookManagerInfo info = *hmil_iter; // Unlink the hook manager from the list hmcl_iter->erase(hmil_iter); // If there were any hooks from other plugins, find a new suitable hook manager. if (stillInUse) { // Find a suitable hook manager in an other plugin hmil_iter = hmcl_iter->begin(); // This should _never_ happen. // If there is a hook from an other plugin, the plugin must have provided a hook manager as well. SH_ASSERT(hmil_iter != hmcl_iter->end(), ("Could not find a suitable hook manager in an other plugin!")); // AddHook should make sure that every plugin only has _one_ hook manager for _one_ proto/vi/vo SH_ASSERT(hmil_iter->m_Plug != plug, ("New hook manager from same plugin!")); // The first hook manager should be always used - so the new hook manager has to be empty SH_ASSERT(hmil_iter->m_VfnPtrs.empty(), ("New hook manager not empty!")); // Move the vfnptrs from the old hook manager to the new one hmil_iter->m_VfnPtrs = info.m_VfnPtrs; // Unregister the old one, register the new one info.m_Func(HA_Unregister, NULL); hmil_iter->m_Func(HA_Register, &(*hmil_iter)); // Go through all vfnptrs in this hookman and patch them to point to the new manager's handler! // or whatever for (CHookManagerInfo::VfnPtrListIter vfnptr_iter = hmil_iter->m_VfnPtrs.begin(); vfnptr_iter != hmil_iter->m_VfnPtrs.end(); ++vfnptr_iter) { // And DEREFERENCE newHookMan->m_HookfuncVfnptr! // otherwise it will be executing the vtable... had to find out the hard way *reinterpret_cast(vfnptr_iter->m_Ptr) = *reinterpret_cast(hmil_iter->m_HookfuncVfnptr); } } } void CSourceHookImpl::RemoveHookManager(RemoveHookManInfo info) { RemoveHookManager(info.plug, info.hookman); } void CSourceHookImpl::CompleteShutdown() { // Remove all hooks List hookstoremove; #define TMP_CHECK_LIST(name, ispost) \ for (hook_iter = iface_iter->name.m_List.begin(); hook_iter != iface_iter->name.m_List.end(); ++hook_iter) \ hookstoremove.push_back(RemoveHookInfo(hook_iter->plug, iface_iter->m_Ptr, \ hook_iter->thisptr_offs, hmil_iter->m_Func, hook_iter->handler, ispost)) for (HookManContList::iterator hmcl_iter = m_HookMans.begin(); hmcl_iter != m_HookMans.end(); ++hmcl_iter) { for (CHookManagerContainer::iterator hmil_iter = hmcl_iter->begin(); hmil_iter != hmcl_iter->end(); ++hmil_iter) { for (CHookManagerInfo::VfnPtrListIter vfnptr_iter = hmil_iter->m_VfnPtrs.begin(); vfnptr_iter != hmil_iter->m_VfnPtrs.end(); ++vfnptr_iter) { for (CVfnPtr::IfaceListIter iface_iter = vfnptr_iter->m_Ifaces.begin(); iface_iter != vfnptr_iter->m_Ifaces.end(); ++iface_iter) { List::iterator hook_iter; TMP_CHECK_LIST(m_PreHooks, false); TMP_CHECK_LIST(m_PostHooks, true); } } } } #undef TMP_CHECK_LIST for (List::iterator rmiter = hookstoremove.begin(); rmiter != hookstoremove.end(); ++rmiter) RemoveHook(*rmiter); m_HookMans.clear(); } bool CSourceHookImpl::AddHook(Plugin plug, void *iface, int thisptr_offs, HookManagerPubFunc myHookMan, ISHDelegate *handler, bool post) { void *adjustediface = reinterpret_cast(reinterpret_cast(iface) + thisptr_offs); // 1) Get info about the hook manager CHookManagerInfo tmp; if (myHookMan(HA_GetInfo, &tmp) != 0) return false; tmp.m_Func = myHookMan; tmp.m_Plug = plug; CHookManagerContainer::HMCI hmci(tmp.m_Proto, tmp.m_VtblOffs, tmp.m_VtblIdx); void **cur_vtptr = *reinterpret_cast( reinterpret_cast(adjustediface) + tmp.m_VtblOffs); void *cur_vfnptr = reinterpret_cast(cur_vtptr + tmp.m_VtblIdx); // Add the container if not already added HookManContList::iterator hmcl_iter = m_HookMans.find(hmci); if (hmcl_iter == m_HookMans.end()) { m_HookMans.push_back(CHookManagerContainer(hmci)); hmcl_iter = m_HookMans.end(); --hmcl_iter; } hmcl_iter->AddHookManager(plug, tmp); CHookManagerContainer::iterator hookman = hmcl_iter->begin(); SH_ASSERT(hookman != hmcl_iter->end(), ("No hookman found - but we've just added one!")); // Check whether any other container holds a hook manager which holds this vfnptr // If yes, it means that the two are incompatible, so return false. for (HookManContList::iterator hmcl_iter2 = m_HookMans.begin(); hmcl_iter2 != m_HookMans.end(); ++hmcl_iter2) { if (hmcl_iter2 != hmcl_iter) { for (CHookManagerContainer::iterator hmil_iter = hmcl_iter2->begin(); hmil_iter != hmcl_iter2->end(); ++hmil_iter) { if (hmil_iter->m_VfnPtrs.find(cur_vfnptr) != hmil_iter->m_VfnPtrs.end()) return false; } } } // Tell it to store the pointer if it's not already active if (hookman->m_VfnPtrs.empty()) hookman->m_Func(HA_Register, &(*hookman)); CHookManagerInfo::VfnPtrListIter vfnptr_iter = hookman->m_VfnPtrs.find(cur_vfnptr); if (vfnptr_iter == hookman->m_VfnPtrs.end()) { // Add a new one CVfnPtr vfp(cur_vfnptr); // Alter vtable entry if (!SetMemAccess(cur_vtptr, sizeof(void*) * (tmp.m_VtblIdx + 1), SH_MEM_READ | SH_MEM_WRITE)) return false; *reinterpret_cast(cur_vfnptr) = *reinterpret_cast(hookman->m_HookfuncVfnptr); hookman->m_VfnPtrs.push_back(vfp); // Make vfnptr_iter point to the new element vfnptr_iter = hookman->m_VfnPtrs.end(); --vfnptr_iter; // Now that it is done, check whether we have to update any callclasses ApplyCallClassPatches(adjustediface, tmp.m_VtblOffs, tmp.m_VtblIdx, vfp.m_OrigEntry); } CVfnPtr::IfaceListIter iface_iter = vfnptr_iter->m_Ifaces.find(adjustediface); if (iface_iter == vfnptr_iter->m_Ifaces.end()) { // Add a new one vfnptr_iter->m_Ifaces.push_back(CIface(adjustediface)); // Make iface_iter point to the new element iface_iter = vfnptr_iter->m_Ifaces.end(); --iface_iter; } // Add the hook HookInfo hookinfo; hookinfo.handler = handler; hookinfo.plug = plug; hookinfo.paused = false; hookinfo.thisptr_offs = thisptr_offs; if (post) iface_iter->m_PostHooks.m_List.push_back(hookinfo); else iface_iter->m_PreHooks.m_List.push_back(hookinfo); return true; } bool CSourceHookImpl::RemoveHook(RemoveHookInfo info) { return RemoveHook(info.plug, info.iface, info.thisptr_offs, info.hookman, info.handler, info.post); } bool CSourceHookImpl::RemoveHook(Plugin plug, void *iface, int thisptr_offs, HookManagerPubFunc myHookMan, ISHDelegate *handler, bool post) { void *adjustediface = reinterpret_cast(reinterpret_cast(iface)+thisptr_offs); CHookManagerInfo tmp; if (myHookMan(HA_GetInfo, &tmp) != 0) return false; // Find the hook manager and the hook HookManContList::iterator hmcl_iter = m_HookMans.find( CHookManagerContainer::HMCI(tmp.m_Proto, tmp.m_VtblOffs, tmp.m_VtblIdx)); if (hmcl_iter == m_HookMans.end() || hmcl_iter->empty()) return false; CHookManagerContainer::iterator hookman = hmcl_iter->begin(); if (!ModuleInMemory(reinterpret_cast(adjustediface) + tmp.m_VtblOffs, sizeof(void*) * (tmp.m_VtblIdx + 1))) { // The module the vtable was in is already unloaded. hookman->m_VfnPtrs.clear(); hookman->m_Func(HA_Unregister, NULL); return true; } void **cur_vtptr = *reinterpret_cast( reinterpret_cast(adjustediface) + tmp.m_VtblOffs); void *cur_vfnptr = reinterpret_cast(cur_vtptr + tmp.m_VtblIdx); CHookManagerInfo::VfnPtrListIter vfnptr_iter = hookman->m_VfnPtrs.find(cur_vfnptr); if (vfnptr_iter == hookman->m_VfnPtrs.end()) return false; for (CVfnPtr::IfaceListIter iface_iter = vfnptr_iter->m_Ifaces.begin(); iface_iter != vfnptr_iter->m_Ifaces.end();) { List &hooks = post ? iface_iter->m_PostHooks.m_List : iface_iter->m_PreHooks.m_List; bool erase; for (List::iterator hookiter = hooks.begin(); hookiter != hooks.end(); ) { erase = hookiter->plug == plug && hookiter->handler->IsEqual(handler) && hookiter->thisptr_offs == thisptr_offs; if (erase) { hookiter->handler->DeleteThis(); // Make the _plugin_ delete the handler object // Move all iterators pointing at this List::iterator oldhookiter = hookiter; hookiter = hooks.erase(hookiter); List::iterator newhookiter = hookiter; --newhookiter; // The hook loop will ++ it then CHookList::CIter *pItIter; for (pItIter = iface_iter->m_PreHooks.m_UsedIters; pItIter; pItIter = pItIter->m_pNext) if (pItIter->m_Iter == oldhookiter) pItIter->m_Iter = newhookiter; } else ++hookiter; } if (iface_iter->m_PostHooks.m_List.empty() && iface_iter->m_PreHooks.m_List.empty()) { // There are no hooks on this iface anymore... for (HookLoopInfoStack::iterator hli_iter = m_HLIStack.begin(); hli_iter != m_HLIStack.end(); ++hli_iter) { if (hli_iter->pCurIface == static_cast(&(*iface_iter))) hli_iter->shouldContinue = false; } iface_iter = vfnptr_iter->m_Ifaces.erase(iface_iter); if (vfnptr_iter->m_Ifaces.empty()) { // No ifaces at all -> Deactivate the hook *reinterpret_cast(vfnptr_iter->m_Ptr) = vfnptr_iter->m_OrigEntry; hookman->m_VfnPtrs.erase(vfnptr_iter); // Remove callclass patch for (Impl_CallClassList::iterator cciter = m_CallClasses.begin(); cciter != m_CallClasses.end(); ++cciter) if (cciter->m_Ptr == adjustediface) cciter->RemoveCallClassPatch(tmp.m_VtblOffs, tmp.m_VtblIdx); if (hookman->m_VfnPtrs.empty()) { // Unregister the hook manager hookman->m_Func(HA_Unregister, NULL); } // Don't try to continue looping through ifaces // - the list is already invalid return true; } } else ++iface_iter; } return true; } GenericCallClass *CSourceHookImpl::GetCallClass(void *iface, size_t size) { for (Impl_CallClassList::iterator cciter = m_CallClasses.begin(); cciter != m_CallClasses.end(); ++cciter) { if (cciter->m_Ptr == iface && cciter->m_ObjSize == size) { ++cciter->m_RefCounter; return &(*cciter); } } // Make a new one CCallClassImpl tmp(iface, size); ApplyCallClassPatches(tmp); m_CallClasses.push_back(tmp); return &m_CallClasses.back(); } void CSourceHookImpl::ReleaseCallClass(GenericCallClass *ptr) { Impl_CallClassList::iterator iter = m_CallClasses.find(ptr); if (iter == m_CallClasses.end()) return; --iter->m_RefCounter; if (iter->m_RefCounter < 1) m_CallClasses.erase(iter); } void CSourceHookImpl::ApplyCallClassPatches(CCallClassImpl &cc) { for (HookManContList::iterator hmcl_iter = m_HookMans.begin(); hmcl_iter != m_HookMans.end(); ++hmcl_iter) { for (CHookManagerContainer::iterator hookman = hmcl_iter->begin(); hookman != hmcl_iter->end(); ++hookman) { for (CHookManagerInfo::VfnPtrListIter vfnptr_iter = hookman->m_VfnPtrs.begin(); vfnptr_iter != hookman->m_VfnPtrs.end(); ++vfnptr_iter) { for (CVfnPtr::IfaceListIter iface_iter = vfnptr_iter->m_Ifaces.begin(); iface_iter != vfnptr_iter->m_Ifaces.end(); ++iface_iter) { if (iface_iter->m_Ptr >= cc.m_Ptr && iface_iter->m_Ptr < (reinterpret_cast(cc.m_Ptr) + cc.m_ObjSize)) { cc.ApplyCallClassPatch(static_cast(reinterpret_cast(iface_iter->m_Ptr) - reinterpret_cast(cc.m_Ptr)) + hookman->m_VtblOffs, hookman->m_VtblIdx, vfnptr_iter->m_OrigEntry); } } } } } } void CSourceHookImpl::ApplyCallClassPatches(void *ifaceptr, int vtbl_offs, int vtbl_idx, void *orig_entry) { for (Impl_CallClassList::iterator cc_iter = m_CallClasses.begin(); cc_iter != m_CallClasses.end(); ++cc_iter) { if (ifaceptr >= cc_iter->m_Ptr && ifaceptr < (reinterpret_cast(cc_iter->m_Ptr) + cc_iter->m_ObjSize)) { cc_iter->ApplyCallClassPatch(static_cast(reinterpret_cast(ifaceptr) - reinterpret_cast(cc_iter->m_Ptr)) + vtbl_offs, vtbl_idx, orig_entry); } } } void CSourceHookImpl::RemoveCallClassPatches(void *ifaceptr, int vtbl_offs, int vtbl_idx) { for (Impl_CallClassList::iterator cc_iter = m_CallClasses.begin(); cc_iter != m_CallClasses.end(); ++cc_iter) { if (ifaceptr >= cc_iter->m_Ptr && ifaceptr < (reinterpret_cast(cc_iter->m_Ptr) + cc_iter->m_ObjSize)) { cc_iter->RemoveCallClassPatch(vtbl_offs, vtbl_idx); } } } void CSourceHookImpl::SetPluginPaused(Plugin plug, bool paused) { // Go through all hook managers, all interfaces, and set the status of all hooks of this plugin to paused for (HookManContList::iterator hmcl_iter = m_HookMans.begin(); hmcl_iter != m_HookMans.end(); ++hmcl_iter) { for (CHookManagerContainer::iterator hookmaniter = hmcl_iter->begin(); hookmaniter != hmcl_iter->end(); ++hookmaniter) { for (CHookManagerInfo::VfnPtrListIter vfnptr_iter = hookmaniter->m_VfnPtrs.begin(); vfnptr_iter != hookmaniter->m_VfnPtrs.end(); ++vfnptr_iter) { for (CVfnPtr::IfaceListIter ifaceiter = vfnptr_iter->m_Ifaces.begin(); ifaceiter != vfnptr_iter->m_Ifaces.end(); ++ifaceiter) { for (List::iterator hookiter = ifaceiter->m_PreHooks.m_List.begin(); hookiter != ifaceiter->m_PreHooks.m_List.end(); ++hookiter) if (plug == hookiter->plug) hookiter->paused = paused; for (List::iterator hookiter = ifaceiter->m_PostHooks.m_List.begin(); hookiter != ifaceiter->m_PostHooks.m_List.end(); ++hookiter) if (plug == hookiter->plug) hookiter->paused = paused; } } } } } void CSourceHookImpl::PausePlugin(Plugin plug) { SetPluginPaused(plug, true); } void CSourceHookImpl::UnpausePlugin(Plugin plug) { SetPluginPaused(plug, false); } void CSourceHookImpl::HookLoopBegin(IIface *pIface) { HookLoopInfo hli; hli.pCurIface = pIface; hli.shouldContinue = true; hli.recall = false; m_HLIStack.push(hli); } void CSourceHookImpl::HookLoopEnd() { m_HLIStack.pop(); } void CSourceHookImpl::SetRes(META_RES res) { *m_HLIStack.front().pCurRes = res; } META_RES CSourceHookImpl::GetPrevRes() { return *m_HLIStack.front().pPrevRes; } META_RES CSourceHookImpl::GetStatus() { return *m_HLIStack.front().pStatus; } const void *CSourceHookImpl::GetOrigRet() { return m_HLIStack.front().pOrigRet; } const void *CSourceHookImpl::GetOverrideRet() { // pOverrideRet is always set since recalls were introduced // GetOverrideRetPtr was added; a function which always returns pOverrideRet // so that RETURN_META_VALUE_NEWPARAMS can provide an override result // This means that we have to filter GetOverrideRet: // If the status variable is < MRES_OVERRIDE, return NULL. return (*m_HLIStack.front().pStatus < MRES_OVERRIDE) ? NULL : m_HLIStack.front().pOverrideRet; } void *CSourceHookImpl::GetOverrideRetPtr() { // As described in the comment above: always return pOverrideRet return m_HLIStack.front().pOverrideRet; } void *CSourceHookImpl::GetIfacePtr() { return *m_HLIStack.front().pIfacePtrPtr; } void CSourceHookImpl::SetCurResPtr(META_RES *mres) { m_HLIStack.front().pCurRes = mres; } void CSourceHookImpl::SetPrevResPtr(META_RES *mres) { m_HLIStack.front().pPrevRes = mres; // If we're recalling, drag the previous mres value to the new hookfunc if (m_HLIStack.size() > 1 && m_HLIStack.second().recall) *mres = *m_HLIStack.second().pPrevRes; } void CSourceHookImpl::SetStatusPtr(META_RES *mres) { m_HLIStack.front().pStatus = mres; // If we're recalling, drag the previous mres value to the new hookfunc if (m_HLIStack.size() > 1 && m_HLIStack.second().recall) *mres = *m_HLIStack.second().pStatus; } void CSourceHookImpl::SetIfacePtrPtr(void **pp) { m_HLIStack.front().pIfacePtrPtr = pp; } void CSourceHookImpl::SetOrigRetPtr(const void *ptr) { m_HLIStack.front().pOrigRet = ptr; } void CSourceHookImpl::SetOverrideRetPtr(void *ptr) { m_HLIStack.front().pOverrideRet = ptr; } // New function which does all of the above + more :) void *CSourceHookImpl::SetupHookLoop(META_RES *statusPtr, META_RES *prevResPtr, META_RES *curResPtr, void **ifacePtrPtr, const void *origRetPtr, void *overrideRetPtr) { HookLoopInfo &hli = m_HLIStack.front(); hli.pStatus = statusPtr; hli.pPrevRes = prevResPtr; hli.pCurRes = curResPtr; hli.pIfacePtrPtr = ifacePtrPtr; hli.pOrigRet = origRetPtr; // Handle some recall stuff if (m_HLIStack.size() > 1 && m_HLIStack.second().recall) { HookLoopInfo &other = m_HLIStack.second(); *statusPtr = *other.pStatus; *prevResPtr = *other.pStatus; hli.pOverrideRet = other.pOverrideRet; } else hli.pOverrideRet = overrideRetPtr; // Tell the hook func which override ret ptr to use return hli.pOverrideRet; } bool CSourceHookImpl::ShouldContinue() { // If recall is true, we shall not continue either. // This is because, if it's true and ShouldContinue is called, it suggests that the // actual recall is done and that we are back in the original handler which shall return // immediately. return m_HLIStack.front().shouldContinue && !m_HLIStack.front().recall; } void CSourceHookImpl::DoRecall() { if (!m_HLIStack.empty()) { m_HLIStack.front().recall = true; CHookList *mlist = static_cast(m_HLIStack.front().pCurIface->GetPreHooks()); mlist->m_Recall = true; // The hookfunc usually do this, but it won't have a chance to see it, // so for recalls, we update status here if it's required if (*m_HLIStack.front().pCurRes > *m_HLIStack.front().pStatus) *m_HLIStack.front().pStatus = *m_HLIStack.front().pCurRes; } } //////////////////////////// // CCallClassImpl //////////////////////////// CSourceHookImpl::CCallClassImpl::CCallClassImpl(void *ptr, size_t size) : m_Ptr(ptr), m_ObjSize(size), m_RefCounter(1) { } CSourceHookImpl::CCallClassImpl::~CCallClassImpl() { } void *CSourceHookImpl::CCallClassImpl::GetThisPtr() { return m_Ptr; } void *CSourceHookImpl::CCallClassImpl::GetOrigFunc(int vtbloffs, int vtblidx) { OrigVTables::iterator iter = m_VT.find(vtbloffs); if (iter != m_VT.end() && iter->val.size() > (size_t)vtblidx) { return iter->val[vtblidx]; } return NULL; } void CSourceHookImpl::CCallClassImpl::ApplyCallClassPatch(int vtbl_offs, int vtbl_idx, void *orig_entry) { OrigFuncs &tmpvec = m_VT[vtbl_offs]; if (tmpvec.size() <= (size_t)vtbl_idx) tmpvec.resize(vtbl_idx+1); tmpvec[vtbl_idx] = orig_entry; } void CSourceHookImpl::CCallClassImpl::RemoveCallClassPatch(int vtbl_offs, int vtbl_idx) { OrigVTables::iterator iter = m_VT.find(vtbl_offs); if (iter != m_VT.end()) { if (iter->val.size() > (size_t)vtbl_idx) { iter->val[vtbl_idx] = 0; OrigFuncs &of = iter->val; OrigFuncs::iterator lastused = of.end(); for (OrigFuncs::iterator viter = of.begin(); viter != of.end(); ++viter) { if (*viter) lastused = viter; } if (lastused == of.end()) { // No used element => Remove the whole vector m_VT.erase(iter); return; } of.resize(lastused - of.begin() + 1); } } } //////////////////////////// // CHookManagerInfo //////////////////////////// CSourceHookImpl::CHookManagerInfo::CHookManagerInfo() : m_HookManVersion(0) { } CSourceHookImpl::CHookManagerInfo::~CHookManagerInfo() { } IVfnPtr *CSourceHookImpl::CHookManagerInfo::FindVfnPtr(void *vfnptr) { VfnPtrListIter iter = m_VfnPtrs.find(vfnptr); return iter == m_VfnPtrs.end() ? NULL : &(*iter); } void CSourceHookImpl::CHookManagerInfo::SetInfo(int vtbl_offs, int vtbl_idx, const char *proto) { m_VtblOffs = vtbl_offs; m_VtblIdx = vtbl_idx; m_Proto = proto; } void CSourceHookImpl::CHookManagerInfo::SetHookfuncVfnptr(void *hookfunc_vfnptr) { m_HookfuncVfnptr = hookfunc_vfnptr; } void CSourceHookImpl::CHookManagerInfo::SetVersion(int version) { m_HookManVersion = version; } //////////////////////////// // CVfnPtr //////////////////////////// // If you get a crash here, the ptr passed is invalid // This usually means a SH_DECL_MANUALHOOK* with wrong thisptroffs/vtbloffs/vtblidx CSourceHookImpl::CVfnPtr::CVfnPtr(void *ptr) : m_Ptr(ptr), m_OrigEntry(*reinterpret_cast(ptr)) { } CSourceHookImpl::CVfnPtr::~CVfnPtr() { } void *CSourceHookImpl::CVfnPtr::GetVfnPtr() { return m_Ptr; } void *CSourceHookImpl::CVfnPtr::GetOrigEntry() { return m_OrigEntry; } IIface *CSourceHookImpl::CVfnPtr::FindIface(void *ptr) { IfaceListIter iter = m_Ifaces.find(ptr); return iter == m_Ifaces.end() ? NULL : &(*iter); } //////////////////////////// // CIface //////////////////////////// CSourceHookImpl::CIface::CIface(void *ptr) : m_Ptr(ptr) { } CSourceHookImpl::CIface::~CIface() { } void *CSourceHookImpl::CIface::GetPtr() { return m_Ptr; } IHookList *CSourceHookImpl::CIface::GetPreHooks() { return &m_PreHooks; } IHookList *CSourceHookImpl::CIface::GetPostHooks() { return &m_PostHooks; } //////////////////////////// // CHookList //////////////////////////// CSourceHookImpl::CHookList::CHookList() : m_FreeIters(NULL), m_UsedIters(NULL), m_Recall(false) { } CSourceHookImpl::CHookList::CHookList(const CHookList &other) : m_List(other.m_List), m_FreeIters(NULL), m_UsedIters(NULL), m_Recall(false) { } CSourceHookImpl::CHookList::~CHookList() { while (m_FreeIters) { CIter *iter = m_FreeIters->m_pNext; delete m_FreeIters; m_FreeIters = iter; } while (m_UsedIters) { CIter *iter = m_UsedIters->m_pNext; delete m_UsedIters; m_UsedIters = iter; } } IHookList::IIter *CSourceHookImpl::CHookList::GetIter() { CIter *ret; if (m_FreeIters) { ret = m_FreeIters; m_FreeIters = ret->m_pNext; ret->GoToBegin(); } else { ret = new CIter(this); } // Muuuh, if we're recalling, it shall be a copy of the last iterator, incremented by one if (m_Recall && m_UsedIters) { ret->Set(m_UsedIters); // m_UsedIters is the last returned and not released iterator ret->Next(); // Use next instead of directly incrementing its m_Iter: // skips paused plugins } ret->m_pNext = m_UsedIters; ret->m_pPrev = NULL; if (m_UsedIters) m_UsedIters->m_pPrev = ret; m_UsedIters = ret; m_Recall = false; return ret; } void CSourceHookImpl::CHookList::ReleaseIter(IIter *pIter) { CIter *pIter2 = static_cast(pIter); // Unlink from m_UsedIters if (pIter2->m_pNext) pIter2->m_pNext->m_pPrev = pIter2->m_pPrev; if (pIter2->m_pPrev) pIter2->m_pPrev->m_pNext = pIter2->m_pNext; if (pIter2 == m_UsedIters) m_UsedIters = NULL; // Link to m_FreeIters pIter2->m_pNext = m_FreeIters; m_FreeIters = pIter2; // Reset recall state. m_Recall = false; } CSourceHookImpl::CHookList::CIter::CIter(CHookList *pList) : m_pList(pList), m_pNext(NULL) { GoToBegin(); } CSourceHookImpl::CHookList::CIter::~CIter() { } void CSourceHookImpl::CHookList::CIter::Set(CIter *pOther) { m_Iter = pOther->m_Iter; } void CSourceHookImpl::CHookList::CIter::GoToBegin() { m_Iter = m_pList->m_List.begin(); SkipPaused(); } bool CSourceHookImpl::CHookList::CIter::End() { if (!m_pList) return false; return m_Iter == m_pList->m_List.end(); } void CSourceHookImpl::CHookList::CIter::Next() { if (!m_pList) return; ++m_Iter; SkipPaused(); } void CSourceHookImpl::CHookList::CIter::Clear() { m_pList = NULL; } void CSourceHookImpl::CHookList::CIter::SkipPaused() { while (m_Iter != m_pList->m_List.end() && m_Iter->paused) ++m_Iter; } ISHDelegate *CSourceHookImpl::CHookList::CIter::Handler() { return m_Iter->handler; } int CSourceHookImpl::CHookList::CIter::ThisPtrOffs() { return m_Iter->thisptr_offs; } ////////////////////////////////////////////////////////////////////////// // CProto ////////////////////////////////////////////////////////////////////////// bool CSourceHookImpl::CProto::Equal(const char *p1, const char *p2) { if (*p1 && *p2) // Case1: Both old { // As in old versions return strcmp(p1, p2) == 0; } else if (!*p1 && !*p2) // Case2: Both new { const ProtoInfo *pi1 = reinterpret_cast(p1); const ProtoInfo *pi2 = reinterpret_cast(p2); if (pi1->retTypeSize == pi2->retTypeSize && pi1->numOfParams == pi2->numOfParams) { // params[0] is 0 for "normal" functions and -1 for vararg functions // params[1] is size of first parameter // params[2] is size of second parameter ... for (int i = 0; i <= pi1->numOfParams; ++i) { if (pi1->params[i] != pi2->params[i]) return false; } return true; } else { return false; } } else // Case3: Mixed old/new { // Trust the user return true; } } ////////////////////////////////////////////////////////////////////////// // CHookManagerContainer ////////////////////////////////////////////////////////////////////////// void CSourceHookImpl::CHookManagerContainer::AddHookManager(Plugin plug, const CHookManagerInfo &hookman) { iterator iter; // Check whether such a hook manager already exists; if yes, ignore. for (iter = begin(); iter != end(); ++iter) { if (iter->m_Plug == plug && iter->m_Func == hookman.m_Func) return; } // It doesn't -> add it. Add it to the end of its version group. for (iter = begin(); iter != end(); ++iter) { if (iter->m_HookManVersion < hookman.m_HookManVersion) break; } bool isBeginning = iter == begin(); insert(iter, hookman); // If we inserted it at the beginning and if the dam breaks open many years too soon // and if there is more than one hookman and if the second one isn't empty, transfer // hooks from second to first if (isBeginning && size() > 1) { iter = begin(); iterator second = iter; ++second; if (!second->m_VfnPtrs.empty()) { // Move the vfnptrs from the old hook manager to the new one iter->m_VfnPtrs = second->m_VfnPtrs; second->m_VfnPtrs.clear(); // Unregister the old one, register the new one second->m_Func(HA_Unregister, NULL); iter->m_Func(HA_Register, &(*iter)); // Go through all vfnptrs in this hookman and patch them to point to the new manager's handler! // or whatever for (CHookManagerInfo::VfnPtrListIter vfnptr_iter = iter->m_VfnPtrs.begin(); vfnptr_iter != iter->m_VfnPtrs.end(); ++vfnptr_iter) { // And DEREFERENCE newHookMan->m_HookfuncVfnptr! // otherwise it will be executing the vtable... had to find out the hard way *reinterpret_cast(vfnptr_iter->m_Ptr) = *reinterpret_cast(iter->m_HookfuncVfnptr); } } } } }