From 6439769d504ae390ce09a429dd2c3122c785861f Mon Sep 17 00:00:00 2001 From: Benoist <14257866+Kenzzer@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:23:13 +0100 Subject: [PATCH] Introduce Virtual Address (#2226) In the current ongoing effort for sourcemod to fully support 64 bits, we are introducing "virtual address". # Explanation Because SourcePawn does not yet support a 64 bits-wide type it's been impossible for any plugins to hold addresses in regular 32-bits wide variable. A first attempt at solving this issue was made in commit ce1a4dcac0c42e3aee3de9af1a26b3823b273208 therein dubbed "PseudoAddress", however this turned out to be an unsatisfactory solution, as any 'high' address if offsetted could turn invalid (or outright be impossible to map). This leaves us with three alternatives : - New type - Convert Address into a handle - Virtual Address A new type is the most destructive solution, as it entails breaking every single Address related method. While that solution is still not off the table, we're reserving it as the last attempt should this commit fail. Converting into a handle type is a good compromise between a brand new type whilst also preserving the Address methods. However, this comes with two issues: the first being that you can no longer offset Address, the second is that we would require authors to free the handle type which will be very confusing. This will likely not be implemented. # Virtual address Under a reasonable assumption, we've noted that the average plugin is unlikely to play with more than 4 GB of memory; this shouldn't be too surprising as all valve games were once 32bits and therefore limited to 4GB. Assuming this stays mostly true and a plugin isn't interested with the mapped memory of lesser known modules (like soundlib or matlib), it is fair to assume plugins are unlikely to access more than 4GB of mapped memory. Working with this in mind, we map the memory the plugins are likely to access to our custom virtual address ranges (from 0 to 4Gb, the values of which can fit on 32bits variable). If any memory was missed and plugins were to try an access it later those ranges will be late-mapped to our virtual address ranges until we run out of them. In order to use virtual addressing, whether on 32 bits or 64 bits. Plugins must now "#include ", as well as use the new SDKCall_VirtualAddress, SDKType_VirtualAddress, LoadAddressFromAddress & StoreAddressToAddress where it's appropriate to. --- .gitmodules | 3 + core/logic/AMBuilder | 12 +- core/logic/PluginSys.cpp | 7 + core/logic/PseudoAddrManager.cpp | 190 ++++++++-------------- core/logic/PseudoAddrManager.h | 9 +- core/logic/common_logic.cpp | 12 +- core/logic/libaddrz | 1 + core/logic/smn_core.cpp | 72 +++++++- core/logic/smn_gameconfigs.cpp | 19 +-- core/smn_entities.cpp | 23 ++- extensions/dhooks/dynhooks_sourcepawn.cpp | 2 +- extensions/dhooks/natives.cpp | 16 +- extensions/dhooks/vhook.cpp | 19 +-- extensions/dhooks/vhook.h | 2 +- extensions/sdktools/vcaller.cpp | 56 ++++++- extensions/sdktools/vdecoder.cpp | 51 ++++++ extensions/sdktools/vdecoder.h | 2 + plugins/include/sdktools.inc | 8 +- plugins/include/sourcemod.inc | 20 +++ plugins/include/virtual_address.inc | 39 +++++ 20 files changed, 371 insertions(+), 192 deletions(-) create mode 160000 core/logic/libaddrz create mode 100644 plugins/include/virtual_address.inc diff --git a/.gitmodules b/.gitmodules index dce1debb8..1a0be190b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,3 +12,6 @@ [submodule "public/safetyhook"] path = public/safetyhook url = https://github.com/alliedmodders/safetyhook +[submodule "core/logic/libaddrz"] + path = core/logic/libaddrz + url = https://github.com/dvander/libaddrz.git diff --git a/core/logic/AMBuilder b/core/logic/AMBuilder index 3af40f19d..581f0c91a 100644 --- a/core/logic/AMBuilder +++ b/core/logic/AMBuilder @@ -86,9 +86,15 @@ for cxx in builder.targets: 'DatabaseConfBuilder.cpp', 'LumpManager.cpp', 'smn_entitylump.cpp', + 'libaddrz/addrz.cpp', + 'libaddrz/mapping.cpp', + 'libaddrz/platform.cpp', + 'libaddrz/proc_maps.cpp', + 'PseudoAddrManager.cpp', ] - - if binary.compiler.target.arch == 'x86_64': - binary.sources += ['PseudoAddrManager.cpp'] + if binary.compiler.target.platform == 'linux': + binary.sources += ['libaddrz/platform_linux.cpp'] + elif binary.compiler.target.platform == 'windows': + binary.sources += ['libaddrz/platform_windows.cpp'] SM.binaries += [builder.Add(binary)] diff --git a/core/logic/PluginSys.cpp b/core/logic/PluginSys.cpp index 33f1c3af3..74d4161dc 100644 --- a/core/logic/PluginSys.cpp +++ b/core/logic/PluginSys.cpp @@ -330,6 +330,13 @@ bool CPlugin::ReadInfo() else m_MaxClientsVar = nullptr; + if (base->FindPubvarByName("PointerSize", &idx) == SP_ERROR_NONE) { + sp_pubvar_t* var = nullptr; + if (base->GetPubvarByIndex(idx, &var) == SP_ERROR_NONE && var) { + *var->offs = sizeof(void*); + } + } + return true; } diff --git a/core/logic/PseudoAddrManager.cpp b/core/logic/PseudoAddrManager.cpp index 75e860467..c784545cb 100644 --- a/core/logic/PseudoAddrManager.cpp +++ b/core/logic/PseudoAddrManager.cpp @@ -28,6 +28,7 @@ */ #include "PseudoAddrManager.h" +#include #ifdef PLATFORM_APPLE #include #include @@ -35,135 +36,88 @@ #ifdef PLATFORM_LINUX #include #endif +#ifdef PLATFORM_WINDOWS +#include +#endif -PseudoAddressManager::PseudoAddressManager() : m_NumEntries(0) +PseudoAddressManager::PseudoAddressManager() : m_dictionary(am::IPlatform::GetDefault()) { } -// A pseudo address consists of a table index in the upper 6 bits and an offset in the -// lower 26 bits. The table consists of memory allocation base addresses. +void PseudoAddressManager::Initialize() { +#ifdef PLATFORM_WINDOWS + auto process = GetCurrentProcess(); + auto get_module_details = [process](const char* name, void*& baseAddress, size_t& moduleSize) { + if (process == NULL) { + return false; + } + auto hndl = GetModuleHandle(name); + if (hndl == NULL) { + return false; + } + MODULEINFO info; + if (!GetModuleInformation(process, hndl, &info, sizeof(info))) { + return false; + } + moduleSize = info.SizeOfImage; + baseAddress = info.lpBaseOfDll; + return true; + }; +#endif +#ifdef PLATFORM_LINUX + auto get_module_details = [](const char* name, void* baseAddress, size_t& moduleSize) { + auto hndl = dlopen(name, RTLD_NOLOAD); + if (hndl == NULL) { + return false; + } + void* addr = dlsym(hndl, "CreateInterface"); + dlclose(hndl); + + if (!addr) { + return false; + } + + Dl_info info; + if (dladdr(addr, &info) == 0) { + return false; + } + + baseAddress = info.dli_fbase; + // It doesn't matter much if we figure out the module size + // libaddrz coalesce maps on linux + moduleSize = 0; + return true; + }; +#endif + + // Early map commonly used modules, it's okay if not all of them are here + // Everything else will be caught by "ToPseudoAddress" but you risk running out of ranges by then + const char* libs[] = { "engine", "server", "tier0", "vstdlib" }; + + char formattedName[64]; + for (int i = 0; i < sizeof(libs) / sizeof(const char*); i++) { + bridge->FormatSourceBinaryName(libs[i], formattedName, sizeof(formattedName)); + void* base_addr = nullptr; + size_t module_size = 0; + if (get_module_details(formattedName, base_addr, module_size)) { + // Create the mapping (hopefully) + m_dictionary.Make32bitAddress(base_addr, module_size); + } + } +} + void *PseudoAddressManager::FromPseudoAddress(uint32_t paddr) { -#ifdef KE_ARCH_X64 - uint8_t index = paddr >> PSEUDO_OFFSET_BITS; - uint32_t offset = paddr & ((1 << PSEUDO_OFFSET_BITS) - 1); - - if (index >= m_NumEntries) + if (paddr == 0) { return nullptr; - - return reinterpret_cast(uintptr_t(m_AllocBases[index]) + offset); -#else - return nullptr; -#endif + } + return m_dictionary.RecoverAddress(paddr).value_or(nullptr); } uint32_t PseudoAddressManager::ToPseudoAddress(void *addr) { -#ifdef KE_ARCH_X64 - uint8_t index = 0; - uint32_t offset = 0; - bool hasEntry = false; - void *base = GetAllocationBase(addr); - - if (base) { - for (int i = 0; i < m_NumEntries; i++) { - if (m_AllocBases[i] == base) { - index = i; - hasEntry = true; - break; - } - } - } else { + if (addr == nullptr) { return 0; } - - if (!hasEntry) { - index = m_NumEntries; - if (m_NumEntries < SM_ARRAYSIZE(m_AllocBases)) - m_AllocBases[m_NumEntries++] = base; - else - return 0; // Table is full - } - - ptrdiff_t diff = uintptr_t(addr) - uintptr_t(base); - - // Ensure difference fits in 26 bits - if (diff > (UINT32_MAX >> PSEUDO_INDEX_BITS)) - return 0; - - return (index << PSEUDO_OFFSET_BITS) | diff; -#else - return 0; -#endif -} - -void *PseudoAddressManager::GetAllocationBase(void *ptr) -{ -#if defined PLATFORM_WINDOWS - - MEMORY_BASIC_INFORMATION info; - if (!VirtualQuery(ptr, &info, sizeof(MEMORY_BASIC_INFORMATION))) - return nullptr; - return info.AllocationBase; - -#elif defined PLATFORM_APPLE - -#ifdef KE_ARCH_X86 - typedef vm_region_info_t mach_vm_region_info_t; - typedef vm_region_basic_info_data_t mach_vm_region_basic_info_data_t; - const vm_region_flavor_t MACH_VM_REGION_BASIC_INFO = VM_REGION_BASIC_INFO; - const mach_msg_type_number_t MACH_VM_REGION_BASIC_INFO_COUNT = VM_REGION_BASIC_INFO_COUNT; - #define mach_vm_region vm_region -#elif defined KE_ARCH_X64 - typedef vm_region_info_64_t mach_vm_region_info_t ; - typedef vm_region_basic_info_data_64_t mach_vm_region_basic_info_data_t; - const vm_region_flavor_t MACH_VM_REGION_BASIC_INFO = VM_REGION_BASIC_INFO_64; - const mach_msg_type_number_t MACH_VM_REGION_BASIC_INFO_COUNT = VM_REGION_BASIC_INFO_COUNT_64; - #define mach_vm_region vm_region_64 -#endif - vm_size_t size; - vm_address_t vmaddr = reinterpret_cast(ptr); - mach_vm_region_basic_info_data_t info; - memory_object_name_t obj; - vm_region_flavor_t flavor = MACH_VM_REGION_BASIC_INFO; - mach_msg_type_number_t count = MACH_VM_REGION_BASIC_INFO_COUNT; - - kern_return_t kr = mach_vm_region(mach_task_self(), &vmaddr, &size, flavor, - reinterpret_cast(&info), - &count, &obj); - - if (kr != KERN_SUCCESS) - return nullptr; - - return reinterpret_cast(vmaddr); - -#elif defined PLATFORM_LINUX - - uintptr_t addr = reinterpret_cast(ptr); - - // Format: - // lower upper prot stuff path - // 08048000-0804c000 r-xp 00000000 03:03 1010107 /bin/cat - FILE *fp = fopen("/proc/self/maps", "r"); - if (fp) { - uintptr_t lower, upper; - while (fscanf(fp, "%" PRIxPTR "-%" PRIxPTR, &lower, &upper) != EOF) { - if (addr >= lower && addr <= upper) { - fclose(fp); - return reinterpret_cast(lower); - } - - // Read to end of line - int c; - while ((c = fgetc(fp)) != '\n') { - if (c == EOF) - break; - } - if (c == EOF) - break; - } - fclose(fp); - } - return nullptr; -#endif + return m_dictionary.Make32bitAddress(addr).value_or(0); } diff --git a/core/logic/PseudoAddrManager.h b/core/logic/PseudoAddrManager.h index 5de802aa2..4c6ca59d7 100644 --- a/core/logic/PseudoAddrManager.h +++ b/core/logic/PseudoAddrManager.h @@ -31,6 +31,7 @@ #define _INCLUDE_SOURCEMOD_PSEUDOADDRESSMANAGER_H_ #include "common_logic.h" +#include "libaddrz/addrz.h" class PseudoAddressManager { @@ -39,13 +40,9 @@ public: public: void *FromPseudoAddress(uint32_t paddr); uint32_t ToPseudoAddress(void *addr); + void Initialize(); private: - void *GetAllocationBase(void *ptr); -private: - static constexpr uint8_t PSEUDO_OFFSET_BITS = 26; - static constexpr uint8_t PSEUDO_INDEX_BITS = sizeof(uint32_t) * 8 - PSEUDO_OFFSET_BITS; - void *m_AllocBases[1 << PSEUDO_INDEX_BITS]; - uint8_t m_NumEntries; + am::AddressDict m_dictionary; }; #endif // _INCLUDE_SOURCEMOD_PSEUDOADDRESSMANAGER_H_ diff --git a/core/logic/common_logic.cpp b/core/logic/common_logic.cpp index f88137321..cc9e1de6c 100644 --- a/core/logic/common_logic.cpp +++ b/core/logic/common_logic.cpp @@ -57,6 +57,7 @@ #include "RootConsoleMenu.h" #include "CellArray.h" #include "smn_entitylump.h" +#include "PseudoAddrManager.h" #include #include @@ -86,9 +87,7 @@ IScriptManager *scripts = &g_PluginSys; IExtensionSys *extsys = &g_Extensions; ILogger *logger = &g_Logger; CNativeOwner g_CoreNatives; -#ifdef KE_ARCH_X64 PseudoAddressManager pseudoAddr; -#endif EntityLumpParseResult lastParseResult; @@ -122,20 +121,12 @@ static void RegisterProfiler(IProfilingTool *tool) static void *FromPseudoAddress(uint32_t paddr) { -#ifdef KE_ARCH_X64 return pseudoAddr.FromPseudoAddress(paddr); -#else - return nullptr; -#endif } static uint32_t ToPseudoAddress(void *addr) { -#ifdef KE_ARCH_X64 return pseudoAddr.ToPseudoAddress(addr); -#else - return 0; -#endif } static void SetEntityLumpWritable(bool writable) @@ -236,6 +227,7 @@ static void logic_init(CoreProvider* core, sm_logic_t* _logic) g_pSourcePawn2 = *core->spe2; SMGlobalClass::head = core->listeners; + pseudoAddr.Initialize(); g_ShareSys.Initialize(); g_pCoreIdent = g_ShareSys.CreateCoreIdentity(); diff --git a/core/logic/libaddrz b/core/logic/libaddrz new file mode 160000 index 000000000..661cd316e --- /dev/null +++ b/core/logic/libaddrz @@ -0,0 +1 @@ +Subproject commit 661cd316e6ff8e8560efa20db1794f3fa479647c diff --git a/core/logic/smn_core.cpp b/core/logic/smn_core.cpp index 67bf66c89..0c98e481b 100644 --- a/core/logic/smn_core.cpp +++ b/core/logic/smn_core.cpp @@ -59,6 +59,7 @@ #include #include #include +#include "PseudoAddrManager.h" #include using namespace SourceMod; @@ -863,11 +864,10 @@ enum NumberType static cell_t LoadFromAddress(IPluginContext *pContext, const cell_t *params) { -#ifdef KE_ARCH_X86 void *addr = reinterpret_cast(params[1]); -#else - void *addr = pseudoAddr.FromPseudoAddress(params[1]); -#endif + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + addr = pseudoAddr.FromPseudoAddress(params[1]); + } if (addr == NULL) { @@ -892,14 +892,12 @@ static cell_t LoadFromAddress(IPluginContext *pContext, const cell_t *params) } } - static cell_t StoreToAddress(IPluginContext *pContext, const cell_t *params) { -#ifdef KE_ARCH_X86 void *addr = reinterpret_cast(params[1]); -#else - void *addr = pseudoAddr.FromPseudoAddress(params[1]); -#endif + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + addr = pseudoAddr.FromPseudoAddress(params[1]); + } if (addr == NULL) { @@ -950,6 +948,60 @@ static cell_t StoreToAddress(IPluginContext *pContext, const cell_t *params) return 0; } +static cell_t LoadAddressFromAddress(IPluginContext *pContext, const cell_t *params) +{ + void *addr = reinterpret_cast(params[1]); + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + addr = pseudoAddr.FromPseudoAddress(params[1]); + } + + if (addr == NULL) + { + return pContext->ThrowNativeError("Address cannot be null"); + } + else if (reinterpret_cast(addr) < VALID_MINIMUM_MEMORY_ADDRESS) + { + return pContext->ThrowNativeError("Invalid address 0x%x is pointing to reserved memory.", addr); + } + + void* data = *reinterpret_cast(addr); + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + return pseudoAddr.ToPseudoAddress(data); + } + return reinterpret_cast(data); +} + +static cell_t StoreAddressToAddress(IPluginContext *pContext, const cell_t *params) +{ + void *addr = reinterpret_cast(params[1]); + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + addr = pseudoAddr.FromPseudoAddress(params[1]); + } + + if (addr == NULL) + { + return pContext->ThrowNativeError("Address cannot be null"); + } + else if (reinterpret_cast(addr) < VALID_MINIMUM_MEMORY_ADDRESS) + { + return pContext->ThrowNativeError("Invalid address 0x%x is pointing to reserved memory.", addr); + } + + void *data = reinterpret_cast(params[2]); + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + data = pseudoAddr.FromPseudoAddress(params[2]); + } + + bool updateMemAccess = params[3]; + + if (updateMemAccess) { + SourceHook::SetMemAccess(addr, sizeof(void*), SH_MEM_READ|SH_MEM_WRITE|SH_MEM_EXEC); + } + *reinterpret_cast(addr) = data; + + return 0; +} + static cell_t IsNullVector(IPluginContext *pContext, const cell_t *params) { cell_t *pNullVec = pContext->GetNullRef(SP_NULL_VECTOR); @@ -1157,6 +1209,8 @@ REGISTER_NATIVES(coreNatives) {"RequireFeature", RequireFeature}, {"LoadFromAddress", LoadFromAddress}, {"StoreToAddress", StoreToAddress}, + {"LoadAddressFromAddress", LoadAddressFromAddress}, + {"StoreAddressToAddress", StoreAddressToAddress}, {"IsNullVector", IsNullVector}, {"IsNullString", IsNullString}, {"LogStackTrace", LogStackTrace}, diff --git a/core/logic/smn_gameconfigs.cpp b/core/logic/smn_gameconfigs.cpp index f475954aa..f8c6cbc83 100644 --- a/core/logic/smn_gameconfigs.cpp +++ b/core/logic/smn_gameconfigs.cpp @@ -32,6 +32,7 @@ #include "common_logic.h" #include #include "GameConfigs.h" +#include "PseudoAddrManager.h" HandleType_t g_GameConfigsType; @@ -155,11 +156,10 @@ static cell_t smn_GameConfGetAddress(IPluginContext *pCtx, const cell_t *params) if (!gc->GetAddress(key, &val)) return 0; -#ifdef KE_ARCH_X86 - return (cell_t)val; -#else - return pseudoAddr.ToPseudoAddress(val); -#endif + if (pCtx->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + return pseudoAddr.ToPseudoAddress(val); + } + return reinterpret_cast(val); } static cell_t smn_GameConfGetMemSig(IPluginContext *pCtx, const cell_t *params) @@ -187,11 +187,10 @@ static cell_t smn_GameConfGetMemSig(IPluginContext *pCtx, const cell_t *params) return 0; } -#ifdef KE_ARCH_X86 - return (cell_t)val; -#else - return pseudoAddr.ToPseudoAddress(val); -#endif + if (pCtx->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + return pseudoAddr.ToPseudoAddress(val); + } + return reinterpret_cast(val); } static GameConfigsNatives s_GameConfigsNatives; diff --git a/core/smn_entities.cpp b/core/smn_entities.cpp index c23f9b911..62a3cd74c 100644 --- a/core/smn_entities.cpp +++ b/core/smn_entities.cpp @@ -724,11 +724,10 @@ static cell_t GetEntDataEnt2(IPluginContext *pContext, const cell_t *params) static cell_t LoadEntityFromHandleAddress(IPluginContext *pContext, const cell_t *params) { -#ifdef KE_ARCH_X86 void *addr = reinterpret_cast(params[1]); -#else - void *addr = g_SourceMod.FromPseudoAddress(params[1]); -#endif + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + addr = g_SourceMod.FromPseudoAddress(params[1]); + } if (addr == NULL) { @@ -835,11 +834,10 @@ static cell_t SetEntDataEnt2(IPluginContext *pContext, const cell_t *params) static cell_t StoreEntityToHandleAddress(IPluginContext *pContext, const cell_t *params) { -#ifdef KE_ARCH_X86 void *addr = reinterpret_cast(params[1]); -#else - void *addr = g_SourceMod.FromPseudoAddress(params[1]); -#endif + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + addr = g_SourceMod.FromPseudoAddress(params[1]); + } if (addr == NULL) { @@ -2772,11 +2770,10 @@ static cell_t GetEntityAddress(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("Entity %d (%d) is invalid", g_HL2.ReferenceToIndex(params[1]), params[1]); } -#ifdef KE_ARCH_X86 - return reinterpret_cast(pEntity); -#else - return g_SourceMod.ToPseudoAddress(pEntity); -#endif + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + return g_SourceMod.ToPseudoAddress(pEntity); + } + return reinterpret_cast(pEntity); } REGISTER_NATIVES(entityNatives) diff --git a/extensions/dhooks/dynhooks_sourcepawn.cpp b/extensions/dhooks/dynhooks_sourcepawn.cpp index 6ad744d16..2204c8fa2 100644 --- a/extensions/dhooks/dynhooks_sourcepawn.cpp +++ b/extensions/dhooks/dynhooks_sourcepawn.cpp @@ -376,7 +376,7 @@ ReturnAction_t HandleDetour(HookType_t hookType, CHook* pDetour) { // The this pointer is implicitly always the first argument. void *thisPtr = pDetour->GetArgument(0); - cell_t thisAddr = GetThisPtr(thisPtr, pWrapper->thisType); + cell_t thisAddr = GetThisPtr(pCallback->GetParentContext(), thisPtr, pWrapper->thisType); pCallback->PushCell(thisAddr); } diff --git a/extensions/dhooks/natives.cpp b/extensions/dhooks/natives.cpp index 82b154ada..1310b5309 100644 --- a/extensions/dhooks/natives.cpp +++ b/extensions/dhooks/natives.cpp @@ -174,7 +174,12 @@ cell_t Native_CreateHook(IPluginContext *pContext, const cell_t *params) //native Handle:DHookCreateDetour(Address:funcaddr, CallingConvention:callConv, ReturnType:returntype, ThisPointerType:thistype); cell_t Native_CreateDetour(IPluginContext *pContext, const cell_t *params) { - HookSetup *setup = new HookSetup((ReturnType)params[3], PASSFLAG_BYVAL, (CallingConvention)params[2], (ThisPointerType)params[4], (void *)params[1]); + void* addr = reinterpret_cast(params[1]); + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + addr = g_pSM->FromPseudoAddress(params[1]); + } + + HookSetup *setup = new HookSetup((ReturnType)params[3], PASSFLAG_BYVAL, (CallingConvention)params[2], (ThisPointerType)params[4], addr); Handle_t hndl = handlesys->CreateHandle(g_HookSetupHandle, setup, pContext->GetIdentity(), myself->GetIdentity(), NULL); @@ -641,7 +646,10 @@ cell_t HookRawImpl(IPluginContext *pContext, const cell_t *params, int callbackI if (removalcbIndex > 0) removalcb = pContext->GetFunctionById(params[removalcbIndex]); - void *iface = (void *)(params[3]); + void* iface = reinterpret_cast(params[3]); + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + iface = g_pSM->FromPseudoAddress(params[3]); + } for(int i = g_pHooks.size() -1; i >= 0; i--) { @@ -1482,6 +1490,10 @@ cell_t Native_GetParamAddress(IPluginContext *pContext, const cell_t *params) } size_t offset = GetParamOffset(paramStruct, index); + + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + return g_pSM->ToPseudoAddress(*(void**)((intptr_t)paramStruct->orgParams + offset)); + } return *(cell_t *)((intptr_t)paramStruct->orgParams + offset); } diff --git a/extensions/dhooks/vhook.cpp b/extensions/dhooks/vhook.cpp index 778c43013..673c0b80f 100644 --- a/extensions/dhooks/vhook.cpp +++ b/extensions/dhooks/vhook.cpp @@ -465,19 +465,18 @@ HookReturnStruct *GetReturnStruct(DHooksCallback *dg) return res; } -cell_t GetThisPtr(void *iface, ThisPointerType type) +cell_t GetThisPtr(IPluginContext* pContext, void *iface, ThisPointerType type) { - if(type == ThisPointer_CBaseEntity) + if (type == ThisPointer_CBaseEntity) { if (!iface) return -1; return gamehelpers->EntityToBCompatRef((CBaseEntity *)iface); } -#ifdef KE_ARCH_X64 - return g_pSM->ToPseudoAddress(iface); -#else + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + return g_pSM->ToPseudoAddress(iface); + } return (cell_t)iface; -#endif } #if defined( WIN32 ) && !defined( KE_ARCH_X64 ) @@ -500,7 +499,7 @@ void *Callback(DHooksCallback *dg, void **argStack) if(dg->thisType == ThisPointer_CBaseEntity || dg->thisType == ThisPointer_Address) { - dg->plugin_callback->PushCell(GetThisPtr(g_SHPtr->GetIfacePtr(), dg->thisType)); + dg->plugin_callback->PushCell(GetThisPtr(dg->plugin_callback->GetParentContext(), g_SHPtr->GetIfacePtr(), dg->thisType)); } if(dg->returnType != ReturnType_Void) { @@ -684,7 +683,7 @@ float Callback_float(DHooksCallback *dg, void **argStack) if(dg->thisType == ThisPointer_CBaseEntity || dg->thisType == ThisPointer_Address) { - dg->plugin_callback->PushCell(GetThisPtr(g_SHPtr->GetIfacePtr(), dg->thisType)); + dg->plugin_callback->PushCell(GetThisPtr(dg->plugin_callback->GetParentContext(), g_SHPtr->GetIfacePtr(), dg->thisType)); } returnStruct = GetReturnStruct(dg); @@ -841,7 +840,7 @@ SDKVector *Callback_vector(DHooksCallback *dg, void **argStack) if(dg->thisType == ThisPointer_CBaseEntity || dg->thisType == ThisPointer_Address) { - dg->plugin_callback->PushCell(GetThisPtr(g_SHPtr->GetIfacePtr(), dg->thisType)); + dg->plugin_callback->PushCell(GetThisPtr(dg->plugin_callback->GetParentContext(), g_SHPtr->GetIfacePtr(), dg->thisType)); } returnStruct = GetReturnStruct(dg); @@ -995,7 +994,7 @@ string_t *Callback_stringt(DHooksCallback *dg, void **argStack) if(dg->thisType == ThisPointer_CBaseEntity || dg->thisType == ThisPointer_Address) { - dg->plugin_callback->PushCell(GetThisPtr(g_SHPtr->GetIfacePtr(), dg->thisType)); + dg->plugin_callback->PushCell(GetThisPtr(dg->plugin_callback->GetParentContext(), g_SHPtr->GetIfacePtr(), dg->thisType)); } returnStruct = GetReturnStruct(dg); diff --git a/extensions/dhooks/vhook.h b/extensions/dhooks/vhook.h index 941a975f5..b8ff69cf8 100644 --- a/extensions/dhooks/vhook.h +++ b/extensions/dhooks/vhook.h @@ -339,7 +339,7 @@ public: }; size_t GetStackArgsSize(DHooksCallback *dg); -cell_t GetThisPtr(void *iface, ThisPointerType type); +cell_t GetThisPtr(IPluginContext* pContext, void *iface, ThisPointerType type); extern IBinTools *g_pBinTools; extern HandleType_t g_HookParamsHandle; diff --git a/extensions/sdktools/vcaller.cpp b/extensions/sdktools/vcaller.cpp index 7236edf18..3ce3e4388 100644 --- a/extensions/sdktools/vcaller.cpp +++ b/extensions/sdktools/vcaller.cpp @@ -63,7 +63,8 @@ inline void DecodePassMethod(ValveType vtype, SDKPassMethod method, PassType &ty type = PassType_Basic; if (vtype == Valve_POD || vtype == Valve_Float - || vtype == Valve_Bool) + || vtype == Valve_Bool + || vtype == Valve_VirtualAddress) { flags = PASSFLAG_BYVAL | PASSFLAG_ASPOINTER; } else { @@ -86,6 +87,13 @@ inline void DecodePassMethod(ValveType vtype, SDKPassMethod method, PassType &ty static cell_t StartPrepSDKCall(IPluginContext *pContext, const cell_t *params) { + auto call_type = (ValveCallType)params[1]; + if (call_type == ValveCall_Raw && pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + return pContext->ThrowNativeError("SDKCall_Raw is unavailable for plugins that have enabled virtual address."); + } + if (call_type == ValveCall_VirtualAddress && pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) != SP_ERROR_NONE) { + return pContext->ThrowNativeError("SDKCall_VirtualAddress is unavailable for plugins that haven't enabled virtual address."); + } s_numparams = 0; s_vtbl_index = -1; s_call_addr = NULL; @@ -172,11 +180,10 @@ static cell_t PrepSDKCall_SetSignature(IPluginContext *pContext, const cell_t *p static cell_t PrepSDKCall_SetAddress(IPluginContext *pContext, const cell_t *params) { -#ifdef KE_ARCH_X86 - s_call_addr = reinterpret_cast(params[1]); -#else - s_call_addr = g_pSM->FromPseudoAddress(params[1]); -#endif + s_call_addr = reinterpret_cast(params[1]); + if (pContext->GetRuntime()->FindPubvarByName("__Virtual_Address__", nullptr) == SP_ERROR_NONE) { + s_call_addr = g_pSM->FromPseudoAddress(params[1]); + } return (s_call_addr != NULL) ? 1 : 0; } @@ -413,6 +420,36 @@ static cell_t SDKCall(IPluginContext *pContext, const cell_t *params) startparam++; } break; + case ValveCall_VirtualAddress: + { + //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(g_pSM->FromPseudoAddress(*cell)); + + if (thisptr == nullptr) + { + vc->stk_put(ptr); + return pContext->ThrowNativeError("ThisPtr address cannot be null"); + } + else if (reinterpret_cast(thisptr) < VALID_MINIMUM_MEMORY_ADDRESS) + { + vc->stk_put(ptr); + return pContext->ThrowNativeError("Invalid ThisPtr address %p is pointing to reserved memory.", thisptr); + } + + *(void **)ptr = thisptr; + startparam++; + } + break; default: { vc->stk_put(ptr); @@ -536,6 +573,13 @@ static cell_t SDKCall(IPluginContext *pContext, const cell_t *params) addr = *(bool **)addr; } return *addr ? 1 : 0; + } else if (vc->retinfo->vtype == Valve_VirtualAddress) { + void *addr = *(void **)vc->retbuf; + if (vc->retinfo->flags & PASSFLAG_ASPOINTER) + { + addr = *(void **)addr; + } + return g_pSM->ToPseudoAddress(addr); } else { cell_t *addr = (cell_t *)vc->retbuf; if (vc->retinfo->flags & PASSFLAG_ASPOINTER) diff --git a/extensions/sdktools/vdecoder.cpp b/extensions/sdktools/vdecoder.cpp index 3fb2618d5..b2d52a8dd 100644 --- a/extensions/sdktools/vdecoder.cpp +++ b/extensions/sdktools/vdecoder.cpp @@ -164,6 +164,21 @@ size_t ValveParamToBinParam(ValveType type, return sizeof(float); } } + case Valve_VirtualAddress: + { + info->flags = flags; + if (flags & PASSFLAG_ASPOINTER) + { + needs_extra = true; + info->type = PassType_Basic; + info->size = sizeof(void**); + return sizeof(void**) + sizeof(void*); + } else { + info->type = PassType_Basic; + info->size = sizeof(void*); + return sizeof(void*); + } + } } return 0; @@ -276,6 +291,19 @@ DataStatus EncodeValveParam(IPluginContext *pContext, *addr = *(bool *)buffer ? 1 : 0; + return Data_Okay; + } + case Valve_VirtualAddress: + { + cell_t *addr; + pContext->LocalToPhysAddr(param, &addr); + + if (data->flags & PASSFLAG_ASPOINTER) + { + buffer = *(void **)buffer; + } + *addr = g_pSM->ToPseudoAddress(*(void**)buffer); + return Data_Okay; } } @@ -585,6 +613,29 @@ DataStatus DecodeValveParam(IPluginContext *pContext, *(char **)buffer = addr; return Data_Okay; } + case Valve_VirtualAddress: + { + if (data->decflags & VDECODE_FLAG_BYREF) + { + cell_t *addr; + pContext->LocalToPhysAddr(param, &addr); + param = *addr; + } + if (data->flags & PASSFLAG_ASPOINTER) + { + *(void **)buffer = (unsigned char *)_buffer + pCall->stackEnd + data->obj_offset; + buffer = *(void **)buffer; + } + void* addr = g_pSM->FromPseudoAddress(param); + if (addr == nullptr && (data->decflags & VDECODE_FLAG_ALLOWNULL) == 0) + { + pContext->ThrowNativeError("NULL Address not allowed"); + return Data_Fail; + } + + *(void **)buffer = addr; + return Data_Okay; + } } return Data_Fail; diff --git a/extensions/sdktools/vdecoder.h b/extensions/sdktools/vdecoder.h index 16478cf10..8f2822401 100644 --- a/extensions/sdktools/vdecoder.h +++ b/extensions/sdktools/vdecoder.h @@ -53,6 +53,7 @@ enum ValveType Valve_Edict, /**< Edict */ Valve_String, /**< String */ Valve_Bool, /**< Boolean */ + Valve_VirtualAddress, /**< SM Virtual Address */ Valve_Object, /**< Object, not matching one of the above types */ }; @@ -84,6 +85,7 @@ enum ValveCallType ValveCall_Raw, /**< Thiscall (address explicit first parameter) */ ValveCall_Server, /**< Thiscall (CBaseServer implicit first parameter) */ ValveCall_Engine, /**< Thiscall (CVEngineServer implicit first parameter) */ + ValveCall_VirtualAddress /**< Thiscall (address explicit first parameter) */ }; /** diff --git a/plugins/include/sdktools.inc b/plugins/include/sdktools.inc index 07fc55f82..15d1dfda1 100644 --- a/plugins/include/sdktools.inc +++ b/plugins/include/sdktools.inc @@ -60,9 +60,10 @@ enum SDKCallType SDKCall_Player, /**< CBasePlayer call */ SDKCall_GameRules, /**< CGameRules call */ SDKCall_EntityList, /**< CGlobalEntityList call */ - SDKCall_Raw, /**< |this| pointer with an arbitrary address */ + SDKCall_Raw, /**< |this| pointer with an arbitrary address. This is not available if SM's virtual addresses are enabled */ SDKCall_Server, /**< CBaseServer call */ - SDKCall_Engine /**< CVEngineServer call */ + SDKCall_Engine, /**< CVEngineServer call */ + SDKCall_VirtualAddress /**< |this| pointer with an arbitrary SM virtual address */ }; enum SDKLibrary @@ -88,7 +89,8 @@ enum SDKType SDKType_Float, /**< Float (any) */ SDKType_Edict, /**< edict_t (always as pointer) */ SDKType_String, /**< NULL-terminated string (always as pointer) */ - SDKType_Bool /**< Boolean (any) */ + SDKType_Bool, /**< Boolean (any) */ + SDKType_VirtualAddress, /**< SM Virtual Address */ }; enum SDKPassMethod diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index 192701568..1e6743b36 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -754,6 +754,26 @@ native any LoadFromAddress(Address addr, NumberType size); */ native void StoreToAddress(Address addr, any data, NumberType size, bool updateMemAccess = true); +/** + * Load sizeof(void*) from a memory address. + * + * @param addr Address to a memory location. + * @return The address that is stored at that address. + * @error Address is null or pointing to reserved memory. + */ +native Address LoadAddressFromAddress(Address addr); + +/** + * Store sizeof(void*) bytes to a memory address. + * + * @param addr Address to a memory location. + * @param data Address to store at that location. + * @param updateMemAccess If true, SourceMod will set read / write / exec permissions + * on the memory page being written to. + * @error Address is null or pointing to reserved memory. + */ +native void StoreAddressToAddress(Address addr, Address data, bool updateMemAccess = true); + methodmap FrameIterator < Handle { // Creates a stack frame iterator to build your own stack traces. // @return New handle to a FrameIterator. diff --git a/plugins/include/virtual_address.inc b/plugins/include/virtual_address.inc new file mode 100644 index 000000000..542b4244a --- /dev/null +++ b/plugins/include/virtual_address.inc @@ -0,0 +1,39 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2025 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#if defined _virtual_address_included + #endinput +#endif +#define _virtual_address_included + +public const Address __Virtual_Address__ = view_as
(0); +public const Address PointerSize = view_as
(0); /**< Size of a pointer in bytes (same as `sizeof(void*)`) */