From 3ea2f023e1af08cb1cbf5aefd97c7aca3d7dff37 Mon Sep 17 00:00:00 2001 From: Pavol Marko Date: Sun, 5 Feb 2006 20:24:58 +0000 Subject: [PATCH] h-hello? new feature: RETURN_META_(VALUE_)NEWPARAMS now also works in post handlers! --HG-- extra : convert_revision : svn%3Ac2935e3e-5518-0410-8daf-afa5dab7d4e3/trunk%40180 --- sourcehook/sourcehook.cpp | 55 +++++++++++++++++++-- sourcehook/sourcehook_impl.h | 40 +++++++++++++++- sourcehook/test/testrecall.cpp | 87 ++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 6 deletions(-) diff --git a/sourcehook/sourcehook.cpp b/sourcehook/sourcehook.cpp index 37512e7..fe13d44 100644 --- a/sourcehook/sourcehook.cpp +++ b/sourcehook/sourcehook.cpp @@ -617,12 +617,19 @@ namespace SourceHook HookLoopInfo hli; hli.pCurIface = pIface; hli.shouldContinue = true; - hli.recall = false; + hli.recall = HookLoopInfo::Recall_No; + + static_cast(pIface)->m_PreHooks.RQFlagReset(); + static_cast(pIface)->m_PostHooks.RQFlagReset(); + m_HLIStack.push(hli); } void CSourceHookImpl::HookLoopEnd() { + // When in a post recall... make sure status is high enough so that it returns the orig ret. + if (m_HLIStack.size() > 1 && m_HLIStack.second().recall == HookLoopInfo::Recall_Post2) + *m_HLIStack.front().pStatus = MRES_SUPERCEDE; // THAT'LL TEACH THEM! m_HLIStack.pop(); } @@ -643,6 +650,10 @@ namespace SourceHook const void *CSourceHookImpl::GetOrigRet() { + // When in a post recall... return the orig ret of the previous hookloop. + if (m_HLIStack.size() > 1 && m_HLIStack.second().recall == HookLoopInfo::Recall_Post2) + return m_HLIStack.second().pOrigRet; + return m_HLIStack.front().pOrigRet; } @@ -724,7 +735,13 @@ namespace SourceHook HookLoopInfo &other = m_HLIStack.second(); *statusPtr = *other.pStatus; *prevResPtr = *other.pStatus; - hli.pOverrideRet = other.pOverrideRet; + + // When the status is low so there's no override return value and we're in a post recall, + // give it the orig return value as override return value. + if (*statusPtr < MRES_OVERRIDE && other.recall == HookLoopInfo::Recall_Post1) + hli.pOverrideRet = const_cast(other.pOrigRet); + else // Otherwise, transfer override ret normally + hli.pOverrideRet = other.pOverrideRet; } else hli.pOverrideRet = overrideRetPtr; @@ -740,6 +757,27 @@ namespace SourceHook // actual recall is done and that we are back in the original handler which shall return // immediately. + // Post-recalls: + // The second element on the stack has recall set to Recall_Post1. + // This means that we want to skip this part and the original function calling thing, so + // we store the status mres value, set status to MRES_SUPERCEDE, and return false. + // We also set the recall flag to Recall_Post2, so the next time the thing calls us, we + // can return true (so that the thing goes into post hooks). + if (m_HLIStack.size() > 1) + { + if (m_HLIStack.second().recall == HookLoopInfo::Recall_Post1) + { + m_HLIStack.front().temporaryStatus = *m_HLIStack.front().pStatus; + *m_HLIStack.front().pStatus = MRES_SUPERCEDE; + m_HLIStack.second().recall = HookLoopInfo::Recall_Post2; + return false; + } + else if (m_HLIStack.second().recall == HookLoopInfo::Recall_Post2) + { + *m_HLIStack.front().pStatus = m_HLIStack.front().temporaryStatus; + return m_HLIStack.front().shouldContinue; + } + } return m_HLIStack.front().shouldContinue && !m_HLIStack.front().recall; } @@ -747,11 +785,16 @@ namespace SourceHook { if (!m_HLIStack.empty()) { - m_HLIStack.front().recall = true; - CHookList *mlist = static_cast(m_HLIStack.front().pCurIface->GetPreHooks()); + // Also watch out for post recalls! Described at the beginning of sourcehook_impl.h + m_HLIStack.front().recall = + static_cast(m_HLIStack.front().pCurIface)->m_PostHooks.RQFlagGet() ? + HookLoopInfo::Recall_Post1 : HookLoopInfo::Recall_Pre; + + CHookList *mlist = static_cast(m_HLIStack.front().recall == HookLoopInfo::Recall_Pre ? + m_HLIStack.front().pCurIface->GetPreHooks() : m_HLIStack.front().pCurIface->GetPostHooks()); mlist->m_Recall = true; - // The hookfunc usually do this, but it won't have a chance to see it, + // The hookfunc usually does 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; @@ -931,6 +974,8 @@ namespace SourceHook } IHookList::IIter *CSourceHookImpl::CHookList::GetIter() { + m_RQFlag = true; + CIter *ret; if (m_FreeIters) { diff --git a/sourcehook/sourcehook_impl.h b/sourcehook/sourcehook_impl.h index 8a8798e..79dd559 100644 --- a/sourcehook/sourcehook_impl.h +++ b/sourcehook/sourcehook_impl.h @@ -116,6 +116,32 @@ Recalls When this recurisvely called hookfunc returns, the macro returns what it returned (using MRES_SUPERCEDE). CSourceHookImpl returns false from ShouldContinue so the original hook loop is abandonned. + +Post Recalls + People wanted to be able to use META_RETURN_(VALUE_)NEWPARAMS from post hooks as well. Crazy people! + Anyway, for this, we have to know where a hook handler is. Is it executing pre or post hooks at the moment? + The only way we can know this is watching when it calls CHookList::GetIter(). So CHookList gets a new variable: + m_RequestedFlag. It also gets two new functions: RQFlagReset() and RQFlagGet(). + HookLoopBegin() calls RQFlagReset on both hooklists of the iface; then DoRecall() checks whether the postlist's + RQ flag is set. if yes, the hook loop is in post mode. + + So, what a about a recall in post mode? The first ShouldContinue returns false and sets Status to supercede. + This way the pre hooks and the function call will be skipped. Then, then next ShouldContinue returns true, so we get + into the post hooks. HA! + +Return Values in Post Recalls + The easy case is when we already have an override return value. In this case, the status register gets transferred, + and so does the override return pointer. So, basically, everything is ok. + + However, what happens if we don't? ie. the status register is on MRES_IGNORED? In this case we'd have to transfer the + orig ret value. But we can't: There's no way to tell the hookfunc: "Use this as orig ret pointer". It uses its own. + So, we emulate it. GetOrigRet will return the orig ret pointer from the old hook loop. If still no one overrides it, + we'd have to return it. BUT! HOW TO DO THIS? Check out SH_RETURN(). First calls HookLoopEnd(), then decides whether + to return the override retval or the orig retval. But it doesn't ask for a new override return. So we give the function + the last orig return value as its new override return value; but leave status where it is so everything works, and in + HookLoopEnd we make sure that status is high enough so that the override return will be returned. crazy. + + All this stuff could be much less complicated if I didn't try to preserve binary compatibility :) */ namespace SourceHook @@ -240,6 +266,7 @@ namespace SourceHook // For recalls bool m_Recall; + bool m_RQFlag; void SetRecallState(); // Sets the list into a state where the next returned // iterator (from GetIter) will be a copy of the last @@ -247,6 +274,8 @@ namespace SourceHook // The hook resets this state automatically on: // GetIter, ReleaseIter + void RQFlagReset() { m_RQFlag = false; } + bool RQFlagGet() { return m_RQFlag; } CHookList(); CHookList(const CHookList &other); virtual ~CHookList(); @@ -452,12 +481,21 @@ namespace SourceHook struct HookLoopInfo { + enum RecallType + { + Recall_No=0, + Recall_Pre, + Recall_Post1, + Recall_Post2 + }; + META_RES *pStatus; META_RES *pPrevRes; META_RES *pCurRes; + META_RES temporaryStatus; //!< Stored during Post1 recall phase bool shouldContinue; - bool recall; //!< True if we're in a recall, eh. + RecallType recall; //!< Specifies which kind of recall we're in. IIface *pCurIface; const void *pOrigRet; diff --git a/sourcehook/test/testrecall.cpp b/sourcehook/test/testrecall.cpp index afb2064..a80ad1b 100644 --- a/sourcehook/test/testrecall.cpp +++ b/sourcehook/test/testrecall.cpp @@ -19,8 +19,14 @@ namespace MAKE_STATE_1(State_Func2, int); MAKE_STATE_1(State_H1_Func2, int); + MAKE_STATE_1(State_H2_Func2, int); MAKE_STATE_2(State_HP_Func2, int, int); + MAKE_STATE_2(State_Func22, int, int); + MAKE_STATE_2(State_H1_Func22, int, int); + MAKE_STATE_2(State_HP1_Func22, int, int); + MAKE_STATE_2(State_HP2_Func22, int, int); + struct Test { virtual void Func1(int a) @@ -37,6 +43,7 @@ namespace // Overloaded version virtual int Func2(int a, int b) { + ADD_STATE(State_Func22(a, b)); return 0xDEADFC; } }; @@ -65,14 +72,46 @@ namespace static_cast(&Test::Func2), (a - 10)); } + int Handler2_Func2(int a) + { + ADD_STATE(State_H2_Func2(a)); + RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, 0, + static_cast(&Test::Func2), (a - 10)); + } + int HandlerPost_Func2(int a) { ADD_STATE(State_HP_Func2(a, META_RESULT_ORIG_RET(int))); RETURN_META_VALUE(MRES_IGNORED, 0); } + int Handler1_Func22(int a, int b) + { + ADD_STATE(State_H1_Func22(a, b)); + RETURN_META_VALUE(MRES_IGNORED, 0); + } + + int HandlerPost1_Func22(int a, int b) + { + ADD_STATE(State_HP1_Func22(a, b)); + RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, 0, static_cast(&Test::Func2), (1, 2)); + } + + int HandlerPost1A_Func22(int a, int b) + { + ADD_STATE(State_HP1_Func22(a, b)); + RETURN_META_VALUE_NEWPARAMS(MRES_OVERRIDE, 0, static_cast(&Test::Func2), (1, 2)); + } + + int HandlerPost2_Func22(int a, int b) + { + ADD_STATE(State_HP2_Func22(a, b)); + RETURN_META_VALUE(MRES_IGNORED, 0); + } + SH_DECL_HOOK1_void(Test, Func1, SH_NOATTRIB, 0, int); SH_DECL_HOOK1(Test, Func2, SH_NOATTRIB, 0, int, int); + SH_DECL_HOOK2(Test, Func2, SH_NOATTRIB, 1, int, int, int); } bool TestRecall(std::string &error) @@ -135,5 +174,53 @@ bool TestRecall(std::string &error) CHECK_COND(a == 500, "Part 4.1"); + // Func2, with other handler + SH_REMOVE_HOOK_STATICFUNC(Test, Func2, ptr, Handler1_Func2, false); + SH_ADD_HOOK_STATICFUNC(Test, Func2, ptr, Handler2_Func2, false); + + a = ptr->Func2(77); + CHECK_STATES((&g_States, + new State_H2_Func2(77), + new State_Func2(67), + new State_HP_Func2(67, 1000), // 1000 because it's the ORIG_RET + NULL), "Part 4.2"); + + CHECK_COND(a == 1000, "Part 4.2.1"); // Should return 1000 as well. + + // Func22 -> post recalls + + // 1) WITH OVERRIDE + + SH_ADD_HOOK_STATICFUNC(Test, Func2, ptr, Handler1_Func22, false); + SH_ADD_HOOK_STATICFUNC(Test, Func2, ptr, HandlerPost1A_Func22, true); + SH_ADD_HOOK_STATICFUNC(Test, Func2, ptr, HandlerPost2_Func22, true); + + a = ptr->Func2(10, 11); + CHECK_STATES((&g_States, + new State_H1_Func22(10, 11), + new State_Func22(10, 11), + new State_HP1_Func22(10, 11), + new State_HP2_Func22(1, 2), + NULL), "Part 5"); + + CHECK_COND(a == 0, "Part 5.1"); + + // 2) WITH IGNORE + SH_REMOVE_HOOK_STATICFUNC(Test, Func2, ptr, HandlerPost1A_Func22, true); + SH_REMOVE_HOOK_STATICFUNC(Test, Func2, ptr, HandlerPost2_Func22, true); + + SH_ADD_HOOK_STATICFUNC(Test, Func2, ptr, HandlerPost1_Func22, true); + SH_ADD_HOOK_STATICFUNC(Test, Func2, ptr, HandlerPost2_Func22, true); + + a = ptr->Func2(10, 11); + CHECK_STATES((&g_States, + new State_H1_Func22(10, 11), + new State_Func22(10, 11), + new State_HP1_Func22(10, 11), + new State_HP2_Func22(1, 2), + NULL), "Part 5"); + + CHECK_COND(a == 0xDEADFC, "Part 5.1"); + return true; }