From 18865c44c817b4d721e98d479429fb6638630ca2 Mon Sep 17 00:00:00 2001 From: Downtown1 Date: Mon, 11 Jan 2010 22:46:44 -0800 Subject: [PATCH] Added a new ValveCallType that allows for arbitrary |this| parameters, as well as associated features in gamedata and for reading/writing memory (bug 3520, r=dvander, sr=fyren). --- core/GameConfigs.cpp | 131 ++++++++++++++++++++++++++++++++ core/GameConfigs.h | 19 +++++ core/smn_core.cpp | 73 ++++++++++++++++++ core/smn_gameconfigs.cpp | 30 ++++++++ extensions/sdktools/vcaller.cpp | 21 ++++- extensions/sdktools/vdecoder.h | 1 + plugins/include/sdktools.inc | 1 + plugins/include/sourcemod.inc | 45 +++++++++++ public/IGameConfigs.h | 11 ++- 9 files changed, 330 insertions(+), 2 deletions(-) diff --git a/core/GameConfigs.cpp b/core/GameConfigs.cpp index d8569066..1dd148f8 100644 --- a/core/GameConfigs.cpp +++ b/core/GameConfigs.cpp @@ -63,6 +63,9 @@ static char g_GameName[256] = {'$', '\0'}; #define PSTATE_GAMEDEFS_CRC 9 #define PSTATE_GAMEDEFS_CRC_BINARY 10 #define PSTATE_GAMEDEFS_CUSTOM 11 +#define PSTATE_GAMEDEFS_ADDRESSES 12 +#define PSTATE_GAMEDEFS_ADDRESSES_ADDRESS 13 +#define PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ 14 #if defined PLATFORM_WINDOWS #define PLATFORM_NAME "windows" @@ -132,6 +135,7 @@ CGameConfig::CGameConfig(const char *file) m_pOffsets = sm_trie_create(); m_pProps = sm_trie_create(); m_pKeys = sm_trie_create(); + m_pAddresses = new KTrie(); m_pSigs = sm_trie_create(); m_pStrings = new BaseStringTable(512); m_RefCount = 0; @@ -145,6 +149,7 @@ CGameConfig::~CGameConfig() sm_trie_destroy(m_pOffsets); sm_trie_destroy(m_pProps); sm_trie_destroy(m_pKeys); + delete m_pAddresses; sm_trie_destroy(m_pSigs); delete m_pStrings; } @@ -212,6 +217,10 @@ SMCResult CGameConfig::ReadSMC_NewSection(const SMCStates *states, const char *n m_ParseState = PSTATE_GAMEDEFS_CRC; bShouldBeReadingDefault = false; } + else if (strcmp(name, "Addresses") == 0) + { + m_ParseState = PSTATE_GAMEDEFS_ADDRESSES; + } else { ITextListener_SMC **pListen = g_GameConfigs.m_customHandlers.retrieve(name); @@ -293,12 +302,41 @@ SMCResult CGameConfig::ReadSMC_NewSection(const SMCStates *states, const char *n return m_CustomHandler->ReadSMC_NewSection(states, name); break; } + case PSTATE_GAMEDEFS_ADDRESSES: + { + m_Address[0] = '\0'; + m_AddressSignature[0] = '\0'; + m_AddressReadCount = 0; + + strncopy(m_Address, name, sizeof(m_Address)); + m_ParseState = PSTATE_GAMEDEFS_ADDRESSES_ADDRESS; + + break; + } + case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS: + { + if (strcmp(name, PLATFORM_NAME) == 0) + { + m_ParseState = PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ; + } + else + { + if (strcmp(name, "linux") != 0 && strcmp(name, "windows") != 0) + { + g_Logger.LogError("[SM] Error while parsing Address section for \"%s\" (%s):", m_Address, m_CurFile); + g_Logger.LogError("[SM] Unrecognized platform \"%s\"", name); + } + m_IgnoreLevel = 1; + } + break; + } /* No sub-sections allowed: case PSTATE_GAMEDEFS_OFFSETS_OFFSET: case PSTATE_GAMEDEFS_KEYS: case PSTATE_GAMEDEFS_SUPPORTED: case PSTATE_GAMEDEFS_SIGNATURES_SIG: case PSTATE_GAMEDEFS_CRC_BINARY: + case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ: */ default: { @@ -376,6 +414,21 @@ SMCResult CGameConfig::ReadSMC_KeyValue(const SMCStates *states, const char *key bShouldBeReadingDefault = true; } } + } else if (m_ParseState == PSTATE_GAMEDEFS_ADDRESSES_ADDRESS || m_ParseState == PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ) { + if (strcmp(key, "read") == 0) { + int limit = sizeof(m_AddressRead)/sizeof(m_AddressRead[0]); + if (m_AddressReadCount < limit) + { + m_AddressRead[m_AddressReadCount] = atoi(value); + m_AddressReadCount++; + } + else + { + g_Logger.LogError("[SM] Error parsing Address \"%s\", does not support more than %d read offsets (gameconf \"%s\")", m_Address, limit, m_CurFile); + } + } else if (strcmp(key, "signature") == 0) { + strncopy(m_AddressSignature, value, sizeof(m_AddressSignature)); + } } else if (m_ParseState == PSTATE_GAMEDEFS_CUSTOM) { return m_CustomHandler->ReadSMC_KeyValue(states, key, value); } @@ -548,6 +601,36 @@ skip_find: sm_trie_replace(m_pSigs, m_offset, final_addr); m_ParseState = PSTATE_GAMEDEFS_SIGNATURES; + break; + } + case PSTATE_GAMEDEFS_ADDRESSES: + { + m_ParseState = PSTATE_GAMEDEFS; + break; + } + case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS: + { + m_ParseState = PSTATE_GAMEDEFS_ADDRESSES; + + if (m_Address[0] == '\0') + { + g_Logger.LogError("[SM] Address sections must have names (gameconf \"%s\")", m_CurFile); + break; + } + if (m_AddressSignature[0] == '\0') + { + g_Logger.LogError("[SM] Address section for \"%s\" did not specify a signature (gameconf \"%s\")", m_Address, m_CurFile); + break; + } + + AddressConf addrConf(m_AddressSignature, sizeof(m_AddressSignature), m_AddressReadCount, m_AddressRead); + m_pAddresses->replace(m_Address, addrConf); + + break; + } + case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ: + { + m_ParseState = PSTATE_GAMEDEFS_ADDRESSES_ADDRESS; break; } } @@ -689,6 +772,7 @@ bool CGameConfig::Reparse(char *error, size_t maxlength) sm_trie_clear(m_pOffsets); sm_trie_clear(m_pProps); sm_trie_clear(m_pKeys); + m_pAddresses->clear(); char path[PLATFORM_MAX_PATH]; @@ -837,6 +921,53 @@ const char *CGameConfig::GetKeyValue(const char *key) return m_pStrings->GetString((int)obj); } +//memory addresses below 0x10000 are automatically considered invalid for dereferencing +#define VALID_MINIMUM_MEMORY_ADDRESS 0x10000 + +bool CGameConfig::GetAddress(const char *key, void **retaddr) +{ + AddressConf *addrConf; + + addrConf = m_pAddresses->retrieve(key); + if (!addrConf) + { + *retaddr = NULL; + return false; + } + + void *addr; + if (!GetMemSig(addrConf->signatureName, &addr)) + { + *retaddr = NULL; + return false; + } + + for (int i = 0; i < addrConf->readCount; ++i) + { + int offset = addrConf->read[i]; + + //NULLs in the middle of an indirection chain are bad, end NULL is ok + if (addr == NULL || reinterpret_cast(addr) < VALID_MINIMUM_MEMORY_ADDRESS) + { + *retaddr = NULL; + return false; + } + addr = *(reinterpret_cast(reinterpret_cast(addr) + offset)); + } + + *retaddr = addr; + return true; +} + +CGameConfig::AddressConf::AddressConf(char *sigName, unsigned sigLength, unsigned readCount, int *read) +{ + unsigned readLimit = min(readCount, sizeof(this->read) / sizeof(this->read[0])); + + strncopy(signatureName, sigName, sizeof(signatureName) / sizeof(signatureName[0])); + this->readCount = readLimit; + memcpy(&this->read[0], read, sizeof(this->read[0])*readLimit); +} + SendProp *CGameConfig::GetSendProp(const char *key) { SendProp *pProp; diff --git a/core/GameConfigs.h b/core/GameConfigs.h index 6a1fb518..067e25a3 100644 --- a/core/GameConfigs.h +++ b/core/GameConfigs.h @@ -65,6 +65,7 @@ public: //IGameConfig bool GetOffset(const char *key, int *value); SendProp *GetSendProp(const char *key); bool GetMemSig(const char *key, void **addr); + bool GetAddress(const char *key, void **addr); public: void IncRefCount(); unsigned int DecRefCount(); @@ -93,6 +94,24 @@ private: /* Custom Sections */ unsigned int m_CustomLevel; ITextListener_SMC *m_CustomHandler; + + /* Support for reading Addresses */ + struct AddressConf + { + char signatureName[64]; + int readCount; + int read[8]; + + AddressConf(char *sigName, unsigned sigLength, unsigned readCount, int *read); + + AddressConf() {} + }; + + char m_Address[64]; + char m_AddressSignature[64]; + int m_AddressReadCount; + int m_AddressRead[8]; + KTrie *m_pAddresses; }; class GameConfigManager : diff --git a/core/smn_core.cpp b/core/smn_core.cpp index 88b9a534..78eb3431 100644 --- a/core/smn_core.cpp +++ b/core/smn_core.cpp @@ -673,6 +673,77 @@ static cell_t RequireFeature(IPluginContext *pContext, const cell_t *params) return 1; } +enum NumberType +{ + NumberType_Int8, + NumberType_Int16, + NumberType_Int32 +}; + +//memory addresses below 0x10000 are automatically considered invalid for dereferencing +#define VALID_MINIMUM_MEMORY_ADDRESS 0x10000 + +static cell_t LoadFromAddress(IPluginContext *pContext, const cell_t *params) +{ + void *addr = reinterpret_cast(params[1]); + + if (addr == NULL) + { + pContext->ThrowNativeError("Address cannot be null"); + } + else if (reinterpret_cast(addr) < VALID_MINIMUM_MEMORY_ADDRESS) + { + pContext->ThrowNativeError("Invalid address 0x%x is pointing to reserved memory.", addr); + } + NumberType size = static_cast(params[2]); + + switch(size) + { + case NumberType_Int8: + return *reinterpret_cast(addr); + case NumberType_Int16: + return *reinterpret_cast(addr); + case NumberType_Int32: + return *reinterpret_cast(addr); + default: + pContext->ThrowNativeError("Invalid number types %d", size); + } + + return 1; +} + + +static cell_t StoreToAddress(IPluginContext *pContext, const cell_t *params) +{ + void *addr = reinterpret_cast(params[1]); + + if (addr == NULL) + { + pContext->ThrowNativeError("Address cannot be null"); + } + else if (reinterpret_cast(addr) < VALID_MINIMUM_MEMORY_ADDRESS) + { + pContext->ThrowNativeError("Invalid address 0x%x is pointing to reserved memory.", addr); + } + cell_t data = params[2]; + + NumberType size = static_cast(params[3]); + + switch(size) + { + case NumberType_Int8: + *reinterpret_cast(addr) = data; + case NumberType_Int16: + *reinterpret_cast(addr) = data; + case NumberType_Int32: + *reinterpret_cast(addr) = data; + default: + pContext->ThrowNativeError("Invalid number types %d", size); + } + + return 1; +} + REGISTER_NATIVES(coreNatives) { {"AutoExecConfig", AutoExecConfig}, @@ -699,6 +770,8 @@ REGISTER_NATIVES(coreNatives) {"VerifyCoreVersion", VerifyCoreVersion}, {"GetFeatureStatus", GetFeatureStatus}, {"RequireFeature", RequireFeature}, + {"LoadFromAddress", LoadFromAddress}, + {"StoreToAddress", StoreToAddress}, {NULL, NULL}, }; diff --git a/core/smn_gameconfigs.cpp b/core/smn_gameconfigs.cpp index 0ec301ed..b0bc5a3e 100644 --- a/core/smn_gameconfigs.cpp +++ b/core/smn_gameconfigs.cpp @@ -128,6 +128,35 @@ static cell_t smn_GameConfGetKeyValue(IPluginContext *pCtx, const cell_t *params return 1; } + +static cell_t smn_GameConfGetAddress(IPluginContext *pCtx, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError herr; + HandleSecurity sec; + IGameConfig *gc; + + sec.pOwner = NULL; + sec.pIdentity = g_pCoreIdent; + + if ((herr=g_HandleSys.ReadHandle(hndl, g_GameConfigsType, &sec, (void **)&gc)) + != HandleError_None) + { + return pCtx->ThrowNativeError("Invalid game config handle %x (error %d)", hndl, herr); + } + + char *key; + void* val; + pCtx->LocalToString(params[2], &key); + + if (!gc->GetAddress(key, &val)) + { + return NULL; + } + + return (cell_t)val; +} + static GameConfigsNatives s_GameConfigsNatives; REGISTER_NATIVES(gameconfignatives) @@ -135,5 +164,6 @@ REGISTER_NATIVES(gameconfignatives) {"LoadGameConfigFile", smn_LoadGameConfigFile}, {"GameConfGetOffset", smn_GameConfGetOffset}, {"GameConfGetKeyValue", smn_GameConfGetKeyValue}, + {"GameConfGetAddress", smn_GameConfGetAddress}, {NULL, NULL} }; diff --git a/extensions/sdktools/vcaller.cpp b/extensions/sdktools/vcaller.cpp index 547d7856..73480d27 100644 --- a/extensions/sdktools/vcaller.cpp +++ b/extensions/sdktools/vcaller.cpp @@ -250,7 +250,7 @@ static cell_t SDKCall(IPluginContext *pContext, const cell_t *params) unsigned char *ptr = vc->stk_get(); - unsigned int numparams = (unsigned)params[0]; + const unsigned int numparams = (unsigned)params[0]; unsigned int startparam = 2; /* Do we need to write a thispointer? */ @@ -308,6 +308,25 @@ static cell_t SDKCall(IPluginContext *pContext, const cell_t *params) *(void **)ptr = g_EntList; } break; + case ValveCall_Raw: + { + //params[startparam] is an address to a pointer to THIS + //params following this are params to the method we will invoke later + if (startparam > numparams) + { + vc->stk_put(ptr); + return pContext->ThrowNativeError("Expected a ThisPtr address, it wasn't found"); + } + + //note: varargs pawn args are passed by-ref + cell_t *cell; + pContext->LocalToPhysAddr(params[startparam], &cell); + void *thisptr = reinterpret_cast(*cell); + + *(void **)ptr = thisptr; + startparam++; + } + break; } } diff --git a/extensions/sdktools/vdecoder.h b/extensions/sdktools/vdecoder.h index f397d7bb..c7836089 100644 --- a/extensions/sdktools/vdecoder.h +++ b/extensions/sdktools/vdecoder.h @@ -80,6 +80,7 @@ enum ValveCallType ValveCall_Player, /**< Thiscall (CBasePlayer implicit first parameter) */ ValveCall_GameRules, /**< Thiscall (CGameRules implicit first paramater) */ ValveCall_EntityList, /**< Thiscall (CGlobalEntityList implicit first paramater) */ + ValveCall_Raw, /**< Thiscall (address explicit first parameter) */ }; /** diff --git a/plugins/include/sdktools.inc b/plugins/include/sdktools.inc index 9e72614e..fedf738e 100644 --- a/plugins/include/sdktools.inc +++ b/plugins/include/sdktools.inc @@ -57,6 +57,7 @@ enum SDKCallType SDKCall_Player, /**< CBasePlayer call */ SDKCall_GameRules, /**< CGameRules call */ SDKCall_EntityList, /**< CGlobalEntityList call */ + SDKCall_Raw, /**< Raw Address call */ }; enum SDKLibrary diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index 407332bb..7b146083 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -386,6 +386,16 @@ native GameConfGetOffset(Handle:gc, const String:key[]); */ native bool:GameConfGetKeyValue(Handle:gc, const String:key[], String:buffer[], maxlen); +/* + * Finds an address calculation in a GameConfig file, + * performs LoadFromAddress on it as appropriate, then returns the final address. + * + * @param gameconf GameConfig Handle, or INVALID_HANDLE to use sdktools.games.txt. + * @param name Name of the property to find. + * @return An address calculated on success, or 0 on failure. + */ +native Address:GameConfGetAddress(Handle:gameconf, const String:name[]); + /** * Returns the operating system's "tick count," which is a number of * milliseconds since the operating system loaded. This can be used @@ -630,6 +640,41 @@ native FeatureStatus:GetFeatureStatus(FeatureType:type, const String:name[]); native RequireFeature(FeatureType:type, const String:name[], const String:fmt[]="", any:...); +/** + * Represents how many bytes we can read from an address with one load + */ +enum NumberType +{ + NumberType_Int8, + NumberType_Int16, + NumberType_Int32 +}; + +enum Address +{ + Address_Null = 0, //a typical invalid result when an address lookup fails + Address_MinimumValid = 0x10000 //addresses below this value are considered invalid to use for Load/Store +}; + +/** + * Load up to 4 bytes from a memory address. + * + * @param addr Address to a memory location. + * @param size How many bytes should be read. + * @return The value that is stored at that address. + */ +native LoadFromAddress(Address:addr, NumberType:size); + +/** + * Store up to 4 bytes to a memory address. + * + * @param addr Address to a memory location. + * @param data Value to store at the address. + * @param size How many bytes should be written. + * @noreturn + */ +native StoreToAddress(Address:addr, data, NumberType:size); + #include #include #include diff --git a/public/IGameConfigs.h b/public/IGameConfigs.h index be00a1e5..83feb61e 100644 --- a/public/IGameConfigs.h +++ b/public/IGameConfigs.h @@ -42,7 +42,7 @@ */ #define SMINTERFACE_GAMECONFIG_NAME "IGameConfigManager" -#define SMINTERFACE_GAMECONFIG_VERSION 5 +#define SMINTERFACE_GAMECONFIG_VERSION 6 class SendProp; @@ -89,6 +89,15 @@ namespace SourceMod * address is NULL. */ virtual bool GetMemSig(const char *key, void **addr) =0; + + /** + * @brief Retrieves the value of an address from the "Address" section. + * + * @param key Key to retrieve from the Address section. + * @param addr Pointer to store the memory address. + * @return True on success, false on failure. + */ + virtual bool GetAddress(const char *key, void **addr) =0; }; /**