diff --git a/extensions/sdktools/extension.cpp b/extensions/sdktools/extension.cpp index 4c528358..c762db5e 100644 --- a/extensions/sdktools/extension.cpp +++ b/extensions/sdktools/extension.cpp @@ -22,6 +22,7 @@ */ #include "extension.h" +#include "vcallbuilder.h" /** * @file extension.cpp @@ -31,19 +32,42 @@ SDKTools g_SdkTools; /**< Global singleton for extension's main interface */ IServerGameEnts *gameents = NULL; IBinTools *g_pBinTools = NULL; -IPlayerManager *g_pPlayers = NULL; +IGameConfig *g_pGameConf = NULL; +HandleType_t g_CallHandle = 0; SMEXT_LINK(&g_SdkTools); +extern sp_nativeinfo_t g_CallNatives[]; + bool SDKTools::SDK_OnLoad(char *error, size_t maxlength, bool late) { - SM_GET_IFACE(PLAYERMANAGER, g_pPlayers); + sharesys->AddDependency(myself, "bintools.ext", true, true); + sharesys->AddNatives(myself, g_CallNatives); - g_pShareSys->AddDependency(myself, "bintools.ext", true, true); + if (!gameconfs->LoadGameConfigFile("sdktools.games", &g_pGameConf, error, maxlength)) + { + return false; + } + + g_CallHandle = handlesys->CreateType("ValveCall", this, 0, NULL, NULL, myself->GetIdentity(), NULL); return true; } +void SDKTools::OnHandleDestroy(HandleType_t type, void *object) +{ + if (type == g_CallHandle) + { + ValveCall *v = (ValveCall *)object; + delete v; + } +} + +void SDKTools::SDK_OnUnload() +{ + gameconfs->CloseGameConfigFile(g_pGameConf); +} + bool SDKTools::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) { GET_V_IFACE_CURRENT(serverFactory, gameents, IServerGameEnts, INTERFACEVERSION_SERVERGAMEENTS); diff --git a/extensions/sdktools/extension.h b/extensions/sdktools/extension.h index 473f9175..48cde279 100644 --- a/extensions/sdktools/extension.h +++ b/extensions/sdktools/extension.h @@ -37,80 +37,27 @@ * @brief Implementation of the SDK Tools extension. * Note: Uncomment one of the pre-defined virtual functions in order to use it. */ -class SDKTools : public SDKExtension +class SDKTools : public SDKExtension, public IHandleTypeDispatch { public: - /** - * @brief This is called after the initial loading sequence has been processed. - * - * @param error Error message buffer. - * @param maxlength Size of error message buffer. - * @param late Whether or not the module was loaded after map load. - * @return True to succeed loading, false to fail. - */ + void OnHandleDestroy(HandleType_t type, void *object); +public: virtual bool SDK_OnLoad(char *error, size_t maxlength, bool late); - - /** - * @brief This is called right before the extension is unloaded. - */ - //virtual void SDK_OnUnload(); - - /** - * @brief This is called once all known extensions have been loaded. - * Note: It is is a good idea to add natives here, if any are provided. - */ + virtual void SDK_OnUnload(); virtual void SDK_OnAllLoaded(); - - /** - * @brief Called when the pause state is changed. - */ //virtual void SDK_OnPauseChange(bool paused); - - /** - * @brief this is called when Core wants to know if your extension is working. - * - * @param error Error message buffer. - * @param maxlength Size of error message buffer. - * @return True if working, false otherwise. - */ virtual bool QueryRunning(char *error, size_t maxlength); public: #if defined SMEXT_CONF_METAMOD - /** - * @brief Called when Metamod is attached, before the extension version is called. - * - * @param error Error buffer. - * @param maxlength Maximum size of error buffer. - * @param late Whether or not Metamod considers this a late load. - * @return True to succeed, false to fail. - */ virtual bool SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late); - - /** - * @brief Called when Metamod is detaching, after the extension version is called. - * NOTE: By default this is blocked unless sent from SourceMod. - * - * @param error Error buffer. - * @param maxlength Maximum size of error buffer. - * @return True to succeed, false to fail. - */ //virtual bool SDK_OnMetamodUnload(char *error, size_t maxlen); - - /** - * @brief Called when Metamod's pause state is changing. - * NOTE: By default this is blocked unless sent from SourceMod. - * - * @param paused Pause state being set. - * @param error Error buffer. - * @param maxlength Maximum size of error buffer. - * @return True to succeed, false to fail. - */ //virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlen); #endif }; extern IServerGameEnts *gameents; extern IBinTools *g_pBinTools; -extern IPlayerManager *g_pPlayers; +extern IGameConfig *g_pGameConf; +extern HandleType_t g_CallHandle; #endif //_INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/extensions/sdktools/msvc8/sdktools.vcproj b/extensions/sdktools/msvc8/sdktools.vcproj index 6c6f8fa7..560fbd36 100644 --- a/extensions/sdktools/msvc8/sdktools.vcproj +++ b/extensions/sdktools/msvc8/sdktools.vcproj @@ -62,6 +62,7 @@ /> + + + + + + + + + + #include #include +#if defined SMEXT_ENABLE_FORWARDSYS #include +#endif //SMEXT_ENABLE_FORWARDSYS +#if defined SMEXT_ENABLE_PLAYERHELPERS +#include +#endif //SMEXT_ENABLE_PlAYERHELPERS +#if defined SMEXT_ENABLE_DBMANAGER +#include +#endif //SMEXT_ENABLE_DBMANAGER +#if defined SMEXT_ENABLE_GAMECONF +#include +#endif +#if defined SMEXT_ENABLE_MEMUTILS +#include +#endif #if defined SMEXT_CONF_METAMOD #include @@ -108,7 +122,7 @@ public: virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlength); #endif -public: // IExtensionInterface +public: //IExtensionInterface virtual bool OnExtensionLoad(IExtension *me, IShareSys *sys, char *error, size_t maxlength, bool late); virtual void OnExtensionUnload(); virtual void OnExtensionsAllLoaded(); @@ -138,9 +152,9 @@ public: // IExtensionInterface /** Returns date string */ virtual const char *GetExtensionDateString(); #if defined SMEXT_CONF_METAMOD -public: // ISmmPlugin +public: //ISmmPlugin /** Called when the extension is attached to Metamod. */ - virtual bool Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool late); + virtual bool Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlength, bool late); /** Returns the author to MM */ virtual const char *GetAuthor(); /** Returns the name to MM */ @@ -158,11 +172,11 @@ public: // ISmmPlugin /** Returns the logtag to MM */ virtual const char *GetLogTag(); /** Called on unload */ - virtual bool Unload(char *error, size_t maxlen); + virtual bool Unload(char *error, size_t maxlength); /** Called on pause */ - virtual bool Pause(char *error, size_t maxlen); + virtual bool Pause(char *error, size_t maxlength); /** Called on unpause */ - virtual bool Unpause(char *error, size_t maxlen); + virtual bool Unpause(char *error, size_t maxlength); private: bool m_SourceMMLoaded; bool m_WeAreUnloaded; @@ -171,12 +185,34 @@ private: }; extern SDKExtension *g_pExtensionIface; +extern IExtension *myself; extern IShareSys *g_pShareSys; -extern IExtension *myself; -extern IHandleSys *g_pHandleSys; +extern IShareSys *sharesys; /* Note: Newer name */ extern ISourceMod *g_pSM; +extern ISourceMod *smutils; /* Note: Newer name */ + +/* Optional interfaces are below */ +#if defined SMEXT_ENABLE_FORWARDSYS extern IForwardManager *g_pForwards; +extern IForwardManager *forwards; /* Note: Newer name */ +#endif //SMEXT_ENABLE_FORWARDSYS +#if defined SMEXT_ENABLE_HANDLESYS +extern IHandleSys *g_pHandleSys; +extern IHandleSys *handlesys; /* Note: Newer name */ +#endif //SMEXT_ENABLE_HANDLESYS +#if defined SMEXT_ENABLE_PLAYERHELPERS +extern IPlayerManager *playerhelpers; +#endif //SMEXT_ENABLE_PLAYERHELPERS +#if defined SMEXT_ENABLE_DBMANAGER +extern IDBManager *dbi; +#endif //SMEXT_ENABLE_DBMANAGER +#if defined SMEXT_ENABLE_GAMECONF +extern IGameConfigManager *gameconfs; +#endif +#if defined SMEXT_ENABLE_MEMUTILS +extern IMemoryUtils *memutils; +#endif #if defined SMEXT_CONF_METAMOD PLUGIN_GLOBALVARS(); @@ -192,7 +228,7 @@ extern IServerGameDLL *gamedll; { \ if (error) \ { \ - snprintf(error, maxlength, "Could not find interface: %s", SMINTERFACE_##prefix##_NAME); \ + snprintf(error, maxlength, "Could not find interface: %s (version: %d)", SMINTERFACE_##prefix##_NAME, SMINTERFACE_##prefix##_VERSION); \ return false; \ } \ } diff --git a/extensions/sdktools/vcallbuilder.cpp b/extensions/sdktools/vcallbuilder.cpp new file mode 100644 index 00000000..f6d5a79c --- /dev/null +++ b/extensions/sdktools/vcallbuilder.cpp @@ -0,0 +1,136 @@ +#include "vcallbuilder.h" +#include "extension.h" + +ValveCall::~ValveCall() +{ + while (!stk.empty()) + { + unsigned char *ptr = stk.front(); + delete [] ptr; + stk.pop(); + } + call->Destroy(); + delete [] retbuf; + delete [] vparams; +} + +unsigned char *ValveCall::stk_get() +{ + unsigned char *ptr; + if (stk.empty()) + { + ptr = new unsigned char[stackSize]; + } else { + ptr = stk.front(); + stk.pop(); + } + return ptr; +} + +void ValveCall::stk_put(unsigned char *ptr) +{ + stk.push(ptr); +} + +ValveCall *CreateValveVCall(unsigned int vtableIdx, + ValveCallType vcalltype, + const ValvePassInfo *retInfo, + const ValvePassInfo *params, + unsigned int numParams) +{ + if (numParams > 32) + { + return NULL; + } + + ValveCall *vc = new ValveCall; + + size_t size = 0; + vc->stackSize = 0; + + /* Get return information - encode only*/ + PassInfo retBuf; + size_t retBufSize = 0; + if (retInfo) + { + if ((size = ValveParamToBinParam(retInfo->vtype, retInfo->type, retInfo->flags, &retBuf)) == 0) + { + delete vc; + return NULL; + } + retBufSize = size; + } + + /* Get parameter info */ + PassInfo paramBuf[32]; + for (unsigned int i=0; istackSize += size; + } + + /* Now we can try creating the call */ + if ((vc->call = g_pBinTools->CreateVCall(vtableIdx, + 0, + 0, + (retInfo ? &retBuf : NULL), + paramBuf, + numParams)) + == NULL) + { + if (!vc->call) + { + delete vc; + return NULL; + } + } + + /* Allocate extra space for thisptr AND ret buffer, even if we don't use it */ + vc->vparams = new ValvePassInfo[numParams + 2]; + + /* We've got the call and everything is encoded. + * It's time to save the valve specific information and helper variables. + */ + if (retInfo) + { + /* Allocate and copy */ + vc->retinfo = &(vc->vparams[numParams]); + *vc->retinfo = *retInfo; + vc->retinfo->offset = 0; + /* Allocate stack space */ + vc->retbuf = new unsigned char[retBufSize]; + } else { + vc->retinfo = NULL; + vc->retbuf = NULL; + } + + /* Save the this info for the dynamic decoder */ + vc->thisinfo = &(vc->vparams[numParams + 1]); + vc->thisinfo->type = PassType_Basic; + if (vcalltype == ValveCall_Entity) + { + vc->thisinfo->vtype = Valve_CBaseEntity; + vc->thisinfo->decflags |= VDECODE_FLAG_ALLOWWORLD; + } else if (vcalltype == ValveCall_Player) { + vc->thisinfo->vtype = Valve_CBasePlayer; + vc->thisinfo->decflags = 0; + } + vc->thisinfo->encflags = 0; + vc->thisinfo->flags = PASSFLAG_BYVAL; + vc->thisinfo->offset = 0; + vc->stackSize += sizeof(void *); + + /* Now, save info about each parameter. */ + for (unsigned int i=0; ivparams[i] = params[i]; + vc->vparams[i].offset = vc->call->GetParamInfo(i)->offset; + } + + return vc; +} diff --git a/extensions/sdktools/vcallbuilder.h b/extensions/sdktools/vcallbuilder.h new file mode 100644 index 00000000..ee728bd4 --- /dev/null +++ b/extensions/sdktools/vcallbuilder.h @@ -0,0 +1,58 @@ +#ifndef _INCLUDE_SOURCEMOD_VALVE_CALLER_H_ +#define _INCLUDE_SOURCEMOD_VALVE_CALLER_H_ + +#include +#include +#include "vdecoder.h" + +using namespace SourceMod; +using namespace SourceHook; + +/** + * @brief Valve pre-defined calling types + */ +enum ValveCallType +{ + ValveCall_Static, /**< Static call */ + ValveCall_Entity, /**< Thiscall (CBaseEntity implicit first parameter) */ + ValveCall_Player, /**< Thiscall (CBasePlayer implicit first parameter) */ +}; + +/** + * @brief Valve parameter info + */ +struct ValvePassInfo +{ + ValveType vtype; /**< IN: Valve type */ + unsigned int decflags; /**< IN: VDECODE_FLAG_* */ + unsigned int encflags; /**< IN: VENCODE_FLAG_* */ + PassType type; /**< IN: Pass information */ + unsigned int flags; /**< IN: Pass flags */ + size_t offset; /**< OUT: stack offset */ +}; + +/** + * @brief Info necessary to call a Valve function + */ +struct ValveCall +{ + ICallWrapper *call; /**< From IBinTools */ + ValvePassInfo *vparams; /**< Valve parameter info */ + ValvePassInfo *retinfo; /**< Return buffer info */ + ValvePassInfo *thisinfo; /**< Thiscall info */ + size_t stackSize; /**< Stack size */ + unsigned char *retbuf; /**< Return buffer */ + CStack stk; /**< Parameter stack */ + + unsigned char *stk_get(); + void stk_put(unsigned char *ptr); + ~ValveCall(); +}; + +ValveCall *CreateValveVCall(unsigned int vtableIdx, + ValveCallType vcalltype, + const ValvePassInfo *retInfo, + const ValvePassInfo *params, + unsigned int numParams); + +#endif //_INCLUDE_SOURCEMOD_VALVE_CALLER_H_ diff --git a/extensions/sdktools/vcaller.cpp b/extensions/sdktools/vcaller.cpp new file mode 100644 index 00000000..70583aae --- /dev/null +++ b/extensions/sdktools/vcaller.cpp @@ -0,0 +1,376 @@ +#include "extension.h" +#include "vcallbuilder.h" + +enum SDKLibrary +{ + SDKLibrary_Server, /**< server.dll/server_i486.so */ + SDKLibrary_Engine, /**< engine.dll/engine_*.so */ +}; + +enum SDKPassMethod +{ + SDKPass_Pointer, /**< Pass as a pointer */ + SDKPass_Plain, /**< Pass as plain data */ + SDKPass_ByValue, /**< Pass an object by value */ + SDKPass_ByRef, /**< Pass an object by reference */ +}; + +int s_vtbl_index = 0; +void *s_call_addr = NULL; +ValveCallType s_vcalltype = ValveCall_Static; +bool s_has_return = false; +ValvePassInfo s_return; +unsigned int s_numparams = 0; +ValvePassInfo s_params[SP_MAX_EXEC_PARAMS]; + +inline void DecodePassMethod(ValveType vtype, SDKPassMethod method, PassType &type, unsigned int &flags) +{ + if (method == SDKPass_Pointer) + { + type = PassType_Basic; + if (vtype == Valve_POD + || vtype == Valve_Float) + { + flags = PASSFLAG_BYREF; + } else { + flags = PASSFLAG_BYVAL; + } + } else if (method == SDKPass_Plain) { + type = PassType_Basic; + flags = PASSFLAG_BYVAL; + } else if (method == SDKPass_ByValue) { + if (vtype == Valve_Vector + || vtype == Valve_QAngle) + { + type = PassType_Object; + } else { + type = PassType_Basic; + } + flags = PASSFLAG_BYVAL; + } else if (method == SDKPass_ByRef) { + if (vtype == Valve_Vector + || vtype == Valve_QAngle) + { + type = PassType_Object; + } else { + type = PassType_Basic; + } + flags = PASSFLAG_BYREF; + } +} + +static cell_t StartPrepSDKCall(IPluginContext *pContext, const cell_t *params) +{ + s_numparams = 0; + s_vtbl_index = 0; + s_call_addr = NULL; + s_has_return = false; + s_vcalltype = (ValveCallType)params[1]; + + return 1; +} + +static cell_t PrepSDKCall_SetVirtual(IPluginContext *pContext, const cell_t *params) +{ + s_vtbl_index = params[1]; + + return 1; +} + +static cell_t PrepSDKCall_SetSignature(IPluginContext *pContext, const cell_t *params) +{ + void *addrInBase = NULL; + if (params[1] == SDKLibrary_Server) + { + addrInBase = g_SMAPI->serverFactory(false); + } else if (params[1] == SDKLibrary_Engine) { + addrInBase = g_SMAPI->engineFactory(false); + } + if (addrInBase == NULL) + { + return 0; + } + + char *sig; + pContext->LocalToString(params[2], &sig); + +#if defined PLATFORM_LINUX + if (sig[0] == '@') + { + Dl_info info; + if (dladdr(addrInBase, &info) == 0) + { + return 0; + } + void *handle = dlopen(info.dli_fname, RTLD_NOW); + if (!handle) + { + return 0; + } + s_call_addr = dlsym(handle, &sig[1]); + dlclose(handle); + + return (s_call_addr != NULL) ? 1 : 0; + } +#endif + + s_call_addr = memutils->FindPattern(addrInBase, sig, params[3]); + + return (s_call_addr != NULL) ? 1 : 0; +} + +static cell_t PrepSDKCall_SetFromConf(IPluginContext *pContext, const cell_t *params) +{ + IGameConfig *conf; + + if (params[1] == BAD_HANDLE) + { + conf = g_pGameConf; + } else { + HandleError err; + if ((conf = gameconfs->ReadHandle(params[1], pContext->GetIdentity(), &err)) == NULL) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", params[1], err); + } + } + + char *key; + pContext->LocalToString(params[3], &key); + + if (params[2] == 0) + { + return conf->GetOffset(key, &s_vtbl_index) ? 1 : 0; + } else if (params[2] == 1) { + bool result = conf->GetMemSig(key, &s_call_addr) ? 1 : 0; + return (result && s_call_addr != NULL) ? 1 : 0; + } + + return 0; +} + +static cell_t PrepSDKCall_SetReturnInfo(IPluginContext *pContext, const cell_t *params) +{ + s_has_return = true; + s_return.vtype = (ValveType)params[1]; + DecodePassMethod(s_return.vtype, (SDKPassMethod)params[2], s_return.type, s_return.flags); + s_return.decflags = params[3]; + s_return.encflags = params[4]; + + return 1; +} + +static cell_t PrepSDKCall_AddParameter(IPluginContext *pContext, const cell_t *params) +{ + if (s_numparams >= SP_MAX_EXEC_PARAMS) + { + return pContext->ThrowNativeError("Parameter limit for SDK calls reached"); + } + + ValvePassInfo *info = &s_params[s_numparams++]; + info->vtype = (ValveType)params[1]; + DecodePassMethod(info->vtype, (SDKPassMethod)params[2], info->type, info->flags); + info->decflags = params[3]; + info->encflags = params[4]; + + return 1; +} + +static cell_t EndPrepSDKCall(IPluginContext *pContext, const cell_t *params) +{ + ValveCall *vc = NULL; + if (s_vtbl_index) + { + vc = CreateValveVCall(s_vtbl_index, s_vcalltype, s_has_return ? &s_return : NULL, s_params, s_numparams); + } + + if (!vc) + { + return BAD_HANDLE; + } + + Handle_t hndl = handlesys->CreateHandle(g_CallHandle, vc, pContext->GetIdentity(), myself->GetIdentity(), NULL); + if (!hndl) + { + delete vc; + } + + return hndl; +} + +static cell_t SDKCall(IPluginContext *pContext, const cell_t *params) +{ + ValveCall *vc; + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + if ((err = handlesys->ReadHandle(params[1], g_CallHandle, &sec, (void **)&vc)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", params[1], err); + } + + unsigned char *ptr = vc->stk_get(); + + unsigned int numparams = (unsigned)params[0]; + unsigned int startparam = 2; + /* Do we need to write a thispointer? */ + + if (vc->thisinfo && + (vc->thisinfo->vtype == Valve_CBaseEntity + || vc->thisinfo->vtype == Valve_CBasePlayer)) + { + if (startparam > numparams) + { + vc->stk_put(ptr); + return pContext->ThrowNativeError("Expected 1 parameter for entity pointer; found none"); + } + if (DecodeValveParam(pContext, + params[startparam], + vc->thisinfo->vtype, + vc->thisinfo->decflags | VDECODE_FLAG_BYREF, + vc->thisinfo->type, + ptr) == Data_Fail) + { + vc->stk_put(ptr); + return 0; + } + startparam++; + } + + /* See if we need to skip any more parameters */ + unsigned int retparam = startparam; + if (vc->retinfo) + { + if (vc->retinfo->vtype == Valve_String) + { + startparam += 2; + } else if (vc->retinfo->vtype == Valve_Vector + || vc->retinfo->vtype == Valve_QAngle) + { + startparam += 1; + } + } + + unsigned int callparams = vc->call->GetParamCount(); + bool will_copyback = false; + for (unsigned int i=0; i numparams) + { + vc->stk_put(ptr); + return pContext->ThrowNativeError("Expected %dth parameter, found none", p); + } + if (DecodeValveParam(pContext, + params[p], + vc->vparams[i].vtype, + vc->vparams[i].decflags | VDECODE_FLAG_BYREF, + vc->vparams[i].type, + ptr + vc->vparams[i].offset) == Data_Fail) + { + vc->stk_put(ptr); + return 0; + } + if (vc->vparams[i].encflags & VENCODE_FLAG_COPYBACK) + { + will_copyback = true; + } + } + + /* Make the actual call */ + vc->call->Execute(ptr, vc->retbuf); + + /* Do we need to copy anything back? */ + if (will_copyback) + { + for (unsigned int i=0; ivparams[i].encflags & VENCODE_FLAG_COPYBACK) + { + if (EncodeValveParam(pContext, + startparam + i, + vc->vparams[i].vtype, + vc->vparams[i].type, + ptr + vc->vparams[i].offset) == Data_Fail) + { + vc->stk_put(ptr); + return 0; + } + } + } + } + + /* Save stack once and for all */ + vc->stk_put(ptr); + + /* Figure out how to decode the return information */ + if (vc->retinfo) + { + if (vc->retinfo->vtype == Valve_String) + { + if (numparams < 3) + { + return pContext->ThrowNativeError("Expected arguments (2,3) for string storage"); + } + cell_t *addr; + size_t written; + pContext->LocalToPhysAddr(params[retparam+1], &addr); + pContext->StringToLocalUTF8(params[retparam], *addr, *(char **)vc->retbuf, &written); + return (cell_t)written; + } else if (vc->retinfo->vtype == Valve_Vector + || vc->retinfo->vtype == Valve_QAngle) + { + if (numparams < 2) + { + return pContext->ThrowNativeError("Expected argument (2) for Float[3] storage"); + } + if (EncodeValveParam(pContext, params[retparam], vc->retinfo->vtype, vc->retinfo->type, vc->retbuf) + == Data_Fail) + { + return 0; + } + } else if (vc->retinfo->vtype == Valve_CBaseEntity + || vc->retinfo->vtype == Valve_CBasePlayer) + { + CBaseEntity *pEntity = *(CBaseEntity **)(vc->retbuf); + if (!pEntity) + { + return -1; + } + edict_t *pEdict = gameents->BaseEntityToEdict(pEntity); + if (!pEdict || pEdict->IsFree()) + { + return -1; + } + return engine->IndexOfEdict(pEdict); + } else if (vc->retinfo->vtype == Valve_Edict) { + edict_t *pEdict = *(edict_t **)(vc->retbuf); + if (!pEdict || pEdict->IsFree()) + { + return -1; + } + return engine->IndexOfEdict(pEdict); + } else { + cell_t *addr = (cell_t *)vc->retbuf; + if (vc->retinfo->flags & PASSFLAG_BYREF) + { + addr = *(cell_t **)addr; + } + return *addr; + } + } + + return 0; +} + +sp_nativeinfo_t g_CallNatives[] = +{ + {"StartPrepSDKCall", StartPrepSDKCall}, + {"PrepSDKCall_SetVirtual", PrepSDKCall_SetVirtual}, + {"PrepSDKCall_SetSignature", PrepSDKCall_SetSignature}, + {"PrepSDKCall_SetFromConf", PrepSDKCall_SetFromConf}, + {"PrepSDKCall_SetReturnInfo", PrepSDKCall_SetReturnInfo}, + {"PrepSDKCall_AddParameter", PrepSDKCall_AddParameter}, + {"EndPrepSDKCall", EndPrepSDKCall}, + {"SDKCall", SDKCall}, + {NULL, NULL}, +}; diff --git a/extensions/sdktools/vdecoder.cpp b/extensions/sdktools/vdecoder.cpp new file mode 100644 index 00000000..4c95b06c --- /dev/null +++ b/extensions/sdktools/vdecoder.cpp @@ -0,0 +1,505 @@ +#include "smsdk_ext.h" +#include "extension.h" +#include "vdecoder.h" + +using namespace SourceMod; +using namespace SourcePawn; + +/** + * For object pointers, the data looks like this instead: + * 4 bytes: POINTER TO NEXT FOUR BYTES + * + bytes: Object internal data + * + * We use the virtual stack as extra fake stack space and create a temp object. + * If these objects had destructors, we'd need to fake destroy toom of course. + * Of course, BinTools only reads the first four bytes and passes the pointer. + */ + +size_t ValveParamToBinParam(ValveType type, + PassType pass, + unsigned int flags, + PassInfo *info) +{ + switch (type) + { + case Valve_Vector: + { + size_t mySize = sizeof(Vector *); + if (pass == PassType_Basic) + { + if (info->flags & PASSFLAG_BYREF) + { + return 0; + } + info->type = PassType_Basic; + info->flags = flags; + info->size = sizeof(Vector *); + mySize += sizeof(Vector); + } else if (pass == PassType_Object) { + info->type = PassType_Object; + info->flags = flags | PASSFLAG_OASSIGNOP | PASSFLAG_OCTOR; + info->size = sizeof(Vector); + } else { + return 0; + } + return mySize; + } + case Valve_QAngle: + { + size_t mySize = sizeof(QAngle *); + if (pass == PassType_Basic) + { + if (info->flags & PASSFLAG_BYREF) + { + return 0; + } + info->type = PassType_Basic; + info->flags = flags; + info->size = sizeof(QAngle *); + mySize += sizeof(QAngle); + } else if (pass == PassType_Object) { + info->type = PassType_Object; + info->flags = flags | PASSFLAG_OASSIGNOP | PASSFLAG_OCTOR; + info->size = sizeof(QAngle); + } else { + return 0; + } + return mySize; + } + case Valve_CBaseEntity: + case Valve_CBasePlayer: + case Valve_POD: + case Valve_Edict: + case Valve_String: + { + if (pass != PassType_Basic + || (info->flags & PASSFLAG_BYREF)) + { + return 0; + } + info->type = PassType_Basic; + info->flags = flags; + info->size = sizeof(void *); + return sizeof(void *); + } + case Valve_Float: + { + info->type = PassType_Float; + info->flags = flags; + info->size = sizeof(float); + return sizeof(float); + } + } + + return 0; +} + +DataStatus EncodeValveParam(IPluginContext *pContext, + cell_t param, + ValveType type, + PassType pass, + const void *buffer) +{ + switch (type) + { + case Valve_Vector: + { + Vector *v = NULL; + + if (pass == PassType_Basic) + { + v = *(Vector **)((unsigned char *)buffer + sizeof(Vector *)); + } else if (pass == PassType_Object) { + v = (Vector *)buffer; + } + + cell_t *addr; + pContext->LocalToPhysAddr(param, &addr); + + addr[0] = v->x; + addr[1] = v->y; + addr[2] = v->z; + + return Data_Okay; + } + case Valve_QAngle: + { + QAngle *q = NULL; + + if (pass == PassType_Basic) + { + q = *(QAngle **)((unsigned char *)buffer + sizeof(QAngle *)); + } else if (pass == PassType_Object) { + q = (QAngle *)buffer; + } + + cell_t *addr; + pContext->LocalToPhysAddr(param, &addr); + + addr[0] = q->x; + addr[1] = q->y; + addr[2] = q->z; + + return Data_Okay; + } + case Valve_CBaseEntity: + case Valve_CBasePlayer: + { + cell_t *addr; + pContext->LocalToPhysAddr(param, &addr); + + CBaseEntity *pEntity = *(CBaseEntity **)buffer; + if (pEntity) + { + edict_t *pEdict = gameents->BaseEntityToEdict(pEntity); + *addr = engine->IndexOfEdict(pEdict); + } else { + *addr = -1; + } + + return Data_Okay; + } + case Valve_Edict: + { + cell_t *addr; + pContext->LocalToPhysAddr(param, &addr); + + edict_t *pEdict = *(edict_t **)buffer; + if (pEdict) + { + *addr = engine->IndexOfEdict(pEdict); + } else { + *addr = -1; + } + + return Data_Okay; + } + case Valve_POD: + case Valve_Float: + { + cell_t *addr; + pContext->LocalToPhysAddr(param, &addr); + + *addr = *(cell_t *)buffer; + + return Data_Okay; + } + } + + return Data_Fail; +} + +DataStatus DecodeValveParam(IPluginContext *pContext, + cell_t param, + ValveType vtype, + unsigned int vflags, + PassType type, + void *buffer) +{ + switch (vtype) + { + case Valve_Vector: + { + cell_t *addr; + int err; + err = pContext->LocalToPhysAddr(param, &addr); + + unsigned char *mem = (unsigned char *)buffer; + if (type == PassType_Basic) + { + /* Store the object in the next N bytes, and store + * a pointer to that object right beforehand. + */ + Vector **realPtr = (Vector **)buffer; + + if (addr == pContext->GetNullRef(SP_NULL_VECTOR)) + { + if (vflags & VDECODE_FLAG_ALLOWNULL) + { + *realPtr = NULL; + return Data_Okay; + } else { + pContext->ThrowNativeError("NULL not allowed"); + return Data_Fail; + } + } else { + mem += sizeof(Vector *); + *realPtr = (Vector *)mem; + } + } + + if (err != SP_ERROR_NONE) + { + pContext->ThrowNativeErrorEx(err, "Could not read plugin data"); + return Data_Fail; + } + + /* Use placement new to initialize the object cleanly + * This has no destructor so we don't need to do + * DestroyValveParam() or something :] + */ + Vector *v = new (mem) Vector( + sp_ctof(addr[0]), + sp_ctof(addr[1]), + sp_ctof(addr[2])); + + return Data_Okay; + } + case Valve_QAngle: + { + cell_t *addr; + int err; + err = pContext->LocalToPhysAddr(param, &addr); + + unsigned char *mem = (unsigned char *)buffer; + if (type == PassType_Basic) + { + /* Store the object in the next N bytes, and store + * a pointer to that object right beforehand. + */ + QAngle **realPtr = (QAngle **)buffer; + + if (addr == pContext->GetNullRef(SP_NULL_VECTOR)) + { + if (!(vflags & VDECODE_FLAG_ALLOWNULL)) + { + pContext->ThrowNativeError("NULL not allowed"); + return Data_Fail; + } else { + *realPtr = NULL; + return Data_Okay; + } + } else { + mem += sizeof(QAngle *); + *realPtr = (QAngle *)mem; + } + } + + if (err != SP_ERROR_NONE) + { + pContext->ThrowNativeErrorEx(err, "Could not read plugin data"); + return Data_Fail; + } + + /* Use placement new to initialize the object cleanly + * This has no destructor so we don't need to do + * DestroyValveParam() or something :] + */ + QAngle *v = new (mem) QAngle( + sp_ctof(addr[0]), + sp_ctof(addr[1]), + sp_ctof(addr[2])); + + return Data_Okay; + } + case Valve_CBasePlayer: + { + edict_t *pEdict; + if (vflags & VDECODE_FLAG_BYREF) + { + cell_t *addr; + pContext->LocalToPhysAddr(param, &addr); + param = *addr; + } + if (param >= 1 && param <= playerhelpers->GetMaxClients()) + { + IGamePlayer *player = playerhelpers->GetGamePlayer(param); + if ((vflags & VDECODE_FLAG_ALLOWNOTINGAME) + && !player->IsConnected()) + { + pContext->ThrowNativeError("Client %d is not connected", param); + return Data_Fail; + } else if (!player->IsInGame()) { + pContext->ThrowNativeError("Client %d is not in game", param); + return Data_Fail; + } + pEdict = player->GetEdict(); + } else if (param == -1) { + if (vflags & VDECODE_FLAG_ALLOWNULL) + { + pEdict = NULL; + } else { + pContext->ThrowNativeError("NULL not allowed"); + return Data_Fail; + } + } else if (param == 0) { + if (vflags & VDECODE_FLAG_ALLOWWORLD) + { + pEdict = engine->PEntityOfEntIndex(0); + } else { + pContext->ThrowNativeError("World not allowed"); + return Data_Fail; + } + } else { + pContext->ThrowNativeError("Entity index %d is not a valid client", param); + return Data_Fail; + } + CBaseEntity *pEntity; + if (pEdict) + { + IServerUnknown *pUnknown = pEdict->GetUnknown(); + if (!pUnknown) + { + pContext->ThrowNativeError("Entity %d is a not an IServerUnknown"); + return Data_Fail; + } + pEntity = pUnknown->GetBaseEntity(); + if (!pEntity) + { + pContext->ThrowNativeError("Entity %d is not a CBaseEntity"); + return Data_Fail; + } + } else { + pEdict = NULL; + } + + CBaseEntity **ebuf = (CBaseEntity **)buffer; + *ebuf = pEntity; + + return Data_Okay; + } + case Valve_CBaseEntity: + { + edict_t *pEdict; + if (vflags & VDECODE_FLAG_BYREF) + { + cell_t *addr; + pContext->LocalToPhysAddr(param, &addr); + param = *addr; + } + if (param >= 1 && param <= playerhelpers->GetMaxClients()) + { + IGamePlayer *player = playerhelpers->GetGamePlayer(param); + if ((vflags & VDECODE_FLAG_ALLOWNOTINGAME) + && !player->IsConnected()) + { + pContext->ThrowNativeError("Client %d is not connected", param); + return Data_Fail; + } else if (!player->IsInGame()) { + pContext->ThrowNativeError("Client %d is not in game", param); + return Data_Fail; + } + pEdict = player->GetEdict(); + } else if (param == -1) { + if (vflags & VDECODE_FLAG_ALLOWNULL) + { + pEdict = NULL; + } else { + pContext->ThrowNativeError("NULL not allowed"); + return Data_Fail; + } + } else if (param == 0) { + if (vflags & VDECODE_FLAG_ALLOWWORLD) + { + pEdict = engine->PEntityOfEntIndex(0); + } else { + pContext->ThrowNativeError("World not allowed"); + return Data_Fail; + } + } else { + pEdict = engine->PEntityOfEntIndex(param); + if (!pEdict || pEdict->IsFree()) + { + pContext->ThrowNativeError("Entity %d is not valid or is freed", param); + return Data_Fail; + } + } + CBaseEntity *pEntity; + if (pEdict) + { + IServerUnknown *pUnknown = pEdict->GetUnknown(); + if (!pUnknown) + { + pContext->ThrowNativeError("Entity %d is a not an IServerUnknown"); + return Data_Fail; + } + pEntity = pUnknown->GetBaseEntity(); + if (!pEntity) + { + pContext->ThrowNativeError("Entity %d is not a CBaseEntity"); + return Data_Fail; + } + } else { + pEdict = NULL; + } + + CBaseEntity **ebuf = (CBaseEntity **)buffer; + *ebuf = pEntity; + + return Data_Okay; + } + case Valve_Edict: + { + edict_t *pEdict; + if (vflags & VDECODE_FLAG_BYREF) + { + cell_t *addr; + pContext->LocalToPhysAddr(param, &addr); + param = *addr; + } + if (param >= 1 && param <= playerhelpers->GetMaxClients()) + { + IGamePlayer *player = playerhelpers->GetGamePlayer(param); + if ((vflags & VDECODE_FLAG_ALLOWNOTINGAME) + && !player->IsConnected()) + { + pContext->ThrowNativeError("Client %d is not connected", param); + return Data_Fail; + } else if (!player->IsInGame()) { + pContext->ThrowNativeError("Client %d is not in game", param); + return Data_Fail; + } + pEdict = player->GetEdict(); + } else if (param == -1) { + if (vflags & VDECODE_FLAG_ALLOWNULL) + { + pEdict = NULL; + } else { + pContext->ThrowNativeError("NULL not allowed"); + return Data_Fail; + } + } else if (param == 0) { + if (vflags & VDECODE_FLAG_ALLOWWORLD) + { + pEdict = engine->PEntityOfEntIndex(0); + } else { + pContext->ThrowNativeError("World not allowed"); + return Data_Fail; + } + } else { + pEdict = engine->PEntityOfEntIndex(param); + if (!pEdict || pEdict->IsFree()) + { + pContext->ThrowNativeError("Entity %d is not valid or is freed", param); + return Data_Fail; + } + } + + edict_t **ebuf = (edict_t **)buffer; + *ebuf = pEdict; + + return Data_Okay; + } + case Valve_POD: + case Valve_Float: + { + if (vflags & VDECODE_FLAG_BYREF) + { + cell_t *addr; + pContext->LocalToPhysAddr(param, &addr); + param = *addr; + } + *(cell_t *)buffer = param; + return Data_Okay; + } + case Valve_String: + { + char *addr; + pContext->LocalToString(param, &addr); + *(char **)buffer = addr; + return Data_Okay; + } + } + + return Data_Fail; +} diff --git a/extensions/sdktools/vdecoder.h b/extensions/sdktools/vdecoder.h new file mode 100644 index 00000000..05355f18 --- /dev/null +++ b/extensions/sdktools/vdecoder.h @@ -0,0 +1,93 @@ +#ifndef _INCLUDE_SOURCEMOD_VDECODER_H_ +#define _INCLUDE_SOURCEMOD_VDECODER_H_ + +#include +#include +#include + +using namespace SourceMod; +using namespace SourcePawn; + +/** + * @brief Encapsulates types from the SDK + */ +enum ValveType +{ + Valve_CBaseEntity, /**< CBaseEntity */ + Valve_CBasePlayer, /**< CBasePlayer (disallow normal ents) */ + Valve_Vector, /**< Vector */ + Valve_QAngle, /**< QAngle */ + Valve_POD, /**< Plain old data */ + Valve_Float, /**< Float */ + Valve_Edict, /**< Edict */ + Valve_String, /**< String */ +}; + +enum DataStatus +{ + Data_Fail = 0, + Data_Okay = 1, +}; + +#define VDECODE_FLAG_ALLOWNULL (1<<0) /**< Allow NULL for pointers */ +#define VDECODE_FLAG_ALLOWNOTINGAME (1<<1) /**< Allow players not in game */ +#define VDECODE_FLAG_ALLOWWORLD (1<<2) /**< Allow World entity */ +#define VDECODE_FLAG_BYREF (1<<3) /**< Floats/ints by reference */ + +#define VENCODE_FLAG_COPYBACK (1<<0) /**< Copy back data */ + +/** + * @brief Converts a valve parameter to a bintools parameter. + * + * @param type Valve type. + * @param pass Either basic or object. + * @param flags Either BYVAL or BYREF. + * @param info Buffer to store param info in. + * @return Number of bytes this will use in the virtual stack, + * or 0 if conversion was impossible. + */ +size_t ValveParamToBinParam(ValveType type, + PassType pass, + unsigned int flags, + PassInfo *info); + +/** + * @brief Decodes data from a plugin to native data. + * + * Note: If you're going to return false, make sure to + * throw an error. + * + * @param pContext Plugin context. + * @param param Parameter value from params array. + * @param type Valve type. + * @param pass Pass info from bin tools. + * @param buffer Buffer space in the virutal stack. + * @return True on success, false otherwise. + */ +DataStatus DecodeValveParam(IPluginContext *pContext, + cell_t param, + ValveType type, + unsigned int vflags, + PassType pass, + void *buffer); + +/** + * @brief Encodes native data back into a plugin. + * + * Note: If you're going to return false, make sure to + * throw an error. + * + * @param pContext Plugin context. + * @param param Parameter value from params array. + * @param type Valve type. + * @param pass Pass info from bin tools. + * @param buffer Buffer space in the virutal stack. + * @return True on success, false otherwise. + */ +DataStatus EncodeValveParam(IPluginContext *pContext, + cell_t param, + ValveType type, + PassType pass, + const void *buffer); + +#endif //_INCLUDE_SOURCEMOD_VDECODER_H_