b18e3284e1
The custom return value was lost when calling the original function. Save and restore our own return value, if we're about to call the original function.
587 lines
16 KiB
C++
587 lines
16 KiB
C++
#include "dynhooks_sourcepawn.h"
|
|
#include "util.h"
|
|
#include <am-autoptr.h>
|
|
|
|
#include "conventions/x86MsCdecl.h"
|
|
#include "conventions/x86MsThiscall.h"
|
|
#include "conventions/x86MsStdcall.h"
|
|
#include "conventions/x86GccCdecl.h"
|
|
#include "conventions/x86GccThiscall.h"
|
|
|
|
#ifdef WIN32
|
|
typedef x86MsCdecl x86DetourCdecl;
|
|
typedef x86MsThiscall x86DetourThisCall;
|
|
typedef x86MsStdcall x86DetourStdCall;
|
|
#else
|
|
typedef x86GccCdecl x86DetourCdecl;
|
|
typedef x86GccThiscall x86DetourThisCall;
|
|
// Uhm
|
|
typedef x86MsStdcall x86DetourStdCall;
|
|
#endif
|
|
|
|
//ke::Vector<CHook *> g_pDetours;
|
|
//CallbackMap g_pPluginPreDetours;
|
|
//CallbackMap g_pPluginPostDetours;
|
|
DetourMap g_pPreDetours;
|
|
DetourMap g_pPostDetours;
|
|
|
|
void UnhookFunction(HookType_t hookType, CHook *pDetour)
|
|
{
|
|
CHookManager *pDetourManager = GetHookManager();
|
|
pDetour->RemoveCallback(hookType, (HookHandlerFn *)(void *)&HandleDetour);
|
|
if (!pDetour->AreCallbacksRegistered())
|
|
pDetourManager->UnhookFunction(pDetour->m_pFunc);
|
|
}
|
|
|
|
bool AddDetourPluginHook(HookType_t hookType, CHook *pDetour, HookSetup *setup, IPluginFunction *pCallback)
|
|
{
|
|
DetourMap *map;
|
|
if (hookType == HOOKTYPE_PRE)
|
|
map = &g_pPreDetours;
|
|
else
|
|
map = &g_pPostDetours;
|
|
|
|
// See if we already have this detour in our list.
|
|
PluginCallbackList *wrappers;
|
|
DetourMap::Insert f = map->findForAdd(pDetour);
|
|
if (f.found())
|
|
{
|
|
wrappers = f->value;
|
|
}
|
|
else
|
|
{
|
|
// Create a vector to store all the plugin callbacks in.
|
|
wrappers = new PluginCallbackList;
|
|
if (!map->add(f, pDetour, wrappers))
|
|
{
|
|
delete wrappers;
|
|
UnhookFunction(hookType, pDetour);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CDynamicHooksSourcePawn *pWrapper = new CDynamicHooksSourcePawn(setup, pDetour, pCallback, hookType == HOOKTYPE_POST);
|
|
if (!wrappers->append(pWrapper))
|
|
{
|
|
if (wrappers->empty())
|
|
{
|
|
delete wrappers;
|
|
UnhookFunction(hookType, pDetour);
|
|
map->remove(f);
|
|
}
|
|
delete pWrapper;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RemoveDetourPluginHook(HookType_t hookType, CHook *pDetour, IPluginFunction *pCallback)
|
|
{
|
|
DetourMap *map;
|
|
if (hookType == HOOKTYPE_PRE)
|
|
map = &g_pPreDetours;
|
|
else
|
|
map = &g_pPostDetours;
|
|
|
|
DetourMap::Result res = map->find(pDetour);
|
|
if (!res.found())
|
|
return false;
|
|
|
|
// Remove the plugin's callback
|
|
bool bRemoved = false;
|
|
PluginCallbackList *wrappers = res->value;
|
|
for (int i = wrappers->length()-1; i >= 0 ; i--)
|
|
{
|
|
CDynamicHooksSourcePawn *pWrapper = wrappers->at(i);
|
|
if (pWrapper->plugin_callback == pCallback)
|
|
{
|
|
bRemoved = true;
|
|
delete pWrapper;
|
|
wrappers->remove(i--);
|
|
}
|
|
}
|
|
|
|
// No more plugin hooks on this callback. Free our structures.
|
|
if (wrappers->empty())
|
|
{
|
|
delete wrappers;
|
|
UnhookFunction(hookType, pDetour);
|
|
map->remove(res);
|
|
}
|
|
|
|
return bRemoved;
|
|
}
|
|
|
|
void RemoveAllCallbacksForContext(HookType_t hookType, DetourMap *map, IPluginContext *pContext)
|
|
{
|
|
PluginCallbackList *wrappers;
|
|
CDynamicHooksSourcePawn *pWrapper;
|
|
DetourMap::iterator it = map->iter();
|
|
// Run through all active detours we added.
|
|
for (; !it.empty(); it.next())
|
|
{
|
|
wrappers = it->value;
|
|
// See if there are callbacks of this plugin context registered
|
|
// and remove them.
|
|
for (int i = wrappers->length() - 1; i >= 0; i--)
|
|
{
|
|
pWrapper = wrappers->at(i);
|
|
if (pWrapper->plugin_callback->GetParentContext() != pContext)
|
|
continue;
|
|
|
|
delete pWrapper;
|
|
wrappers->remove(i--);
|
|
}
|
|
|
|
// No plugin interested in this hook anymore. unhook.
|
|
if (wrappers->empty())
|
|
{
|
|
delete wrappers;
|
|
UnhookFunction(hookType, it->key);
|
|
it.erase();
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoveAllCallbacksForContext(IPluginContext *pContext)
|
|
{
|
|
RemoveAllCallbacksForContext(HOOKTYPE_PRE, &g_pPreDetours, pContext);
|
|
RemoveAllCallbacksForContext(HOOKTYPE_POST, &g_pPostDetours, pContext);
|
|
}
|
|
|
|
void CleanupDetours(HookType_t hookType, DetourMap *map)
|
|
{
|
|
PluginCallbackList *wrappers;
|
|
CDynamicHooksSourcePawn *pWrapper;
|
|
DetourMap::iterator it = map->iter();
|
|
// Run through all active detours we added.
|
|
for (; !it.empty(); it.next())
|
|
{
|
|
wrappers = it->value;
|
|
// See if there are callbacks of this plugin context registered
|
|
// and remove them.
|
|
for (int i = wrappers->length() - 1; i >= 0; i--)
|
|
{
|
|
pWrapper = wrappers->at(i);
|
|
delete pWrapper;
|
|
}
|
|
|
|
// Unhook the function
|
|
delete wrappers;
|
|
UnhookFunction(hookType, it->key);
|
|
}
|
|
map->clear();
|
|
}
|
|
|
|
void CleanupDetours()
|
|
{
|
|
CleanupDetours(HOOKTYPE_PRE, &g_pPreDetours);
|
|
CleanupDetours(HOOKTYPE_POST, &g_pPostDetours);
|
|
}
|
|
|
|
ICallingConvention *ConstructCallingConvention(HookSetup *setup)
|
|
{
|
|
ke::Vector<DataTypeSized_t> vecArgTypes;
|
|
for (size_t i = 0; i < setup->params.size(); i++)
|
|
{
|
|
ParamInfo &info = setup->params[i];
|
|
DataTypeSized_t type;
|
|
type.type = DynamicHooks_ConvertParamTypeFrom(info.type);
|
|
type.size = info.size;
|
|
vecArgTypes.append(type);
|
|
}
|
|
|
|
DataTypeSized_t returnType;
|
|
returnType.type = DynamicHooks_ConvertReturnTypeFrom(setup->returnType);
|
|
returnType.size = 0;
|
|
|
|
ICallingConvention *pCallConv = nullptr;
|
|
switch (setup->callConv)
|
|
{
|
|
case CallConv_CDECL:
|
|
pCallConv = new x86DetourCdecl(vecArgTypes, returnType);
|
|
break;
|
|
case CallConv_THISCALL:
|
|
pCallConv = new x86DetourThisCall(vecArgTypes, returnType);
|
|
break;
|
|
case CallConv_STDCALL:
|
|
pCallConv = new x86DetourStdCall(vecArgTypes, returnType);
|
|
break;
|
|
}
|
|
|
|
return pCallConv;
|
|
}
|
|
|
|
ReturnAction_t HandleDetour(HookType_t hookType, CHook* pDetour)
|
|
{
|
|
DetourMap *map;
|
|
if (hookType == HOOKTYPE_PRE)
|
|
map = &g_pPreDetours;
|
|
else
|
|
map = &g_pPostDetours;
|
|
|
|
// Find the callback list for this detour.
|
|
DetourMap::Result r = map->find(pDetour);
|
|
if (!r.found())
|
|
return ReturnAction_Ignored;
|
|
|
|
// List of all callbacks.
|
|
PluginCallbackList *wrappers = r->value;
|
|
|
|
HookReturnStruct *returnStruct = NULL;
|
|
Handle_t rHndl = BAD_HANDLE;
|
|
|
|
HookParamsStruct *paramStruct = NULL;
|
|
Handle_t pHndl = BAD_HANDLE;
|
|
|
|
int argNum = pDetour->m_pCallingConvention->m_vecArgTypes.length();
|
|
ReturnAction_t finalRet = ReturnAction_Ignored;
|
|
ke::AutoPtr<void> finalRetBuf(new uint8_t[pDetour->m_pCallingConvention->m_returnType.size]);
|
|
|
|
// Call all the plugin functions..
|
|
for (size_t i = 0; i < wrappers->length(); i++)
|
|
{
|
|
CDynamicHooksSourcePawn *pWrapper = wrappers->at(i);
|
|
IPluginFunction *pCallback = pWrapper->plugin_callback;
|
|
ReturnAction_t tempRet = ReturnAction_Ignored;
|
|
ke::AutoPtr<void> tempRetBuf(new uint8_t[pDetour->m_pCallingConvention->m_returnType.size]);
|
|
|
|
// Find the this pointer.
|
|
if (pWrapper->callConv == CallConv_THISCALL)
|
|
{
|
|
void *thisPtr = pDetour->GetArgument<void *>(0);
|
|
cell_t thisAddr = GetThisPtr(thisPtr, pWrapper->thisType);
|
|
pCallback->PushCell(thisAddr);
|
|
}
|
|
|
|
if (pWrapper->returnType != ReturnType_Void)
|
|
{
|
|
returnStruct = pWrapper->GetReturnStruct();
|
|
HandleError err;
|
|
rHndl = handlesys->CreateHandle(g_HookReturnHandle, returnStruct, pCallback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity(), &err);
|
|
if (!rHndl)
|
|
{
|
|
pCallback->Cancel();
|
|
pCallback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(pCallback, "Error creating ReturnHandle in preparation to call hook callback. (error %d)", err);
|
|
|
|
if (returnStruct)
|
|
delete returnStruct;
|
|
|
|
// Don't call more callbacks. They will probably fail too.
|
|
break;
|
|
}
|
|
pCallback->PushCell(rHndl);
|
|
}
|
|
|
|
if (argNum > 0)
|
|
{
|
|
paramStruct = pWrapper->GetParamStruct();
|
|
HandleError err;
|
|
pHndl = handlesys->CreateHandle(g_HookParamsHandle, paramStruct, pCallback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity(), &err);
|
|
if (!pHndl)
|
|
{
|
|
pCallback->Cancel();
|
|
pCallback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(pCallback, "Error creating ThisHandle in preparation to call hook callback. (error %d)", err);
|
|
|
|
// Don't leak our own handles here!
|
|
if (rHndl)
|
|
{
|
|
HandleSecurity sec(pCallback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity());
|
|
handlesys->FreeHandle(rHndl, &sec);
|
|
rHndl = BAD_HANDLE;
|
|
}
|
|
|
|
if (paramStruct)
|
|
delete paramStruct;
|
|
|
|
// Don't call more callbacks. They will probably fail too.
|
|
break;
|
|
}
|
|
pCallback->PushCell(pHndl);
|
|
}
|
|
|
|
cell_t result = (cell_t)MRES_Ignored;
|
|
pCallback->Execute(&result);
|
|
|
|
switch ((MRESReturn)result)
|
|
{
|
|
case MRES_Handled:
|
|
tempRet = ReturnAction_Handled;
|
|
break;
|
|
case MRES_ChangedHandled:
|
|
tempRet = ReturnAction_Handled;
|
|
pWrapper->UpdateParamsFromStruct(paramStruct);
|
|
break;
|
|
case MRES_ChangedOverride:
|
|
if (pWrapper->returnType != ReturnType_Void)
|
|
{
|
|
if (returnStruct->isChanged)
|
|
{
|
|
if (pWrapper->returnType == ReturnType_String || pWrapper->returnType == ReturnType_Int || pWrapper->returnType == ReturnType_Bool)
|
|
{
|
|
tempRetBuf = *(void **)returnStruct->newResult;
|
|
}
|
|
else if (pWrapper->returnType == ReturnType_Float)
|
|
{
|
|
*(float *)tempRetBuf.get() = *(float *)returnStruct->newResult;
|
|
}
|
|
else
|
|
{
|
|
tempRetBuf = returnStruct->newResult;
|
|
}
|
|
}
|
|
else //Throw an error if no override was set
|
|
{
|
|
tempRet = ReturnAction_Ignored;
|
|
pCallback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(pCallback, "Tried to override return value without return value being set");
|
|
break;
|
|
}
|
|
}
|
|
tempRet = ReturnAction_Override;
|
|
pWrapper->UpdateParamsFromStruct(paramStruct);
|
|
break;
|
|
case MRES_Override:
|
|
if (pWrapper->returnType != ReturnType_Void)
|
|
{
|
|
if (returnStruct->isChanged)
|
|
{
|
|
tempRet = ReturnAction_Override;
|
|
if (pWrapper->returnType == ReturnType_String || pWrapper->returnType == ReturnType_Int || pWrapper->returnType == ReturnType_Bool)
|
|
{
|
|
tempRetBuf = *(void **)returnStruct->newResult;
|
|
}
|
|
else if (pWrapper->returnType == ReturnType_Float)
|
|
{
|
|
*(float *)tempRetBuf.get() = *(float *)returnStruct->newResult;
|
|
}
|
|
else
|
|
{
|
|
tempRetBuf = returnStruct->newResult;
|
|
}
|
|
}
|
|
else //Throw an error if no override was set
|
|
{
|
|
tempRet = ReturnAction_Ignored;
|
|
pCallback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(pCallback, "Tried to override return value without return value being set");
|
|
}
|
|
}
|
|
break;
|
|
case MRES_Supercede:
|
|
if (pWrapper->returnType != ReturnType_Void)
|
|
{
|
|
if (returnStruct->isChanged)
|
|
{
|
|
tempRet = ReturnAction_Supercede;
|
|
if (pWrapper->returnType == ReturnType_String || pWrapper->returnType == ReturnType_Int || pWrapper->returnType == ReturnType_Bool)
|
|
{
|
|
tempRetBuf = *(void **)returnStruct->newResult;
|
|
}
|
|
else if (pWrapper->returnType == ReturnType_Float)
|
|
{
|
|
*(float *)tempRetBuf.get() = *(float *)returnStruct->newResult;
|
|
}
|
|
else
|
|
{
|
|
tempRetBuf = returnStruct->newResult;
|
|
}
|
|
}
|
|
else //Throw an error if no override was set
|
|
{
|
|
tempRet = ReturnAction_Ignored;
|
|
pCallback->GetParentRuntime()->GetDefaultContext()->BlamePluginError(pCallback, "Tried to override return value without return value being set");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tempRet = ReturnAction_Supercede;
|
|
}
|
|
break;
|
|
default:
|
|
tempRet = ReturnAction_Ignored;
|
|
break;
|
|
}
|
|
|
|
// Prioritize the actions.
|
|
if (finalRet <= tempRet) {
|
|
|
|
// ------------------------------------
|
|
// Copy the action and return value.
|
|
// ------------------------------------
|
|
finalRet = tempRet;
|
|
memcpy(*finalRetBuf, *tempRetBuf, pDetour->m_pCallingConvention->m_returnType.size);
|
|
}
|
|
|
|
// Free the handles again.
|
|
HandleSecurity sec(pCallback->GetParentRuntime()->GetDefaultContext()->GetIdentity(), myself->GetIdentity());
|
|
|
|
if (returnStruct)
|
|
{
|
|
handlesys->FreeHandle(rHndl, &sec);
|
|
}
|
|
if (paramStruct)
|
|
{
|
|
handlesys->FreeHandle(pHndl, &sec);
|
|
}
|
|
}
|
|
|
|
// If we want to use our own return value, write it back.
|
|
if (finalRet >= ReturnAction_Override)
|
|
{
|
|
void* pPtr = pDetour->m_pCallingConvention->GetReturnPtr(pDetour->m_pRegisters);
|
|
memcpy(pPtr, *finalRetBuf, pDetour->m_pCallingConvention->m_returnType.size);
|
|
pDetour->m_pCallingConvention->ReturnPtrChanged(pDetour->m_pRegisters, pPtr);
|
|
}
|
|
|
|
return finalRet;
|
|
}
|
|
|
|
CDynamicHooksSourcePawn::CDynamicHooksSourcePawn(HookSetup *setup, CHook *pDetour, IPluginFunction *pCallback, bool post)
|
|
{
|
|
this->params = setup->params;
|
|
this->offset = -1;
|
|
this->returnFlag = setup->returnFlag;
|
|
this->returnType = setup->returnType;
|
|
this->post = post;
|
|
this->plugin_callback = pCallback;
|
|
this->entity = -1;
|
|
this->thisType = setup->thisType;
|
|
this->hookType = setup->hookType;
|
|
this->m_pDetour = pDetour;
|
|
this->callConv = setup->callConv;
|
|
}
|
|
|
|
HookReturnStruct *CDynamicHooksSourcePawn::GetReturnStruct()
|
|
{
|
|
HookReturnStruct *res = new HookReturnStruct();
|
|
res->isChanged = false;
|
|
res->type = this->returnType;
|
|
res->orgResult = NULL;
|
|
res->newResult = NULL;
|
|
|
|
if (this->post)
|
|
{
|
|
switch (this->returnType)
|
|
{
|
|
case ReturnType_String:
|
|
res->orgResult = malloc(sizeof(string_t));
|
|
res->newResult = malloc(sizeof(string_t));
|
|
*(string_t *)res->orgResult = m_pDetour->GetReturnValue<string_t>();
|
|
break;
|
|
case ReturnType_Int:
|
|
res->orgResult = malloc(sizeof(int));
|
|
res->newResult = malloc(sizeof(int));
|
|
*(int *)res->orgResult = m_pDetour->GetReturnValue<int>();
|
|
break;
|
|
case ReturnType_Bool:
|
|
res->orgResult = malloc(sizeof(bool));
|
|
res->newResult = malloc(sizeof(bool));
|
|
*(bool *)res->orgResult = m_pDetour->GetReturnValue<bool>();
|
|
break;
|
|
case ReturnType_Float:
|
|
res->orgResult = malloc(sizeof(float));
|
|
res->newResult = malloc(sizeof(float));
|
|
*(float *)res->orgResult = m_pDetour->GetReturnValue<float>();
|
|
break;
|
|
case ReturnType_Vector:
|
|
{
|
|
res->orgResult = malloc(sizeof(SDKVector));
|
|
res->newResult = malloc(sizeof(SDKVector));
|
|
SDKVector vec = m_pDetour->GetReturnValue<SDKVector>();
|
|
*(SDKVector *)res->orgResult = vec;
|
|
break;
|
|
}
|
|
default:
|
|
res->orgResult = m_pDetour->GetReturnValue<void *>();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (this->returnType)
|
|
{
|
|
case ReturnType_String:
|
|
res->orgResult = malloc(sizeof(string_t));
|
|
res->newResult = malloc(sizeof(string_t));
|
|
*(string_t *)res->orgResult = NULL_STRING;
|
|
break;
|
|
case ReturnType_Vector:
|
|
res->orgResult = malloc(sizeof(SDKVector));
|
|
res->newResult = malloc(sizeof(SDKVector));
|
|
*(SDKVector *)res->orgResult = SDKVector();
|
|
break;
|
|
case ReturnType_Int:
|
|
res->orgResult = malloc(sizeof(int));
|
|
res->newResult = malloc(sizeof(int));
|
|
*(int *)res->orgResult = 0;
|
|
break;
|
|
case ReturnType_Bool:
|
|
res->orgResult = malloc(sizeof(bool));
|
|
res->newResult = malloc(sizeof(bool));
|
|
*(bool *)res->orgResult = false;
|
|
break;
|
|
case ReturnType_Float:
|
|
res->orgResult = malloc(sizeof(float));
|
|
res->newResult = malloc(sizeof(float));
|
|
*(float *)res->orgResult = 0.0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
HookParamsStruct *CDynamicHooksSourcePawn::GetParamStruct()
|
|
{
|
|
HookParamsStruct *params = new HookParamsStruct();
|
|
params->dg = this;
|
|
|
|
size_t paramsSize = this->m_pDetour->m_pCallingConvention->GetArgStackSize();
|
|
ke::Vector<DataTypeSized_t> &argTypes = m_pDetour->m_pCallingConvention->m_vecArgTypes;
|
|
int numArgs = argTypes.length();
|
|
|
|
params->orgParams = (void **)malloc(paramsSize);
|
|
params->newParams = (void **)malloc(paramsSize);
|
|
params->isChanged = (bool *)malloc(numArgs * sizeof(bool));
|
|
|
|
size_t offset = 0;
|
|
for (int i = 0; i < numArgs; i++)
|
|
{
|
|
void *pArgPtr = m_pDetour->m_pCallingConvention->GetStackArgumentPtr(m_pDetour->m_pRegisters);
|
|
memcpy(params->orgParams, pArgPtr, paramsSize);
|
|
|
|
*(void **)((intptr_t)params->newParams + offset) = NULL;
|
|
params->isChanged[i] = false;
|
|
|
|
offset += argTypes[i].size;
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
void CDynamicHooksSourcePawn::UpdateParamsFromStruct(HookParamsStruct *params)
|
|
{
|
|
// Function had no params to update now.
|
|
if (!params)
|
|
return;
|
|
|
|
ke::Vector<DataTypeSized_t> &argTypes = m_pDetour->m_pCallingConvention->m_vecArgTypes;
|
|
int numArgs = argTypes.length();
|
|
|
|
int firstArg = 0;
|
|
if (callConv == CallConv_THISCALL)
|
|
firstArg = 1;
|
|
|
|
size_t offset = 0;
|
|
for (int i = 0; i < numArgs; i++)
|
|
{
|
|
int size = argTypes[i].size;;
|
|
if (params->isChanged[i])
|
|
{
|
|
void *paramAddr = (void *)((intptr_t)params->newParams + offset);
|
|
void *stackAddr = m_pDetour->m_pCallingConvention->GetArgumentPtr(i + firstArg, m_pDetour->m_pRegisters);
|
|
memcpy(stackAddr, paramAddr, size);
|
|
}
|
|
offset += size;
|
|
}
|
|
} |