First version of automatic hookman/hookfunc generation functionality + test

Currently supports (msvc): all params, ret types: integeral, floating-point
Todo: other rettypes (pod/objects, ctors/dtors), gcc support, integration with ISourceHook::AddHook

--HG--
branch : hookman_autogen
extra : convert_revision : svn%3Ac2935e3e-5518-0410-8daf-afa5dab7d4e3/branches/hookman_autogen%40532
This commit is contained in:
Pavol Marko 2007-10-21 21:47:53 +00:00
parent f440bbd1b2
commit 7fa9f150fb
10 changed files with 6282 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,232 @@
/* ======== SourceHook ========
* Copyright (C) 2004-2007 Metamod:Source Development Team
* No warranties of any kind
*
* License: zlib/libpng
*
* Author(s): Pavol "PM OnoTo" Marko
* ============================
*/
#ifndef __SOURCEHOOK_HOOKMANGEN_H__
#define __SOURCEHOOK_HOOKMANGEN_H__
#include "sourcehook_impl.h"
#include "windows.h"
namespace SourceHook
{
namespace Impl
{
// Code gen stuff
#if defined HAVE_STDINT_H && !defined WIN32
#include <stdint.h>
typedef int8_t jit_int8_t;
typedef uint8_t jit_uint8_t;
typedef int32_t jit_int32_t;
typedef uint32_t jit_uint32_t;
typedef int64_t jit_int64_t;
typedef uint64_t jit_uint64_t;
#elif defined WIN32
typedef __int8 jit_int8_t;
typedef unsigned __int8 jit_uint8_t;
typedef __int32 jit_int32_t;
typedef unsigned __int32 jit_uint32_t;
typedef __int64 jit_int64_t;
typedef unsigned __int64 jit_uint64_t;
#endif
typedef unsigned int jitoffs_t;
typedef signed int jitrel_t;
class GenBuffer
{
unsigned char *m_pData;
jitoffs_t m_Size;
jitoffs_t m_AllocatedSize;
public:
GenBuffer() : m_pData(NULL), m_Size(0), m_AllocatedSize(0)
{
}
~GenBuffer()
{
clear();
}
jitoffs_t GetSize()
{
return m_Size;
}
unsigned char *GetData()
{
return m_pData;
}
template <class PT> void push(PT what)
{
push((const unsigned char *)&what, sizeof(PT));
}
void push(const unsigned char *data, jitoffs_t size)
{
jitoffs_t newSize = m_Size + size;
if (newSize > m_AllocatedSize)
{
m_AllocatedSize = newSize > m_AllocatedSize*2 ? newSize : m_AllocatedSize*2;
if (m_AllocatedSize < 64)
m_AllocatedSize = 64;
unsigned char *newBuf;
try
{
//!!!! Better use of pages! or something!
newBuf = reinterpret_cast<unsigned char*>(VirtualAlloc(NULL, m_AllocatedSize, MEM_COMMIT, PAGE_READWRITE));
}
catch (std::bad_alloc)
{
newBuf = NULL;
}
if (!newBuf)
{
SH_ASSERT(0, ("bad_alloc: couldn't allocate 0x%08X bytes of memory\n", m_AllocatedSize));
return;
}
memcpy((void*)newBuf, (const void*)m_pData, m_Size);
if (m_pData)
VirtualFree(m_pData, 0, MEM_RELEASE);
m_pData = newBuf;
}
memcpy((void*)(m_pData + m_Size), (const void*)data, size);
m_Size = newSize;
}
template <class PT> void rewrite(jitoffs_t offset, PT what)
{
rewrite(offset, (const unsigned char *)&what, sizeof(PT));
}
void rewrite(jitoffs_t offset, const unsigned char *data, jitoffs_t size)
{
SH_ASSERT(offset + size <= m_AllocatedSize, ("rewrite too far"));
memcpy((void*)(m_pData + offset), (const void*)data, size);
}
void clear()
{
if (m_pData)
VirtualFree(m_pData, 0, MEM_RELEASE);
m_pData = NULL;
m_Size = 0;
m_AllocatedSize = 0;
}
operator unsigned char *()
{
return GetData();
}
void write_ubyte(jit_uint8_t x) { push(x); }
void write_byte(jit_uint8_t x) { push(x); }
void write_ushort(unsigned short x) { push(x); }
void write_short(signed short x) { push(x); }
void write_uint32(jit_uint32_t x) { push(x); }
void write_int32(jit_uint32_t x) { push(x); }
jitoffs_t get_outputpos()
{
return m_Size;
}
void start_count(jitoffs_t &offs)
{
offs = get_outputpos();
}
void end_count(jitoffs_t &offs)
{
offs = get_outputpos() - offs;
}
// :TODO: real buffer which uses virtualalloc correctly
};
class GenContext
{
const static int SIZE_MWORD = 4;
const static int SIZE_PTR = sizeof(void*);
CProto m_Proto;
int m_VtblOffs;
int m_VtblIdx;
ISourceHook *m_SHPtr;
GenBuffer m_HookFunc;
GenBuffer m_PubFunc;
ProtoInfo *m_BuiltPI;
PassInfo *m_BuiltPI_Params;
PassInfo::V2Info *m_BuiltPI_Params2;
// For hookfunc
void **m_pHI;
void **m_HookfuncVfnptr;
// Level 3 - Helpers
int m_RegCounter;
jit_int8_t NextRegEBX_ECX_EDX();
// size info
jit_int32_t GetRealSize(const IntPassInfo &info);
jit_int32_t GetStackSize(const IntPassInfo &info);
short GetParamsStackSize(); // sum(GetStackSize(i), 0 <= i < numOfParams)
// Param push
jit_int32_t PushParams(jit_int32_t param_base_offset);
jit_int32_t PushRef(jit_int32_t param_offset, const IntPassInfo &pi);
jit_int32_t PushBasic(jit_int32_t param_offset, const IntPassInfo &pi);
jit_int32_t PushFloat(jit_int32_t param_offset, const IntPassInfo &pi);
jit_int32_t PushObject(jit_int32_t param_offset, const IntPassInfo &pi);
// Ret val processing
void SaveRetVal(int v_where);
void ProcessPluginRetVal(int v_cur_res, int v_pContext, int v_plugin_ret);
void PrepareReturn(int v_status, int v_pContext, int v_retptr);
void DoReturn(int v_retptr);
// Call hooks
void GenerateCallHooks(int v_status, int v_prev_res, int v_cur_res, int v_iter,
int v_pContext, int base_param_offset, int v_plugin_ret);
// Call orig
void GenerateCallOrig(int v_status, int v_pContext,
int param_base_offs, int v_this, int v_vfnptr_origentry, int v_orig_ret, int v_override_ret);
// Hook loop
void CallSetupHookLoop(int v_orig_ret, int v_override_ret,
int v_cur_res, int v_prev_res, int v_status, int v_vnfptr_origentry,
int v_this, int v_pContext);
void CallEndContext(int v_pContext);
// Level 2 -> called from Generate()
bool PassInfoSupported(const IntPassInfo &pi, bool is_ret);
void Clear();
void BuildProtoInfo();
unsigned char *GenerateHookFunc();
unsigned char *GeneratePubFunc();
public:
// Level 1 -> Public interface
GenContext(const ProtoInfo *proto, int vtbl_offs, int vtbl_idx, ISourceHook *pSHPtr);
~GenContext();
HookManagerPubFunc Generate();
};
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
:: Generates everything
:: Usage:
:: generate.bat <num-of-arguments>
..\generate\shworker iter testhookmangen.hxx testhookmangen.h %1

View File

@ -46,6 +46,7 @@ DECL_TEST(Multi);
DECL_TEST(Ref);
DECL_TEST(RefRet);
DECL_TEST(VPHooks);
DECL_TEST(HookManGen);
int main(int argc, char *argv[])
{
@ -68,6 +69,7 @@ int main(int argc, char *argv[])
DO_TEST(Ref);
DO_TEST(RefRet);
DO_TEST(VPHooks);
DO_TEST(HookManGen);
cout << endl << "----" << endl << "Passed: " << passed << endl << "Failed: " << failed << endl;
cout << "Total: " << passed + failed << endl;

View File

@ -17,6 +17,7 @@ struct CAutoPtrDestruction
CAutoPtrDestruction(T *p) : m_Ptr(p) { }
~CAutoPtrDestruction() { if (m_Ptr) delete m_Ptr; }
void clear() { m_Ptr = NULL; }
void set(T *ptr) { m_Ptr = ptr; }
};
struct CSHPtrAutoDestruction

View File

@ -31,9 +31,25 @@ struct State
return (typeid(other) == typeid(this)) ? true : false;
}
virtual bool Ignore()
{
return false;
}
virtual void Dump() = 0;
};
struct IgnoreState : public State
{
virtual bool Ignore()
{
return true;
}
virtual void Dump()
{
}
};
typedef std::list<State*> StateList;
namespace
{
@ -53,6 +69,8 @@ namespace
State *cs = va_arg(argptr, State*);
if (!cs)
break;
if (cs->Ignore())
continue;
requiredstates.push_back(cs);
}
va_end(argptr);
@ -86,6 +104,14 @@ namespace
}
}
if (!ok && g_Verbose)
{
std::cout << std::endl << "FAIL: Should be:" << std::endl;
DumpStates(&requiredstates);
std::cout << std::endl << "FAIL: Is:" << std::endl;
DumpStates(sl);
}
for (StateList::iterator iter = requiredstates.begin(); iter != requiredstates.end(); ++iter)
delete *iter;
for (StateList::iterator iter = sl->begin(); iter != sl->end(); ++iter)

View File

@ -0,0 +1,470 @@
#include <string>
#include "sourcehook.h"
#include "sourcehook_test.h"
#include "testevents.h"
#include "sourcehook_hookmangen.h"
// TESTHOOKMANGEN
// Test automatic hookman generation
// Tests void and non-void functions
// 0 to 6 params:
// integer-type, float-type, plain-old-data struct and objects with ctors/dtors
// both byval and byref
// also tests ignore/supercede
// also tests recalls
// :TODO: test override as well
namespace
{
#include "testhookmangen.h"
StateList g_States;
SourceHook::ISourceHook *g_SHPtr;
SourceHook::Plugin g_PLID;
// PtrBuf(ptr) gives ptrs unique numbers
// in the order they appear
SourceHook::List<const void*> g_PtrHash;
bool g_Inside_LeafFunc = false; // inside a hook or a func
template <class T>
int PtrBuf(T ptr)
{
int a = 0;
const void *vptr = reinterpret_cast<const void*>(ptr);
for (SourceHook::List<const void*>::iterator iter = g_PtrHash.begin(); iter != g_PtrHash.end(); ++iter)
{
if (*iter == vptr)
return a;
else
++a;
}
g_PtrHash.push_back(vptr);
return static_cast<int>(g_PtrHash.size()) - 1;
}
template <class T>
T PtrBufPtr(T ptr)
{
PtrBuf(ptr);
return ptr;
}
void PtrBuf_Clear(int leave_in = 0)
{
for (SourceHook::List<const void*>::iterator iter = g_PtrHash.begin(); iter != g_PtrHash.end();)
{
if (--leave_in < 0)
iter = g_PtrHash.erase(iter);
else
++iter;
}
}
// POD / Object types
template <int MYSIZE>
struct POD
{
char x[MYSIZE];
bool operator==(const POD<MYSIZE> &other)
{
return memcmp(reinterpret_cast<void*>(x), reinterpret_cast<const void*>(other.x), MYSIZE) == 0;
}
};
template <int MYSIZE>
std::ostream& operator <<(std::ostream &os, const POD<MYSIZE> &obj)
{
os << "Some POD!";
return os;
}
MAKE_STATE_1(State_ObjOCtor_Called, int /*MYSIZE*/);
MAKE_STATE_1(State_ObjCCtor_Called, int /*MYSIZE*/);
MAKE_STATE_1(State_ObjODtor_Called, int /*MYSIZE*/);
template <int MYSIZE>
struct Object
{
char x[MYSIZE];
Object(char initch)
{
memset(reinterpret_cast<void*>(x), initch, MYSIZE);
if (!g_Inside_LeafFunc)
ADD_STATE(State_ObjOCtor_Called(MYSIZE));
}
Object()
{
if (!g_Inside_LeafFunc)
ADD_STATE(State_ObjOCtor_Called(MYSIZE));
}
Object(const Object<MYSIZE> & other)
{
memcpy(reinterpret_cast<void*>(x), reinterpret_cast<const void*>(other.x), MYSIZE);
if (!g_Inside_LeafFunc)
ADD_STATE(State_ObjCCtor_Called(MYSIZE));
}
~Object()
{
if (!g_Inside_LeafFunc)
ADD_STATE(State_ObjODtor_Called(MYSIZE));
}
bool operator==(const Object<MYSIZE> &other)
{
return memcmp(reinterpret_cast<void*>(x), reinterpret_cast<const void*>(other.x), MYSIZE) == 0;
}
};
template <int MYSIZE>
std::ostream& operator <<(std::ostream &os, const Object<MYSIZE> &obj)
{
os << "Some Obj!";
return os;
}
// "Increment" something: (reproducible change for recall tests)
// integer: ++
// float: += 1.3
// pod/object: x[i]++, 0 <= i < MYSIZE
template <class T>
struct Increment
{
static void Incr(T &what)
{
++what;
}
};
template<>
struct Increment<float>
{
static void Incr(float &what)
{
what += 1.3f;
}
};
template<>
struct Increment<double>
{
static void Incr(double &what)
{
what += 1.3;
}
};
template<int MYSIZE>
struct Increment< POD<MYSIZE> >
{
static void Incr(POD<MYSIZE> &what)
{
for (int i = 0; i < MYSIZE; ++i)
++ what.x[i];
}
};
template<int MYSIZE>
struct Increment< Object<MYSIZE> >
{
static void Incr(Object<MYSIZE> &what)
{
for (int i = 0; i < MYSIZE; ++i)
++ what.x[i];
}
};
// MyDelegate base class for other delegates
class MyDelegate : public SourceHook::ISHDelegate
{
// Unneeded
// because we don't use old SH_REMOVE_HOOK syntax
virtual bool IsEqual(SourceHook::ISHDelegate *pOtherDeleg)
{
return false;
}
virtual void DeleteThis()
{
delete this;
}
};
THGM_MAKE_TEST0_void(0);
THGM_SETUP_PI0(0);
THGM_MAKE_TEST1_void(1, char);
THGM_SETUP_PI1(1, char, SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal);
THGM_MAKE_TEST1_void(2, short);
THGM_SETUP_PI1(2, short, SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal);
THGM_MAKE_TEST1_void(3, int);
THGM_SETUP_PI1(3, int, SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal);
THGM_MAKE_TEST1_void(4, float);
THGM_SETUP_PI1(4, float, SourceHook::PassInfo::PassType_Float, SourceHook::PassInfo::PassFlag_ByVal);
THGM_MAKE_TEST1_void(5, double);
THGM_SETUP_PI1(5, double, SourceHook::PassInfo::PassType_Float, SourceHook::PassInfo::PassFlag_ByVal);
THGM_MAKE_TEST5_void(6, char, short, int, float, double);
THGM_SETUP_PI5(6,
char, SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal,
short, SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal,
int, SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal,
float, SourceHook::PassInfo::PassType_Float, SourceHook::PassInfo::PassFlag_ByVal,
double, SourceHook::PassInfo::PassType_Float, SourceHook::PassInfo::PassFlag_ByVal
);
THGM_MAKE_TEST2_void(7, char&, double&);
THGM_SETUP_PI2(7,
char&, SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByRef,
double&, SourceHook::PassInfo::PassType_Float, SourceHook::PassInfo::PassFlag_ByRef
);
THGM_MAKE_TEST1_void(8, POD<7>);
THGM_SETUP_PI1(8, POD<7>, SourceHook::PassInfo::PassType_Object, SourceHook::PassInfo::PassFlag_ByVal);
THGM_MAKE_TEST1_void(9, POD<600>);
THGM_SETUP_PI1(9, POD<600>, SourceHook::PassInfo::PassType_Object, SourceHook::PassInfo::PassFlag_ByVal);
THGM_MAKE_TEST1_void(10, POD<600> &);
THGM_SETUP_PI1(10, POD<600> &, SourceHook::PassInfo::PassType_Object, SourceHook::PassInfo::PassFlag_ByRef);
THGM_MAKE_TEST2_void(11, Object<3>, Object<600>&);
THGM_SETUP_PI2(11,
Object<3>, SourceHook::PassInfo::PassType_Object, SourceHook::PassInfo::PassFlag_ByVal | SourceHook::PassInfo::PassFlag_OCtor | SourceHook::PassInfo::PassFlag_ODtor | SourceHook::PassInfo::PassFlag_CCtor,
Object<600> &, SourceHook::PassInfo::PassType_Object, SourceHook::PassInfo::PassFlag_ByRef | SourceHook::PassInfo::PassFlag_OCtor | SourceHook::PassInfo::PassFlag_ODtor | SourceHook::PassInfo::PassFlag_CCtor
);
THGM_MAKE_TEST0(101, char);
THGM_SETUP_PI0(101);
THGM_SETUP_RI(101, char, SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal);
THGM_MAKE_TEST0(102, short);
THGM_SETUP_PI0(102);
THGM_SETUP_RI(102, short, SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal);
THGM_MAKE_TEST0(103, int);
THGM_SETUP_PI0(103);
THGM_SETUP_RI(103, int, SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal);
THGM_MAKE_TEST0(104, float);
THGM_SETUP_PI0(104);
THGM_SETUP_RI(104, float, SourceHook::PassInfo::PassType_Float, SourceHook::PassInfo::PassFlag_ByVal);
THGM_MAKE_TEST0(105, double);
THGM_SETUP_PI0(105);
THGM_SETUP_RI(105, double, SourceHook::PassInfo::PassType_Float, SourceHook::PassInfo::PassFlag_ByVal);
}
bool TestHookManGen(std::string &error)
{
GET_SHPTR(g_SHPtr);
g_PLID = 1337;
THGM_DO_TEST_void(0, ());
THGM_DO_TEST_void(1, (100));
THGM_DO_TEST_void(2, (0x1F00));
THGM_DO_TEST_void(3, (0x1F000000));
THGM_DO_TEST_void(4, (0.5f));
THGM_DO_TEST_void(5, (5.5));
THGM_DO_TEST_void(6, (100, 0x1f00, 0x1f000000, 0.5f, 5.5));
char a = 5;
double b = 233.33;
THGM_DO_TEST_void(7, (a, b));
POD<7> pod7 = {78};
THGM_DO_TEST_void(8, (pod7));
POD<600> pod600 = {34};
THGM_DO_TEST_void(9, (pod600));
THGM_DO_TEST_void(10, (pod600));
// Test11: Special: constructors/destructors
PtrBuf_Clear();
Object<3> obj3(33);
Object<600> obj600(21);
CHECK_STATES((&g_States,
new State_ObjOCtor_Called(3),
new State_ObjOCtor_Called(600),
NULL), "Test11 Part0");
setuppi_11();
g_Genc11 = new SourceHook::Impl::GenContext(&protoinfo_11, 0, 0, g_SHPtr);
g_Genc_ad11.set(g_Genc11);
SourceHook::HookManagerPubFunc myhookman11 = g_Genc11->Generate();
int hook1_11, hook2_11, hook3_11, hook4_11;
TestClass11 *pTest11 = new TestClass11;
CAutoPtrDestruction<TestClass11> apd11(pTest11);
/* no hooks - no hooks */
PtrBuf_Clear();
pTest11->Func(obj3, obj600);
g_Inside_LeafFunc = true;
CHECK_STATES((&g_States,
new State_ObjCCtor_Called(3),
new State_Func11(pTest11, ParamState_m11 (obj3, obj600)),
new State_ObjODtor_Called(3),
NULL), "Test" "11" " Part1");
g_Inside_LeafFunc = false;
/* hook1 - no hooks */
THGM_ADD_HOOK(11, 1);
PtrBuf_Clear();
pTest11->Func(obj3, obj600);
g_Inside_LeafFunc = true;
CHECK_STATES((&g_States,
new State_ObjCCtor_Called(3),
new State_ObjCCtor_Called(3),
new State_Deleg1_11(pTest11, 0, ParamState_m11 (obj3, obj600)),
new State_ObjODtor_Called(3),
new State_ObjCCtor_Called(3),
new State_Func11(pTest11, ParamState_m11 (obj3, obj600)),
new State_ObjODtor_Called(3),
new State_ObjODtor_Called(3),
NULL), "Test" "11" " Part2");
g_Inside_LeafFunc = false;
THGM_REMOVE_HOOK(11, 1);
/* hook1, hook2 - hook3, hook4 */
THGM_ADD_HOOK(11, 1);
THGM_ADD_HOOK(11, 2);
THGM_ADD_HOOK(11, 3);
THGM_ADD_HOOK(11, 4);
PtrBuf_Clear();
pTest11->Func(obj3, obj600);
g_Inside_LeafFunc = true;
CHECK_STATES((&g_States,
new State_ObjCCtor_Called(3),
new State_ObjCCtor_Called(3),
new State_Deleg1_11(pTest11, 0, ParamState_m11 (obj3, obj600)),
new State_ObjODtor_Called(3),
new State_ObjCCtor_Called(3),
new State_Deleg2_11(pTest11, 1, ParamState_m11 (obj3, obj600)),
new State_ObjODtor_Called(3),
new State_ObjCCtor_Called(3),
new State_Deleg3_11(pTest11, 2, ParamState_m11 (obj3, obj600)),
new State_ObjODtor_Called(3),
new State_ObjCCtor_Called(3),
new State_Deleg4_11(pTest11, 3, ParamState_m11 (obj3, obj600)),
new State_ObjODtor_Called(3),
new State_ObjODtor_Called(3),
NULL), "Test" "11" " Part3");
g_Inside_LeafFunc = false;
/* hook1 - hook3, hook4 WITH RECALLS */
THGM_REMOVE_HOOK(11, 2);
PtrBuf_Clear();
TestClass11::ms_DoRecall = true;
pTest11->Func(obj3, obj600);
g_Inside_LeafFunc = true;
CHECK_STATES((&g_States,
new State_ObjCCtor_Called(3),
new State_ObjCCtor_Called(3),
new State_Deleg1_11(pTest11, 0 /* first deleg ptr */, ParamState_m11 (obj3, obj600)),
// recall !
// second hookfunc -> new copy
new State_ObjCCtor_Called(3),
// in second hookfunc now
// -> calls orig func
new State_ObjCCtor_Called(3),
new State_Func11(pTest11, ParamState_m11 (obj3, obj600)(1)),
new State_ObjODtor_Called(3),
// calls first posthook
new State_ObjCCtor_Called(3),
new State_Deleg3_11(pTest11, 1 /* second deleg ptr */, ParamState_m11 (obj3, obj600)(1)),
// recall!
// third hookfunc -> new copy
new State_ObjCCtor_Called(3),
// calls second posthook
new State_ObjCCtor_Called(3),
new State_Deleg4_11(pTest11, 2 /* third deleg ptr */, ParamState_m11 (obj3, obj600)(2)),
// recall!
// fourth hookfunc -> new copy
new State_ObjCCtor_Called(3),
// has nothing to do though!
// fourth hookfunc done -> ret
new State_ObjODtor_Called(3),
// third hookfunc done -> ret
new State_ObjODtor_Called(3),
// ret from hookhandler which did the recall
new State_ObjODtor_Called(3),
// second hookfunc done -> ret
new State_ObjODtor_Called(3),
// ret from hookhandler which did the recall
new State_ObjODtor_Called(3),
// deleg1's instance
new State_ObjODtor_Called(3),
// first hookfunc done -> ret
new State_ObjODtor_Called(3),
NULL), "Test" "11" " Part4");
g_Inside_LeafFunc = false;
THGM_REMOVE_HOOK(11, 1);
THGM_REMOVE_HOOK(11, 3);
THGM_REMOVE_HOOK(11, 4);
THGM_DO_TEST(101, ());
THGM_DO_TEST(102, ());
THGM_DO_TEST(103, ());
THGM_DO_TEST(104, ());
THGM_DO_TEST(105, ());
// Shutdown now!
// If we don't SH will auto-shutdown _after_ genc's destructor is called
// -> crash
Test_CompleteShutdown(g_SHPtr);
return true;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,578 @@
// Strip &
template <class T> struct StripRef
{
typedef T type;
};
template <class T> struct StripRef<T&>
{
typedef T type;
};
// Address of constructor/destructor
// (using wrappers)
template <class T>
class Ctor_Thunk
{
public:
void NormalConstructor()
{
new(this) T;
}
void CopyConstructor(const T &other)
{
new(this) T(other);
}
void Destructor()
{
reinterpret_cast<T*>(this)->~T();
}
};
template <class T>
void *FindFuncAddr(T mfp)
{
if (sizeof(mfp) != sizeof(void*))
return NULL;
else
return fastdelegate::detail::horrible_cast<void*>(mfp);
}
// Reference carrier
template <class T> struct MyRefCarrier
{
typedef T type;
};
template <class T> struct MyRefCarrier<T&>
{
class type
{
T *m_StoredRef;
public:
type() : m_StoredRef(NULL)
{
}
type(T& ref) : m_StoredRef(&ref)
{
}
T& operator= (T& ref)
{
m_StoredRef = &ref;
return ref;
}
operator T& () const
{
return *m_StoredRef;
}
bool operator== (const typename MyRefCarrier<T&>::type &other)
{
return m_StoredRef == other.m_StoredRef;
}
friend std::ostream& operator <<(std::ostream &os,const typename MyRefCarrier<T&>::type &obj)
{
os << *obj.m_StoredRef;
return os;
}
};
};
// Stores parameter status
@[$1,0,$a:
template<int dummy@[$2,1,$1:, class p$2@]>
struct ParamState$1
{
@[$2,1,$1:typename MyRefCarrier<p$2>::type m_$2; @]
bool operator==(const ParamState$1<dummy@[$2,1,$1:, p$2@]> &other)
{
return true
@[$2,1,$1: && m_$1 == other.m_$1@]
;
}
ParamState$1(@[$2,1,$1|, :p$2 a$2@]) @[$1!=0::@] @[$2,1,$1|, :m_$2(a$2)@]
{
}
ParamState$1<dummy@[$2,1,$1:, p$2@]> & operator() (int incrsteps)
{
@[$1!=0:int i;@]
@[$2,1,$1:
for (i = 0; i < incrsteps; ++i)
Increment<StripRef< p$2 >::type>::Incr(m_$2);
@]
return *this;
}
};
@[$1!=0:template<@[$2,1,$1|, :class p$2@]>@]
std::ostream& operator <<(std::ostream &os,const ParamState$1<0@[$2,1,$1:, p$2@]> &obj)
{
@[$1!=0:os@] @[$2,1,$1:<< obj.m_$2@];
return os;
}
@]
#define CAT2(a, b) a##b
#define CAT3(a, b, c) a##b##c
#define CAT4(a, b, c, d) a##b##c##d
// hook1: pre ignore
// hook2: pre supercede
// hook3: post ignore
// hook4: post supercede
@[$1,0,$a:
#define THGM_MAKE_TEST$1_void(id@[$2,1,$1:, param$2@]) \
struct TestClass##id; \
typedef ParamState$1<0@[$2,1,$1:, param$2@] > ParamState_m##id; \
MAKE_STATE_2(State_Func##id, TestClass##id* /*thisptr*/, ParamState_m##id ); \
MAKE_STATE_3(State_Deleg1_##id, TestClass##id* /*ifptr*/, int /*deleg thisptr*/, ParamState_m##id ); \
MAKE_STATE_3(State_Deleg2_##id, TestClass##id* /*ifptr*/, int /*deleg thisptr*/, ParamState_m##id ); \
MAKE_STATE_3(State_Deleg3_##id, TestClass##id* /*ifptr*/, int /*deleg thisptr*/, ParamState_m##id ); \
MAKE_STATE_3(State_Deleg4_##id, TestClass##id* /*ifptr*/, int /*deleg thisptr*/, ParamState_m##id ); \
\
struct TestClass##id \
{ \
static bool ms_DoRecall; \
\
virtual void Func(@[$2,1,$1|, :param$2 p$2@]) \
{ \
g_Inside_LeafFunc = true; \
ADD_STATE(State_Func##id(this, ParamState_m##id(@[$2,1,$1|, :p$2@]))); \
g_Inside_LeafFunc = false; \
} \
\
struct Delegate1 : public MyDelegate \
{ \
virtual void Call(@[$2,1,$1|, :param$2 p$2@]) \
{ \
g_Inside_LeafFunc = true; \
ADD_STATE(State_Deleg1_##id(META_IFACEPTR(TestClass##id), PtrBuf(this), ParamState_m##id(@[$2,1,$1|, :p$2@]))); \
g_Inside_LeafFunc = false; \
if (ms_DoRecall) \
{ \
@[$2,1,$1:Increment<StripRef< param$2 >::type>::Incr(p$2);@] \
RETURN_META_NEWPARAMS(MRES_IGNORED, &TestClass##id::Func, (@[$2,1,$1|, :p$2@])); \
} \
else \
RETURN_META(MRES_IGNORED); \
} \
}; \
struct Delegate2 : public MyDelegate \
{ \
virtual void Call(@[$2,1,$1|, :param$2 p$2@]) \
{ \
g_Inside_LeafFunc = true; \
ADD_STATE(State_Deleg2_##id(META_IFACEPTR(TestClass##id), PtrBuf(this), ParamState_m##id(@[$2,1,$1|, :p$2@]))); \
g_Inside_LeafFunc = false; \
if (ms_DoRecall) \
{ \
@[$2,1,$1:Increment<StripRef< param$2 >::type>::Incr(p$2);@] \
RETURN_META_NEWPARAMS(MRES_SUPERCEDE, &TestClass##id::Func, (@[$2,1,$1|, :p$2@])); \
} \
else \
RETURN_META(MRES_SUPERCEDE); \
} \
}; \
struct Delegate3 : public MyDelegate \
{ \
virtual void Call(@[$2,1,$1|, :param$2 p$2@]) \
{ \
g_Inside_LeafFunc = true; \
ADD_STATE(State_Deleg3_##id(META_IFACEPTR(TestClass##id), PtrBuf(this), ParamState_m##id(@[$2,1,$1|, :p$2@]))); \
g_Inside_LeafFunc = false; \
if (ms_DoRecall) \
{ \
@[$2,1,$1:Increment<StripRef< param$2 >::type>::Incr(p$2);@] \
RETURN_META_NEWPARAMS(MRES_IGNORED, &TestClass##id::Func, (@[$2,1,$1|, :p$2@])); \
} \
else \
RETURN_META(MRES_IGNORED); \
} \
}; \
struct Delegate4 : public MyDelegate \
{ \
virtual void Call(@[$2,1,$1|, :param$2 p$2@]) \
{ \
g_Inside_LeafFunc = true; \
ADD_STATE(State_Deleg4_##id(META_IFACEPTR(TestClass##id), PtrBuf(this), ParamState_m##id(@[$2,1,$1|, :p$2@]))); \
g_Inside_LeafFunc = false; \
if (ms_DoRecall) \
{ \
@[$2,1,$1:Increment<StripRef< param$2 >::type>::Incr(p$2);@] \
RETURN_META_NEWPARAMS(MRES_SUPERCEDE, &TestClass##id::Func, (@[$2,1,$1|, :p$2@])); \
} \
else \
RETURN_META(MRES_SUPERCEDE); \
}; \
}; \
}; \
\
bool TestClass##id::ms_DoRecall = false; \
\
SourceHook::PassInfo paraminfos_##id[$1+1]; \
SourceHook::PassInfo::V2Info paraminfos2_##id[$1+1]; \
SourceHook::ProtoInfo protoinfo_##id = { $1, {0, 0, 0}, paraminfos_##id, \
SourceHook::ProtoInfo::CallConv_ThisCall, __SH_EPI, paraminfos2_##id }; \
\
SourceHook::Impl::GenContext *g_Genc##id = NULL; \
CAutoPtrDestruction<SourceHook::Impl::GenContext> g_Genc_ad##id(NULL);
#define THGM_MAKE_TEST$1(id, ret_type@[$2,1,$1:, param$2@]) \
struct TestClass##id; \
typedef ParamState$1<0@[$2,1,$1:, param$2@] > ParamState_m##id; \
MAKE_STATE_2(State_Func##id, TestClass##id* /*thisptr*/, ParamState_m##id ); \
MAKE_STATE_3(State_Deleg1_##id, TestClass##id* /*ifptr*/, int /*deleg thisptr*/, ParamState_m##id ); \
MAKE_STATE_3(State_Deleg2_##id, TestClass##id* /*ifptr*/, int /*deleg thisptr*/, ParamState_m##id ); \
MAKE_STATE_3(State_Deleg3_##id, TestClass##id* /*ifptr*/, int /*deleg thisptr*/, ParamState_m##id ); \
MAKE_STATE_3(State_Deleg4_##id, TestClass##id* /*ifptr*/, int /*deleg thisptr*/, ParamState_m##id ); \
\
struct TestClass##id \
{ \
static bool ms_DoRecall; \
\
virtual ret_type Func(@[$2,1,$1|, :param$2 p$2@]) \
{ \
g_Inside_LeafFunc = true; \
ADD_STATE(State_Func##id(this, ParamState_m##id(@[$2,1,$1|, :p$2@]))); \
g_Inside_LeafFunc = false; \
\
return 0; \
} \
\
struct Delegate1 : public MyDelegate \
{ \
virtual ret_type Call(@[$2,1,$1|, :param$2 p$2@]) \
{ \
g_Inside_LeafFunc = true; \
ADD_STATE(State_Deleg1_##id(META_IFACEPTR(TestClass##id), PtrBuf(this), ParamState_m##id(@[$2,1,$1|, :p$2@]))); \
g_Inside_LeafFunc = false; \
if (ms_DoRecall) \
{ \
@[$2,1,$1:Increment<StripRef< param$2 >::type>::Incr(p$2);@] \
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, 1, &TestClass##id::Func, (@[$2,1,$1|, :p$2@])); \
} \
else \
RETURN_META_VALUE(MRES_IGNORED, 1); \
} \
}; \
struct Delegate2 : public MyDelegate \
{ \
virtual ret_type Call(@[$2,1,$1|, :param$2 p$2@]) \
{ \
g_Inside_LeafFunc = true; \
ADD_STATE(State_Deleg2_##id(META_IFACEPTR(TestClass##id), PtrBuf(this), ParamState_m##id(@[$2,1,$1|, :p$2@]))); \
g_Inside_LeafFunc = false; \
if (ms_DoRecall) \
{ \
@[$2,1,$1:Increment<StripRef< param$2 >::type>::Incr(p$2);@] \
RETURN_META_VALUE_NEWPARAMS(MRES_SUPERCEDE, 2, &TestClass##id::Func, (@[$2,1,$1|, :p$2@])); \
} \
else \
RETURN_META_VALUE(MRES_SUPERCEDE, 2); \
} \
}; \
struct Delegate3 : public MyDelegate \
{ \
virtual ret_type Call(@[$2,1,$1|, :param$2 p$2@]) \
{ \
g_Inside_LeafFunc = true; \
ADD_STATE(State_Deleg3_##id(META_IFACEPTR(TestClass##id), PtrBuf(this), ParamState_m##id(@[$2,1,$1|, :p$2@]))); \
g_Inside_LeafFunc = false; \
if (ms_DoRecall) \
{ \
@[$2,1,$1:Increment<StripRef< param$2 >::type>::Incr(p$2);@] \
RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, 3, &TestClass##id::Func, (@[$2,1,$1|, :p$2@])); \
} \
else \
RETURN_META_VALUE(MRES_IGNORED, 3); \
} \
}; \
struct Delegate4 : public MyDelegate \
{ \
virtual ret_type Call(@[$2,1,$1|, :param$2 p$2@]) \
{ \
g_Inside_LeafFunc = true; \
ADD_STATE(State_Deleg4_##id(META_IFACEPTR(TestClass##id), PtrBuf(this), ParamState_m##id(@[$2,1,$1|, :p$2@]))); \
g_Inside_LeafFunc = false; \
if (ms_DoRecall) \
{ \
@[$2,1,$1:Increment<StripRef< param$2 >::type>::Incr(p$2);@] \
RETURN_META_VALUE_NEWPARAMS(MRES_SUPERCEDE, 4, &TestClass##id::Func, (@[$2,1,$1|, :p$2@])); \
} \
else \
RETURN_META_VALUE(MRES_SUPERCEDE, 4); \
}; \
}; \
}; \
\
bool TestClass##id::ms_DoRecall = false; \
\
SourceHook::PassInfo paraminfos_##id[$1+1]; \
SourceHook::PassInfo::V2Info paraminfos2_##id[$1+1]; \
SourceHook::ProtoInfo protoinfo_##id = { $1, {0, 0, 0}, paraminfos_##id, \
SourceHook::ProtoInfo::CallConv_ThisCall, __SH_EPI, paraminfos2_##id }; \
\
SourceHook::Impl::GenContext *g_Genc##id = NULL; \
CAutoPtrDestruction<SourceHook::Impl::GenContext> g_Genc_ad##id(NULL);
#define THGM_SETUP_PI$1(id@[$2,1,$1:, p$2_type, p$2_passtype, p$2_flags@]) \
void setuppi_##id() \
{ \
paraminfos_##id[0].size = 1; paraminfos_##id[0].type = 0; paraminfos_##id[0].flags = 0; \
\
@[$2,1,$1: paraminfos_##id[$2].size = sizeof(p$2_type); paraminfos_##id[$2].type = p$2_passtype; paraminfos_##id[$2].flags = p$2_flags; @] \
\
@[$2,1,$1: paraminfos2_##id[$2].pNormalCtor = (paraminfos_##id[$2].flags & SourceHook::PassInfo::PassFlag_OCtor) ? FindFuncAddr(&Ctor_Thunk<StripRef< p$2_type >::type>::NormalConstructor) : NULL; @] \
@[$2,1,$1: paraminfos2_##id[$2].pCopyCtor = (paraminfos_##id[$2].flags & SourceHook::PassInfo::PassFlag_CCtor) ? FindFuncAddr(&Ctor_Thunk<StripRef< p$2_type >::type>::CopyConstructor) : NULL; @] \
@[$2,1,$1: paraminfos2_##id[$2].pDtor = (paraminfos_##id[$2].flags & SourceHook::PassInfo::PassFlag_ODtor) ? FindFuncAddr(&Ctor_Thunk<StripRef< p$2_type >::type>::Destructor) : NULL; @] \
}
@]
#define THGM_SETUP_RI(id, ret_type, ret_passtype, ret_flags) \
void setupri_##id() \
{ \
protoinfo_##id.retPassInfo.size = sizeof(ret_type); \
protoinfo_##id.retPassInfo.type = ret_passtype; \
protoinfo_##id.retPassInfo.flags = ret_flags; \
\
protoinfo_##id.retPassInfo2.pNormalCtor = (protoinfo_##id.retPassInfo.flags & SourceHook::PassInfo::PassFlag_OCtor) ? FindFuncAddr(&Ctor_Thunk<StripRef< ret_type >::type>::NormalConstructor) : NULL; \
protoinfo_##id.retPassInfo2.pCopyCtor = (protoinfo_##id.retPassInfo.flags & SourceHook::PassInfo::PassFlag_CCtor) ? FindFuncAddr(&Ctor_Thunk<StripRef< ret_type >::type>::CopyConstructor) : NULL; \
protoinfo_##id.retPassInfo2.pDtor = (protoinfo_##id.retPassInfo.flags & SourceHook::PassInfo::PassFlag_ODtor) ? FindFuncAddr(&Ctor_Thunk<StripRef< ret_type >::type>::Destructor) : NULL; \
}
#define THGM_ADD_HOOK(id, num) \
CAT4(hook, num, _, id) = g_SHPtr->AddHook(g_PLID, SourceHook::ISourceHook::Hook_Normal, reinterpret_cast<void*>(pTest##id), \
0, myhookman##id, PtrBufPtr(new TestClass##id::Delegate##num), num >= 3);
#define THGM_REMOVE_HOOK(id, num) \
g_SHPtr->RemoveHookByID(CAT4(hook, num, _, id));
#define THGM_CALLS_void(id, call_params) \
pTest##id->Func call_params; \
SH_CALL(pTest##id, &TestClass##id::Func) call_params;
#define THGM_DO_TEST_void(id, call_params) \
setuppi_##id(); \
g_Genc##id = new SourceHook::Impl::GenContext(&protoinfo_##id, 0, 0, g_SHPtr); \
g_Genc_ad##id.set(g_Genc##id); \
SourceHook::HookManagerPubFunc myhookman##id = g_Genc##id->Generate(); \
int hook1_##id, hook2_##id, hook3_##id, hook4_##id; \
\
TestClass##id::ms_DoRecall = false; \
TestClass##id *pTest##id = new TestClass##id; \
CAutoPtrDestruction<TestClass##id> apd##id(pTest##id); \
\
/* no hooks - no hooks */ \
PtrBuf_Clear(); \
THGM_CALLS_void(id, call_params); \
CHECK_STATES((&g_States, \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part1"); \
\
/* hook1 - no hooks */ \
PtrBuf_Clear(); \
THGM_ADD_HOOK(id, 1); \
THGM_CALLS_void(id, call_params); \
CHECK_STATES((&g_States, \
new State_Deleg1_##id(pTest##id, 0, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part2"); \
THGM_REMOVE_HOOK(id, 1); \
\
/* no hooks - hook3 */ \
PtrBuf_Clear(); \
THGM_ADD_HOOK(id, 3); \
THGM_CALLS_void(id, call_params); \
CHECK_STATES((&g_States, \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
new State_Deleg3_##id(pTest##id, 0, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part3"); \
THGM_REMOVE_HOOK(id, 3); \
\
/* hook1 - hook3 */ \
PtrBuf_Clear(); \
THGM_ADD_HOOK(id, 1); \
THGM_ADD_HOOK(id, 3); \
THGM_CALLS_void(id, call_params); \
CHECK_STATES((&g_States, \
new State_Deleg1_##id(pTest##id, 0, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
new State_Deleg3_##id(pTest##id, 1, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part4"); \
THGM_REMOVE_HOOK(id, 1); \
THGM_REMOVE_HOOK(id, 3); \
\
/* hook1, hook2 - hook3 */ \
PtrBuf_Clear(); \
THGM_ADD_HOOK(id, 1); \
THGM_ADD_HOOK(id, 2); \
THGM_ADD_HOOK(id, 3); \
THGM_CALLS_void(id, call_params); \
CHECK_STATES((&g_States, \
new State_Deleg1_##id(pTest##id, 0, ParamState_m##id call_params), \
new State_Deleg2_##id(pTest##id, 1, ParamState_m##id call_params), \
new State_Deleg3_##id(pTest##id, 2, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part5"); \
THGM_REMOVE_HOOK(id, 1); \
THGM_REMOVE_HOOK(id, 2); \
THGM_REMOVE_HOOK(id, 3); \
/* hook1, hook2 - hook3, hook4 */ \
PtrBuf_Clear(); \
THGM_ADD_HOOK(id, 1); \
THGM_ADD_HOOK(id, 2); \
THGM_ADD_HOOK(id, 3); \
THGM_ADD_HOOK(id, 4); \
THGM_CALLS_void(id, call_params); \
CHECK_STATES((&g_States, \
new State_Deleg1_##id(pTest##id, 0, ParamState_m##id call_params), \
new State_Deleg2_##id(pTest##id, 1, ParamState_m##id call_params), \
new State_Deleg3_##id(pTest##id, 2, ParamState_m##id call_params), \
new State_Deleg4_##id(pTest##id, 3, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part6"); \
\
/* hook1 - hook3, hook4, with recalls! */ \
\
TestClass##id::ms_DoRecall = true; \
THGM_REMOVE_HOOK(id, 2); \
THGM_CALLS_void(id, call_params); \
CHECK_STATES((&g_States, \
new State_Deleg1_##id(pTest##id, 0, ParamState_m##id call_params(0)), \
new State_Func##id(pTest##id, ParamState_m##id call_params(1)), \
new State_Deleg3_##id(pTest##id, 2, ParamState_m##id call_params(1)), \
new State_Deleg4_##id(pTest##id, 3, ParamState_m##id call_params(2)), \
/* sh_call one */ \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part7"); \
THGM_REMOVE_HOOK(id, 1); \
THGM_REMOVE_HOOK(id, 3); \
THGM_REMOVE_HOOK(id, 4);
#define THGM_CALLS(id, call_params, exp_ret_norm, exp_ret_shcall, err) \
CHECK_COND(pTest##id->Func call_params == exp_ret_norm, err " /retcallnorm"); \
CHECK_COND(SH_CALL(pTest##id, &TestClass##id::Func) call_params == exp_ret_shcall, err " /retcallshcall");
#define THGM_DO_TEST(id, call_params) \
setuppi_##id(); \
setupri_##id(); \
g_Genc##id = new SourceHook::Impl::GenContext(&protoinfo_##id, 0, 0, g_SHPtr); \
g_Genc_ad##id.set(g_Genc##id); \
SourceHook::HookManagerPubFunc myhookman##id = g_Genc##id->Generate(); \
int hook1_##id, hook2_##id, hook3_##id, hook4_##id; \
\
TestClass##id::ms_DoRecall = false; \
TestClass##id *pTest##id = new TestClass##id; \
CAutoPtrDestruction<TestClass##id> apd##id(pTest##id); \
\
/* no hooks - no hooks */ \
PtrBuf_Clear(); \
THGM_CALLS(id, call_params, 0, 0, "Part1"); \
CHECK_STATES((&g_States, \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part1"); \
\
/* hook1 - no hooks */ \
PtrBuf_Clear(); \
THGM_ADD_HOOK(id, 1); \
THGM_CALLS(id, call_params, 0, 0, "Part2"); \
CHECK_STATES((&g_States, \
new State_Deleg1_##id(pTest##id, 0, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part2"); \
THGM_REMOVE_HOOK(id, 1); \
\
/* no hooks - hook3 */ \
PtrBuf_Clear(); \
THGM_ADD_HOOK(id, 3); \
THGM_CALLS(id, call_params, 0, 0, "Part3"); \
CHECK_STATES((&g_States, \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
new State_Deleg3_##id(pTest##id, 0, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part3"); \
THGM_REMOVE_HOOK(id, 3); \
\
/* hook1 - hook3 */ \
PtrBuf_Clear(); \
THGM_ADD_HOOK(id, 1); \
THGM_ADD_HOOK(id, 3); \
THGM_CALLS(id, call_params, 0, 0, "Part4"); \
CHECK_STATES((&g_States, \
new State_Deleg1_##id(pTest##id, 0, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
new State_Deleg3_##id(pTest##id, 1, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part4"); \
THGM_REMOVE_HOOK(id, 1); \
THGM_REMOVE_HOOK(id, 3); \
\
/* hook1, hook2 - hook3 */ \
PtrBuf_Clear(); \
THGM_ADD_HOOK(id, 1); \
THGM_ADD_HOOK(id, 2); \
THGM_ADD_HOOK(id, 3); \
THGM_CALLS(id, call_params, 2, 0, "Part5"); \
CHECK_STATES((&g_States, \
new State_Deleg1_##id(pTest##id, 0, ParamState_m##id call_params), \
new State_Deleg2_##id(pTest##id, 1, ParamState_m##id call_params), \
new State_Deleg3_##id(pTest##id, 2, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part5"); \
THGM_REMOVE_HOOK(id, 1); \
THGM_REMOVE_HOOK(id, 2); \
THGM_REMOVE_HOOK(id, 3); \
/* hook1, hook2 - hook3, hook4 */ \
PtrBuf_Clear(); \
THGM_ADD_HOOK(id, 1); \
THGM_ADD_HOOK(id, 2); \
THGM_ADD_HOOK(id, 3); \
THGM_ADD_HOOK(id, 4); \
THGM_CALLS(id, call_params, 4, 0, "Part6"); \
CHECK_STATES((&g_States, \
new State_Deleg1_##id(pTest##id, 0, ParamState_m##id call_params), \
new State_Deleg2_##id(pTest##id, 1, ParamState_m##id call_params), \
new State_Deleg3_##id(pTest##id, 2, ParamState_m##id call_params), \
new State_Deleg4_##id(pTest##id, 3, ParamState_m##id call_params), \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part6"); \
\
/* hook1 - hook3, hook4, with recalls! */ \
\
TestClass##id::ms_DoRecall = true; \
THGM_REMOVE_HOOK(id, 2); \
THGM_CALLS(id, call_params, 4, 0, "Part7"); \
CHECK_STATES((&g_States, \
new State_Deleg1_##id(pTest##id, 0, ParamState_m##id call_params(0)), \
new State_Func##id(pTest##id, ParamState_m##id call_params(1)), \
new State_Deleg3_##id(pTest##id, 2, ParamState_m##id call_params(1)), \
new State_Deleg4_##id(pTest##id, 3, ParamState_m##id call_params(2)), \
/* sh_call one */ \
new State_Func##id(pTest##id, ParamState_m##id call_params), \
NULL), "Test" #id " Part7"); \
THGM_REMOVE_HOOK(id, 1); \
THGM_REMOVE_HOOK(id, 3); \
THGM_REMOVE_HOOK(id, 4);