/* ======== SourceHook ======== * Copyright (C) 2004-2010 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng * * Author(s): Pavol "PM OnoTo" Marko * ============================ */ #include "sourcehook_impl.h" namespace SourceHook { namespace Impl { CPageAlloc CVfnPtr::ms_AlignedPageAllocator(8); CVfnPtr::CVfnPtr(void *ptr) : m_Ptr(ptr), m_OrigEntry(*reinterpret_cast(m_Ptr)), m_OrigCallThunk(NULL) { } CVfnPtr::~CVfnPtr() { if (!m_HookMans.empty()) m_HookMans.front()->DecrRef(this); } bool CVfnPtr::Init() { // Initalize the vfn ptr info object. // If we're running on GCC and the original vtable entry is odd // we have a problem. // The hook functions use non-virtual member function pointers to // call the original function (see sourcehook.hxx, SH_SETUP_MFP and SH_CALL_ORIG) // GCC has the same format for virtual and non-virtual MFPs: // virtual ones have an odd value as "funcptr" where the offset into the vtable is encoded // non-virtual ones have the direct function pointer as "funcptr", assuming that it is even // When m_OrigEntry is odd, GCC's runtime MFP calling code interpretes it as a virtual // function call though we want to call the function in a non-virtual way // (the original function call mechanism between the pre and post hook loop has to bypass // the virtual calling mechanism in order to call the original function). #if SH_COMP==SH_COMP_GCC if ((((ptrdiff_t)m_OrigEntry) & 1) != 0) { // Generate a new thunk m_OrigCallThunk = ms_AlignedPageAllocator.Alloc(12); ms_AlignedPageAllocator.SetRW(m_OrigCallThunk); unsigned char* thunkBase = reinterpret_cast(m_OrigCallThunk); ptrdiff_t offset = reinterpret_cast(m_OrigEntry) - thunkBase - 5; if (offset >= INT_MIN && offset <= INT_MAX) { *(thunkBase + 0) = 0xE9; // offset jump, immediate operand ptrdiff_t *offsetAddr = reinterpret_cast(thunkBase + 1); // destination = src + offset + 5 // <=> offset = destination - src - 5 *offsetAddr = offset; } else { // mov rax, m_origEntry *(thunkBase + 0) = 0x48; *(thunkBase + 1) = 0xB8; void **offsetAddr = reinterpret_cast(thunkBase + 2); *offsetAddr = m_OrigEntry; // jmp rax *(thunkBase + 10) = 0xFF; *(thunkBase + 11) = 0xE0; } ms_AlignedPageAllocator.SetRE(m_OrigCallThunk); } #endif return true; } class CVfnPtrOrigThunkCleanup : public ICleanupTask { CPageAlloc *m_Allocator; void *m_AddrToFree; public: CVfnPtrOrigThunkCleanup(CPageAlloc *allocator, void *addrToFree) : m_Allocator(allocator), m_AddrToFree(addrToFree) { } virtual void CleanupAndDeleteThis() { m_Allocator->Free(m_AddrToFree); delete this; } }; ICleanupTask* CVfnPtr::GetCleanupTask() { if (m_OrigCallThunk != NULL) { return new CVfnPtrOrigThunkCleanup(&ms_AlignedPageAllocator, m_OrigCallThunk); } else { return NULL; } } void* CVfnPtr::GetOrigCallAddr() const { if (m_OrigCallThunk) { return m_OrigCallThunk; } else { return m_OrigEntry; } } bool CVfnPtr::Revert() { // Only patch the vfnptr back if the module is still in memory // If it's not, do not remove stuff like we did before // First off we did it wrong (shutdown the whole hookman, uh..) and secondly applications may be // confused by RemoveHook returning false then (yeah, I know, I made this one up, no one checks for RemoveHook error) if (ModuleInMemory(reinterpret_cast(m_Ptr), SH_PTRSIZE)) { return Patch(m_OrigEntry); } else { return true; } } CIface *CVfnPtr::FindIface(void *iface) { List::iterator iter = m_IfaceList.find(iface); if (iter == m_IfaceList.end()) return NULL; else return &(*iter); } void CVfnPtr::AddHookMan(CHookManager *pHookMan) { List::iterator iter; // Don't accept invalid hook managers if (!*pHookMan) return; // Check whether this hook manager already exists; if yes, ignore. iter = m_HookMans.find(pHookMan); if (iter != m_HookMans.end()) return; // It doesn't -> add it. Add it to the end of its version group. for (iter = m_HookMans.begin(); iter != m_HookMans.end(); ++iter) { if ((*iter)->GetVersion() < pHookMan->GetVersion()) break; } bool isBeginning = iter == m_HookMans.begin(); m_HookMans.insert(iter, pHookMan); if (isBeginning) { pHookMan->IncrRef(this); if (m_HookMans.size() > 1) { // If another hookman was used until now but this one is better // (which it is because it's the first -> it has a higher version) // -> switch! List::iterator second = m_HookMans.begin(); ++second; (*second)->DecrRef(this); } // Make sure that this vfnptr points at it Patch(pHookMan->GetHookFunc()); } } bool CVfnPtr::HookManRemoved(CHookManager *pHookMan) { // Don't accept invalid hook managers if (!*pHookMan) return true; List::iterator iter = m_HookMans.find(pHookMan); if (iter == m_HookMans.end()) return true; // Didn't exist here anyway if (iter == m_HookMans.begin()) { // It is the first one! pHookMan->DecrRef(this); m_HookMans.erase(iter); if (m_HookMans.empty()) return false; // No more hookmans -> let SH delete us // Activate second -> now first hookman m_HookMans.front()->IncrRef(this); Patch(m_HookMans.front()->GetHookFunc()); } else { m_HookMans.erase(iter); } return true; } bool CVfnPtr::Patch(void *newValue) { if (!MakePageWritable(m_Ptr)) { return false; } *reinterpret_cast(m_Ptr) = newValue; return true; } CIface &CVfnPtr::GetIface(void *iface) { List::iterator iter = m_IfaceList.find(iface); if (iter == m_IfaceList.end()) { CIface newIface(iface); if (iface == NULL) { m_IfaceList.push_front(newIface); return m_IfaceList.front(); } else { m_IfaceList.push_back(newIface); return m_IfaceList.back(); } } else { return *iter; } } } }