diff --git a/sourcehook/sh_listcat.h b/sourcehook/sh_listcat.h new file mode 100644 index 0000000..8eb74ca --- /dev/null +++ b/sourcehook/sh_listcat.h @@ -0,0 +1,147 @@ +/* ======== SourceMM ======== +* Copyright (C) 2004-2007 Metamod:Source Development Team +* No warranties of any kind +* +* License: zlib/libpng +* +* Author(s): Pavol "PM OnoTo" Marko +* ============================ +*/ + +// Used for VP-Hooks + +#ifndef _INCLUDE_SMM_LISTCAT_H +#define _INCLUDE_SMM_LISTCAT_H + +#include "sh_list.h" + +namespace SourceHook +{ + + // Only a very special-case forward iterator! + template + class ListCatIterator + { + List *m_pListLeft; + List *m_pListRight; + typename List::iterator m_Iter; + + void CheckLeftEmptyOnBegin() + { + // If the left list is empty and right is valid, GoToBegin sets m_Iter to m_Left + // End() checks for equality to m_Right.end() so it returns false + // then Next() corrupts by incrementing! + + // To avoid this, we skip left if it's empty. + if (m_pListLeft && m_pListLeft->empty() && m_pListRight) + { + m_Iter = m_pListRight->begin(); + } + } + public: + // At least one list has to be non-null! + ListCatIterator(List *pListLeft, List *pListRight) : m_pListLeft(pListLeft), m_pListRight(pListRight), + m_Iter(pListLeft ? pListLeft->begin() : pListRight->begin()) + { + CheckLeftEmptyOnBegin(); + } + + void GoToBegin() + { + m_Iter = m_pListLeft ? m_pListLeft->begin() : m_pListRight->begin(); + CheckLeftEmptyOnBegin(); + } + + bool End() + { + return m_pListRight ? (m_Iter == m_pListRight->end()) + : (m_Iter == m_pListLeft->end()); + } + + //pre increment + ListCatIterator & operator++() + { + ++m_Iter; + if (m_pListLeft && m_Iter == m_pListLeft->end()) + { + if (m_pListRight) + m_Iter = m_pListRight->begin(); + } + return *this; + } + //post increment + ListCatIterator operator++(int) + { + ListCatIterator old(*this); + + ++m_Iter; + if (m_pListLeft && m_Iter == m_pListLeft->end()) + { + if (m_pListRight) + m_Iter = m_pListRight->begin(); + } + + return old; + } + + const T & operator * () const + { + return *m_Iter; + } + T & operator * () + { + return *m_Iter; + } + + T * operator -> () + { + return &(*m_Iter); + } + const T * operator -> () const + { + return &(*m_Iter); + } + + bool operator != (const typename List::iterator &where) const + { + return (m_Iter != where); + } + bool operator ==(const typename List::iterator &where) const + { + return (m_Iter == where); + } + + ListCatIterator & operator = (const typename List::iterator &where) + { + m_Iter = where; + + if (m_pListLeft && m_Iter == m_pListLeft->end()) + { + if (m_pListRight) + m_Iter = m_pListRight->begin(); + + // :HACK HACK: RemoveHookById is not aware of ListCatIterator (yet? :TODO: Change it!) + // So we have to do this here... (look for the "Move all iterators pointing at this" section) + --m_Iter; + } + + return *this; + } + + ListCatIterator & operator = (const ListCatIterator &other) + { + m_Iter = other.m_Iter; + m_pListLeft = other.m_pListLeft; + m_pListRight = other.m_pListRight; + return *this; + } + + void SetListLeft(List *pList) + { + m_pListLeft = pList; + } + }; +} + +#endif + diff --git a/sourcehook/test/testvphooks.cpp b/sourcehook/test/testvphooks.cpp new file mode 100644 index 0000000..8d03bfd --- /dev/null +++ b/sourcehook/test/testvphooks.cpp @@ -0,0 +1,347 @@ +#include +#include "sourcehook.h" +#include "sourcehook_test.h" +#include "testevents.h" + +// TEST VP HOOKS +// Test vfnptr-wide hooks +// Also contains a test for removing hooks on deleted instances (by id) + +namespace +{ + StateList g_States; + SourceHook::ISourceHook *g_SHPtr; + SourceHook::Plugin g_PLID; + + class IBase; + + MAKE_STATE_1(State_D1_Func1, IBase *); + MAKE_STATE_1(State_D2_Func1, IBase *); + MAKE_STATE_1(State_Func1_Pre, IBase *); + MAKE_STATE_1(State_Func1_Post, IBase *); + + MAKE_STATE_1(State_D1_Func2, IBase *); + MAKE_STATE_1(State_D2_Func2, IBase *); + MAKE_STATE_1(State_Func2_Pre, IBase *); + MAKE_STATE_1(State_Func2_Post, IBase *); + + MAKE_STATE_2(State_D1_Func3, IBase *, int); + MAKE_STATE_2(State_D2_Func3, IBase *, int); + MAKE_STATE_2(State_Func3_Pre, IBase *, int); + MAKE_STATE_2(State_Func3_Post, IBase *, int); + + class IBase + { + public: + virtual void Func1() = 0; + virtual void Func2() = 0; + virtual void Func3(int x) = 0; + }; + + class CDerived1 : public IBase + { + public: + virtual void Func1() + { + ADD_STATE(State_D1_Func1(this)); + } + virtual void Func2() + { + ADD_STATE(State_D1_Func2(this)); + } + virtual void Func3(int x) + { + ADD_STATE(State_D1_Func3(this, x)); + } + }; + + class CDerived2 : public IBase + { + public: + virtual void Func1() + { + ADD_STATE(State_D2_Func1(this)); + } + virtual void Func2() + { + ADD_STATE(State_D2_Func2(this)); + } + virtual void Func3(int x) + { + ADD_STATE(State_D2_Func3(this, x)); + } + }; + + void Handler_Func1_Pre() + { + ADD_STATE(State_Func1_Pre(META_IFACEPTR(IBase))); + } + void Handler_Func1_Post() + { + ADD_STATE(State_Func1_Post(META_IFACEPTR(IBase))); + } + int g_F2_Pre_HookToRemove = 0; + void Handler_Func2_Pre() + { + ADD_STATE(State_Func2_Pre(META_IFACEPTR(IBase))); + SH_REMOVE_HOOK_ID(g_F2_Pre_HookToRemove); + } + void Handler_Func2_Post() + { + ADD_STATE(State_Func2_Post(META_IFACEPTR(IBase))); + } + + + void Handler_Func3_Pre(int x) + { + ADD_STATE(State_Func3_Pre(META_IFACEPTR(IBase), x)); + + RETURN_META_NEWPARAMS(MRES_IGNORED, &IBase::Func3, (x+1)); + } + void Handler_Func3_Post(int x) + { + ADD_STATE(State_Func3_Post(META_IFACEPTR(IBase), x)); + } + + SH_DECL_HOOK0_void(IBase, Func1, SH_NOATTRIB, 0); + SH_DECL_HOOK0_void(IBase, Func2, SH_NOATTRIB, 0); + SH_DECL_HOOK1_void(IBase, Func3, SH_NOATTRIB, 0, int); + + SH_DECL_MANUALHOOK1_void(IBase_Func3_Manual, 2, 0, 0, int); +} + +bool TestVPHooks(std::string &error) +{ + GET_SHPTR(g_SHPtr); + g_PLID = 1337; + + CDerived1 d1i1; + CDerived1 d1i2; + CDerived2 d2i1; + + IBase *p_d1i1 = &d1i1; + IBase *p_d1i2 = &d1i2; + IBase *p_d2i1 = &d2i1; + + int hook1 = SH_ADD_VPHOOK(IBase, Func1, p_d1i1, SH_STATIC(Handler_Func1_Pre), false); + + p_d1i1->Func1(); + p_d1i2->Func1(); + p_d2i1->Func1(); + + CHECK_STATES((&g_States, + new State_Func1_Pre(p_d1i1), + new State_D1_Func1(p_d1i1), + + new State_Func1_Pre(p_d1i2), + new State_D1_Func1(p_d1i2), + + new State_D2_Func1(p_d2i1), + NULL), "Part 1"); + + SH_CALL(p_d1i1, &IBase::Func1)(); + SH_CALL(p_d1i2, &IBase::Func1)(); + SH_CALL(p_d2i1, &IBase::Func1)(); + + CHECK_STATES((&g_States, + new State_D1_Func1(p_d1i1), + + new State_D1_Func1(p_d1i2), + + new State_D2_Func1(p_d2i1), + NULL), "Part 1.1"); + + SH_REMOVE_HOOK_ID(hook1); + + p_d1i1->Func1(); + p_d1i2->Func1(); + p_d2i1->Func1(); + + CHECK_STATES((&g_States, + new State_D1_Func1(p_d1i1), + + new State_D1_Func1(p_d1i2), + + new State_D2_Func1(p_d2i1), + NULL), "Part 2"); + + + + // Normal hook, then vp hook + + int hook2 = SH_ADD_HOOK(IBase, Func1, p_d1i1, SH_STATIC(Handler_Func1_Pre), false); + hook1 = SH_ADD_VPHOOK(IBase, Func1, p_d1i1, SH_STATIC(Handler_Func1_Pre), false); + + p_d1i1->Func1(); + p_d1i2->Func1(); + p_d2i1->Func1(); + + CHECK_STATES((&g_States, + new State_Func1_Pre(p_d1i1), + new State_Func1_Pre(p_d1i1), + new State_D1_Func1(p_d1i1), + + new State_Func1_Pre(p_d1i2), + new State_D1_Func1(p_d1i2), + + new State_D2_Func1(p_d2i1), + NULL), "Part 3"); + + SH_REMOVE_HOOK_ID(hook1); + + p_d1i1->Func1(); + p_d1i2->Func1(); + p_d2i1->Func1(); + + CHECK_STATES((&g_States, + new State_Func1_Pre(p_d1i1), + new State_D1_Func1(p_d1i1), + + new State_D1_Func1(p_d1i2), + + new State_D2_Func1(p_d2i1), + NULL), "Part 4"); + + SH_REMOVE_HOOK_ID(hook2); + + p_d1i1->Func1(); + p_d1i2->Func1(); + p_d2i1->Func1(); + + CHECK_STATES((&g_States, + new State_D1_Func1(p_d1i1), + + new State_D1_Func1(p_d1i2), + + new State_D2_Func1(p_d2i1), + NULL), "Part 5"); + + // Test this: + // Normal hook AND vp hook on Func2 + // Func2's pre handler removes the VP hook. It has to work anyway :) + + hook1 = SH_ADD_VPHOOK(IBase, Func2, p_d1i1, SH_STATIC(Handler_Func2_Pre), false); + hook2 = SH_ADD_HOOK(IBase, Func2, p_d1i1, SH_STATIC(Handler_Func2_Pre), false); + + g_F2_Pre_HookToRemove = hook1; + p_d1i1->Func2(); + p_d1i1->Func2(); + + CHECK_STATES((&g_States, + new State_Func2_Pre(p_d1i1), + new State_Func2_Pre(p_d1i1), + new State_D1_Func2(p_d1i1), + + new State_Func2_Pre(p_d1i1), + new State_D1_Func2(p_d1i1), + + NULL), "Part 6"); + + SH_REMOVE_HOOK_ID(hook1); + + // Hook function 3: + // Using manualhook, VP + hook1 = SH_ADD_MANUALVPHOOK(IBase_Func3_Manual, p_d1i1, SH_STATIC(Handler_Func3_Pre), false); + + // Normally, VP + hook2 = SH_ADD_VPHOOK(IBase, Func3, p_d1i1, SH_STATIC(Handler_Func3_Pre), false); + + // Normally, no VP + int hook3 = SH_ADD_HOOK(IBase, Func3, p_d1i1, SH_STATIC(Handler_Func3_Pre), false); + + p_d1i1->Func3(1); + + CHECK_STATES((&g_States, + new State_Func3_Pre(p_d1i1, 1), // manual vp hook + new State_Func3_Pre(p_d1i1, 2), // normal vp hook + new State_Func3_Pre(p_d1i1, 3), // normal non-vp hook + + new State_D1_Func3(p_d1i1, 4), // function + + NULL), "Part 7.1"); + + p_d1i2->Func3(1); + + CHECK_STATES((&g_States, + new State_Func3_Pre(p_d1i2, 1), // manual vp hook + new State_Func3_Pre(p_d1i2, 2), // normal vp hook + + new State_D1_Func3(p_d1i2, 3), // function + + NULL), "Part 7.2"); + + SH_REMOVE_HOOK_ID(hook1); + SH_REMOVE_HOOK_ID(hook2); + SH_REMOVE_HOOK_ID(hook3); + + // Test direct VP hook + + p_d1i1->Func1(); + p_d1i2->Func1(); + + // get vtable manually + void *pVtable = *reinterpret_cast(p_d1i1); + + hook1 = SH_ADD_DVPHOOK(IBase, Func1, pVtable, SH_STATIC(Handler_Func1_Pre), false); + + p_d1i1->Func1(); + p_d1i2->Func1(); + + CHECK_STATES((&g_States, + new State_D1_Func1(p_d1i1), + new State_D1_Func1(p_d1i2), + + new State_Func1_Pre(p_d1i1), + new State_D1_Func1(p_d1i1), + new State_Func1_Pre(p_d1i2), + new State_D1_Func1(p_d1i2), + NULL), "Part 8.1"); + + SH_REMOVE_HOOK_ID(hook1); + + // Now try manual dvp hooks + p_d1i1->Func3(1); + p_d1i2->Func3(1); + + hook1 = SH_ADD_MANUALDVPHOOK(IBase_Func3_Manual, pVtable, SH_STATIC(Handler_Func3_Pre), false); + + p_d1i1->Func3(1); + p_d1i2->Func3(1); + + CHECK_STATES((&g_States, + new State_D1_Func3(p_d1i1, 1), + new State_D1_Func3(p_d1i2, 1), + + new State_Func3_Pre(p_d1i1, 1), // manual vp hook + new State_D1_Func3(p_d1i1, 2), // function + + new State_Func3_Pre(p_d1i2, 1), // normal vp hook + new State_D1_Func3(p_d1i2, 2), // function + + NULL), "Part 8.2"); + + + // Test removing normal hooks even though the instance is deleted + IBase *pOther = new CDerived1; + SH_ADD_HOOK(IBase, Func1, pOther, SH_STATIC(Handler_Func1_Pre), false); + delete pOther; + // The following line may not crash! + SH_REMOVE_HOOK(IBase, Func1, pOther, SH_STATIC(Handler_Func1_Pre), false); + + + // Now test GetOrigVfnPtrEntry + void *origfuncptr = (*reinterpret_cast(p_d1i1))[0]; + + CHECK_COND(SH_GET_ORIG_VFNPTR_ENTRY(p_d1i1, &IBase::Func1) == origfuncptr, "Part 9.1"); + + SH_ADD_HOOK(IBase, Func1, p_d1i1, SH_STATIC(Handler_Func1_Pre), false); + + CHECK_COND(SH_GET_ORIG_VFNPTR_ENTRY(p_d1i1, &IBase::Func1) == origfuncptr, "Part 9.2"); + + SH_REMOVE_HOOK(IBase, Func1, p_d1i1, SH_STATIC(Handler_Func1_Pre), false); + + CHECK_COND(SH_GET_ORIG_VFNPTR_ENTRY(p_d1i1, &IBase::Func1) == origfuncptr, "Part 9.3"); + + return true; +} +