diff --git a/core/logic/AMBuilder b/core/logic/AMBuilder index 3af40f19d7..859783d830 100644 --- a/core/logic/AMBuilder +++ b/core/logic/AMBuilder @@ -86,6 +86,7 @@ for cxx in builder.targets: 'DatabaseConfBuilder.cpp', 'LumpManager.cpp', 'smn_entitylump.cpp', + 'MemoryPointer.cpp' ] if binary.compiler.target.arch == 'x86_64': diff --git a/core/logic/MemoryPointer.cpp b/core/logic/MemoryPointer.cpp new file mode 100644 index 0000000000..3c8f01de6c --- /dev/null +++ b/core/logic/MemoryPointer.cpp @@ -0,0 +1,67 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2024 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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$ + */ + +#include "MemoryPointer.h" +#include +#include +#include + +MemoryPointer::MemoryPointer(cell_t size) : m_ptr(malloc(size)), m_owned(true), m_size(size) +{ +} + +MemoryPointer::MemoryPointer(void* ptr, cell_t size) : m_ptr(ptr), m_owned(false), m_size(size) +{ +} + +MemoryPointer::~MemoryPointer() +{ + if (m_owned && m_ptr) + { + free(m_ptr); + m_ptr = nullptr; + } +} + +void MemoryPointer::Delete() +{ + delete this; +} + +void* MemoryPointer::Get() +{ + return m_ptr; +} + +cell_t MemoryPointer::GetSize() +{ + return m_size; +} \ No newline at end of file diff --git a/core/logic/MemoryPointer.h b/core/logic/MemoryPointer.h new file mode 100644 index 0000000000..4602122cd3 --- /dev/null +++ b/core/logic/MemoryPointer.h @@ -0,0 +1,54 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2024 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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$ + */ + +#pragma once + +#include +#include +#include +#include + +class MemoryPointer : public SourceMod::IMemoryPointer +{ +public: + MemoryPointer(cell_t size); + MemoryPointer(void* ptr, cell_t size); + +// SourceMod::IMemoryPointer + virtual ~MemoryPointer(); + virtual void Delete() override; + virtual void* Get() override; + virtual cell_t GetSize() override; +protected: + void* m_ptr; + bool m_owned; + cell_t m_size; +}; \ No newline at end of file diff --git a/core/logic/smn_core.cpp b/core/logic/smn_core.cpp index 8e90d40310..9926dbff66 100644 --- a/core/logic/smn_core.cpp +++ b/core/logic/smn_core.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -67,6 +68,7 @@ using namespace SourcePawn; HandleType_t g_PlIter; HandleType_t g_FrameIter; +HandleType_t g_MemoryPtr; IForward *g_OnLogAction = NULL; @@ -86,6 +88,14 @@ class CoreNativeHelpers : g_PlIter = handlesys->CreateType("PluginIterator", this, 0, NULL, NULL, g_pCoreIdent, NULL); g_FrameIter = handlesys->CreateType("FrameIterator", this, 0, NULL, NULL, g_pCoreIdent, NULL); + HandleAccess mp_hacc; + TypeAccess mp_tacc; + mp_hacc.access[HandleAccess_Read] = 0; + mp_hacc.access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER; + mp_hacc.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER; + mp_tacc.access[HTypeAccess_Create] = true; + g_MemoryPtr = handlesys->CreateType("MemoryPointer", this, 0, &mp_tacc, &mp_hacc, g_pCoreIdent, NULL); + g_OnLogAction = forwardsys->CreateForward("OnLogAction", ET_Hook, 5, @@ -100,7 +110,11 @@ class CoreNativeHelpers : } void OnHandleDestroy(HandleType_t type, void *object) { - if (type == g_FrameIter) + if (type == g_MemoryPtr) + { + ((IMemoryPointer *)object)->Delete(); + } + else if (type == g_FrameIter) { delete (SafeFrameIterator *) object; } @@ -115,6 +129,7 @@ class CoreNativeHelpers : forwardsys->ReleaseForward(g_OnLogAction); handlesys->RemoveType(g_PlIter, g_pCoreIdent); handlesys->RemoveType(g_FrameIter, g_pCoreIdent); + handlesys->RemoveType(g_MemoryPtr, g_pCoreIdent); } } g_CoreNativeHelpers; @@ -971,6 +986,169 @@ static cell_t IsNullString(IPluginContext *pContext, const cell_t *params) return str == nullptr; } +static cell_t MemoryPointer_Create(IPluginContext *pContext, const cell_t *params) +{ + auto ptr = new MemoryPointer(params[1]); + + Handle_t handle = handlesys->CreateHandle(g_MemoryPtr, ptr, pContext->GetIdentity(), g_pCoreIdent, NULL); + if (handle == BAD_HANDLE) + { + delete ptr; + return BAD_HANDLE; + } + + return handle; +} + +static cell_t MemoryPointer_Store(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMemoryPointer *ptr; + + HandleSecurity sec; + sec.pIdentity = g_pCoreIdent; + sec.pOwner = pContext->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_MemoryPtr, &sec, (void **)&ptr)) != HandleError_None) + { + return pContext->ThrowNativeError("Could not read Handle %x (error %d)", hndl, err); + } + + unsigned int bytesSize = 0; + switch (params[3]) + { + case NumberType_Int8: + bytesSize = sizeof(std::uint8_t); + break; + case NumberType_Int16: + bytesSize = sizeof(std::uint16_t); + break; + case NumberType_Int32: + bytesSize = sizeof(std::uint32_t); + break; + default: + return pContext->ThrowNativeError("Invalid number types %d", params[3]); + } + + ptr->Store(params[2], bytesSize, params[4], params[5] != 0); + + return 0; +} + +static cell_t MemoryPointer_Load(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMemoryPointer *ptr; + + HandleSecurity sec; + sec.pIdentity = g_pCoreIdent; + sec.pOwner = pContext->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_MemoryPtr, &sec, (void **)&ptr)) != HandleError_None) + { + return pContext->ThrowNativeError("Could not read Handle %x (error %d)", hndl, err); + } + + unsigned int bytesSize = 0; + switch (params[2]) + { + case NumberType_Int8: + bytesSize = sizeof(std::uint8_t); + break; + case NumberType_Int16: + bytesSize = sizeof(std::uint16_t); + break; + case NumberType_Int32: + bytesSize = sizeof(std::uint32_t); + break; + default: + return pContext->ThrowNativeError("Invalid number types %d", params[2]); + } + + return ptr->Load(bytesSize, params[3]); +} + +static cell_t MemoryPointer_StoreMemoryPointer(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMemoryPointer *ptr; + + HandleSecurity sec; + sec.pIdentity = g_pCoreIdent; + sec.pOwner = pContext->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_MemoryPtr, &sec, (void **)&ptr)) != HandleError_None) + { + return pContext->ThrowNativeError("Could not read Handle %x (error %d)", hndl, err); + } + + hndl = (Handle_t)params[2]; + IMemoryPointer *store; + if ((err=handlesys->ReadHandle(hndl, g_MemoryPtr, &sec, (void **)&store)) != HandleError_None) + { + return pContext->ThrowNativeError("Could not read Handle %x (error %d)", hndl, err); + } + + ptr->StorePtr(store->Get(), params[3], params[4] != 0); + return 0; +} + +static cell_t MemoryPointer_LoadMemoryPointer(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMemoryPointer *ptr; + + HandleSecurity sec; + sec.pIdentity = g_pCoreIdent; + sec.pOwner = pContext->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_MemoryPtr, &sec, (void **)&ptr)) != HandleError_None) + { + return pContext->ThrowNativeError("Could not read Handle %x (error %d)", hndl, err); + } + + auto newPtr = new MemoryPointer(ptr->LoadPtr(params[2]), 0); + + Handle_t newHandle = handlesys->CreateHandle(g_MemoryPtr, newPtr, pContext->GetIdentity(), g_pCoreIdent, NULL); + if (newHandle == BAD_HANDLE) + { + delete newPtr; + return BAD_HANDLE; + } + + return newHandle; +} + +static cell_t MemoryPointer_Offset(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMemoryPointer *ptr; + + HandleSecurity sec; + sec.pIdentity = g_pCoreIdent; + sec.pOwner = pContext->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_MemoryPtr, &sec, (void **)&ptr)) != HandleError_None) + { + return pContext->ThrowNativeError("Could not read Handle %x (error %d)", hndl, err); + } + + auto newPtr = new MemoryPointer((void*)(((intptr_t)ptr->Get()) + params[2]), 0); + Handle_t newHandle = handlesys->CreateHandle(g_MemoryPtr, newPtr, pContext->GetIdentity(), g_pCoreIdent, NULL); + if (newHandle == BAD_HANDLE) + { + delete newPtr; + return BAD_HANDLE; + } + + return newHandle; +} + static cell_t FrameIterator_Create(IPluginContext *pContext, const cell_t *params) { IFrameIterator *it = pContext->CreateFrameIterator(); @@ -1160,6 +1338,13 @@ REGISTER_NATIVES(coreNatives) {"IsNullVector", IsNullVector}, {"IsNullString", IsNullString}, {"LogStackTrace", LogStackTrace}, + + {"MemoryPointer.MemoryPointer", MemoryPointer_Create}, + {"MemoryPointer.Store", MemoryPointer_Store}, + {"MemoryPointer.Load", MemoryPointer_Load}, + {"MemoryPointer.StoreMemoryPointer", MemoryPointer_StoreMemoryPointer}, + {"MemoryPointer.LoadMemoryPointer", MemoryPointer_LoadMemoryPointer}, + {"MemoryPointer.Offset", MemoryPointer_Offset}, {"FrameIterator.FrameIterator", FrameIterator_Create}, {"FrameIterator.Next", FrameIterator_Next}, diff --git a/core/logic/smn_gameconfigs.cpp b/core/logic/smn_gameconfigs.cpp index eaff876a6f..1c3fc4085e 100644 --- a/core/logic/smn_gameconfigs.cpp +++ b/core/logic/smn_gameconfigs.cpp @@ -31,6 +31,7 @@ #include "common_logic.h" #include +#include "MemoryPointer.h" #include "GameConfigs.h" HandleType_t g_GameConfigsType; @@ -194,6 +195,78 @@ static cell_t smn_GameConfGetMemSig(IPluginContext *pCtx, const cell_t *params) #endif } +extern HandleType_t g_MemoryPtr; + +static cell_t smn_GameConfGetAddressEx(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=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 = nullptr; + pCtx->LocalToString(params[2], &key); + + if (!gc->GetAddress(key, &val) || val == nullptr) + return BAD_HANDLE; + + auto newPtr = new MemoryPointer(val, 0); + Handle_t newHandle = handlesys->CreateHandle(g_MemoryPtr, newPtr, pCtx->GetIdentity(), g_pCoreIdent, NULL); + if (newHandle == BAD_HANDLE) + { + delete newPtr; + return BAD_HANDLE; + } + + return newHandle; +} + +static cell_t smn_GameConfGetMemSigEx(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=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 = nullptr; + pCtx->LocalToString(params[2], &key); + + if (!gc->GetMemSig(key, &val) || val == nullptr) + { + return BAD_HANDLE; + } + + auto newPtr = new MemoryPointer(val, 0); + Handle_t newHandle = handlesys->CreateHandle(g_MemoryPtr, newPtr, pCtx->GetIdentity(), g_pCoreIdent, NULL); + if (newHandle == BAD_HANDLE) + { + delete newPtr; + return BAD_HANDLE; + } + + return newHandle; +} + static GameConfigsNatives s_GameConfigsNatives; REGISTER_NATIVES(gameconfignatives) @@ -207,6 +280,10 @@ REGISTER_NATIVES(gameconfignatives) {"GameData.GameData", smn_LoadGameConfigFile}, {"GameData.GetOffset", smn_GameConfGetOffset}, {"GameData.GetKeyValue", smn_GameConfGetKeyValue}, + {"GameData.GetAddressEx", smn_GameConfGetAddressEx}, + {"GameData.GetMemSigEx", smn_GameConfGetMemSigEx}, + + // Deprecated {"GameData.GetAddress", smn_GameConfGetAddress}, {"GameData.GetMemSig", smn_GameConfGetMemSig}, {NULL, NULL} diff --git a/core/smn_entities.cpp b/core/smn_entities.cpp index 929b4a4a8f..6b5230a633 100644 --- a/core/smn_entities.cpp +++ b/core/smn_entities.cpp @@ -35,6 +35,7 @@ #include "PlayerManager.h" #include "HalfLife2.h" #include +#include #include "sm_stringutil.h" #include "logic_bridge.h" @@ -76,6 +77,44 @@ // Not defined in the sdk as we have no clue what it is #define FL_EP2V_UNKNOWN (1 << 2) +HandleType_t g_MemoryPtr = 0; + +class SimpleMemoryPointer : IMemoryPointer +{ +public: + SimpleMemoryPointer(void* ptr) : m_ptr(ptr) + { + } + + virtual void Delete() + { + delete this; + } + + virtual cell_t GetSize() override + { + return 0; + } + + virtual void* Get() override + { + return m_ptr; + } +protected: + void* m_ptr; +}; + +class EntitiesHelpers : + public SMGlobalClass +{ +public: + virtual void OnSourceModAllInitialized_Post() + { + // This should never fail + handlesys->FindHandleType("MemoryPointer", &g_MemoryPtr); + } +} s_EntitiesHelpers; + enum PropType { Prop_Send = 0, @@ -2779,6 +2818,87 @@ static cell_t GetEntityAddress(IPluginContext *pContext, const cell_t *params) #endif } +static cell_t GetEntityMemoryPointer(IPluginContext *pContext, const cell_t *params) +{ + CBaseEntity * pEntity = GetEntity(params[1]); + if (!pEntity) + { + return pContext->ThrowNativeError("Entity %d (%d) is invalid", g_HL2.ReferenceToIndex(params[1]), params[1]); + } + + + auto newPtr = new SimpleMemoryPointer(pEntity); + Handle_t newHandle = handlesys->CreateHandle(g_MemoryPtr, newPtr, pContext->GetIdentity(), g_pCoreIdent, NULL); + if (newHandle == BAD_HANDLE) + { + delete newPtr; + return BAD_HANDLE; + } + return newHandle; +} + +static cell_t MemoryPointer_StoreEntityToHandle(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMemoryPointer *ptr; + + HandleSecurity sec; + sec.pIdentity = g_pCoreIdent; + sec.pOwner = pContext->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_MemoryPtr, &sec, (void **)&ptr)) != HandleError_None) + { + return pContext->ThrowNativeError("Could not read Handle %x (error %d)", hndl, err); + } + + CBaseHandle &entityHandle = *reinterpret_cast(((intptr_t)ptr->Get()) + params[3]); + + if ((unsigned)params[2] == INVALID_EHANDLE_INDEX) + { + entityHandle.Set(NULL); + } + else + { + CBaseEntity *pOther = GetEntity(params[2]); + + if (!pOther) + { + return pContext->ThrowNativeError("Entity %d (%d) is invalid", g_HL2.ReferenceToIndex(params[2]), params[2]); + } + + IHandleEntity *pHandleEnt = (IHandleEntity *)pOther; + entityHandle.Set(pHandleEnt); + } + return 0; +} + +static cell_t MemoryPointer_LoadEntityFromHandle(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMemoryPointer *ptr; + + HandleSecurity sec; + sec.pIdentity = g_pCoreIdent; + sec.pOwner = pContext->GetIdentity(); + + if ((err=handlesys->ReadHandle(hndl, g_MemoryPtr, &sec, (void **)&ptr)) != HandleError_None) + { + return pContext->ThrowNativeError("Could not read Handle %x (error %d)", hndl, err); + } + + CBaseHandle &entityHandle = *reinterpret_cast(((intptr_t)ptr->Get()) + params[2]); + CBaseEntity *pHandleEntity = g_HL2.ReferenceToEntity(entityHandle.GetEntryIndex()); + + if (!pHandleEntity || entityHandle != reinterpret_cast(pHandleEntity)->GetRefEHandle()) + { + return -1; + } + + return g_HL2.EntityToBCompatRef(pHandleEntity); +} + REGISTER_NATIVES(entityNatives) { {"ChangeEdictState", ChangeEdictState}, @@ -2823,8 +2943,11 @@ REGISTER_NATIVES(entityNatives) {"SetEntPropString", SetEntPropString}, {"SetEntPropVector", SetEntPropVector}, {"GetEntityAddress", GetEntityAddress}, + {"GetEntityMemoryPointer", GetEntityMemoryPointer}, {"FindDataMapInfo", FindDataMapInfo}, {"LoadEntityFromHandleAddress", LoadEntityFromHandleAddress}, {"StoreEntityToHandleAddress", StoreEntityToHandleAddress}, + {"MemoryPointer.StoreEntityToHandle", MemoryPointer_StoreEntityToHandle}, + {"MemoryPointer.LoadEntityFromHandle", MemoryPointer_LoadEntityFromHandle}, {NULL, NULL} }; diff --git a/extensions/sdktools/extension.cpp b/extensions/sdktools/extension.cpp index 66ff208cec..c2d0a01574 100644 --- a/extensions/sdktools/extension.cpp +++ b/extensions/sdktools/extension.cpp @@ -92,6 +92,7 @@ SourceHook::CallClass *enginePatch = NULL; SourceHook::CallClass *enginesoundPatch = NULL; HandleType_t g_CallHandle = 0; HandleType_t g_TraceHandle = 0; +HandleType_t g_MemPtrHandle = 0; ISDKTools *g_pSDKTools; SMEXT_LINK(&g_SdkTools); @@ -169,6 +170,12 @@ bool SDKTools::SDK_OnLoad(char *error, size_t maxlength, bool late) return false; } + if (!handlesys->FindHandleType("MemoryPointer", &g_MemPtrHandle)) + { + ke::SafeSprintf(error, maxlength, "Could not find MemoryPointer handle type"); + return false; + } + #if SOURCE_ENGINE >= SE_ORANGEBOX g_pCVar = icvar; #endif diff --git a/extensions/sdktools/extension.h b/extensions/sdktools/extension.h index 877ebb95cf..4585dbd092 100644 --- a/extensions/sdktools/extension.h +++ b/extensions/sdktools/extension.h @@ -174,6 +174,7 @@ extern IGameHelpers *g_pGameHelpers; /* Handle types */ extern HandleType_t g_CallHandle; extern HandleType_t g_TraceHandle; +extern HandleType_t g_MemPtrHandle; /* Call Wrappers */ extern ICallWrapper *g_pAcceptInput; /* Timers */ diff --git a/extensions/sdktools/vcaller.cpp b/extensions/sdktools/vcaller.cpp index 3cabcc40ae..3371454fdd 100644 --- a/extensions/sdktools/vcaller.cpp +++ b/extensions/sdktools/vcaller.cpp @@ -30,6 +30,7 @@ #include "extension.h" #include "vcallbuilder.h" #include "vglobals.h" +#include "IMemoryPointer.h" enum SDKLibrary { @@ -63,7 +64,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_MemoryPointer) { flags = PASSFLAG_BYVAL | PASSFLAG_ASPOINTER; } else { @@ -181,6 +183,25 @@ static cell_t PrepSDKCall_SetAddress(IPluginContext *pContext, const cell_t *par return (s_call_addr != NULL) ? 1 : 0; } +static cell_t PrepSDKCall_SetAddressFromMemoryPointer(IPluginContext *pContext, const cell_t *params) +{ + IMemoryPointer* memPtr = nullptr; + + HandleSecurity security; + security.pIdentity = myself->GetIdentity(); + security.pOwner = pContext->GetIdentity(); + + HandleError err = HandleError_None; + Handle_t hndl = (Handle_t)params[1]; + if ((err = handlesys->ReadHandle(hndl, g_MemPtrHandle, &security, (void **)&memPtr)) != HandleError_None) + { + return pContext->ThrowNativeError("Could not read MemoryPointer Handle %x (error %d)", hndl, err); + } + + s_call_addr = memPtr->Get(); + return (s_call_addr != NULL) ? 1 : 0; +} + // Must match same enum in sdktools.inc enum SDKFuncConfSource { @@ -413,6 +434,44 @@ static cell_t SDKCall(IPluginContext *pContext, const cell_t *params) startparam++; } break; + case ValveCall_MemoryPointer: + { + //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); + Handle_t hndl = (Handle_t)(*cell); + + if (hndl == 0) + { + vc->stk_put(ptr); + return pContext->ThrowNativeError("MemoryPointer handle cannot be null"); + } + + IMemoryPointer* memPtr = nullptr; + + HandleSecurity security; + security.pIdentity = myself->GetIdentity(); + security.pOwner = pContext->GetIdentity(); + + HandleError err = HandleError_None; + if ((err = handlesys->ReadHandle(hndl, g_MemPtrHandle, &security, (void **)&memPtr)) != HandleError_None) + { + pContext->ThrowNativeError("Could not read MemoryPointer Handle %x (error %d)", hndl, err); + return Data_Fail; + } + + *(void **)ptr = memPtr->Get(); + startparam++; + } + break; default: { vc->stk_put(ptr); @@ -429,7 +488,8 @@ static cell_t SDKCall(IPluginContext *pContext, const cell_t *params) { startparam += 2; } else if (vc->retinfo->vtype == Valve_Vector - || vc->retinfo->vtype == Valve_QAngle) + || vc->retinfo->vtype == Valve_QAngle + || vc->retinfo->vtype == Valve_MemoryPointer) { startparam += 1; } @@ -506,11 +566,12 @@ static cell_t SDKCall(IPluginContext *pContext, const cell_t *params) 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) + || vc->retinfo->vtype == Valve_QAngle + || vc->retinfo->vtype == Valve_MemoryPointer) { if (numparams < 2) { - return pContext->ThrowNativeError("Expected argument (2) for Float[3] storage"); + return pContext->ThrowNativeError("Expected argument (2) for return storage"); } if (EncodeValveParam(pContext, params[retparam], vc, vc->retinfo, vc->retbuf) == Data_Fail) @@ -555,6 +616,7 @@ sp_nativeinfo_t g_CallNatives[] = {"PrepSDKCall_SetVirtual", PrepSDKCall_SetVirtual}, {"PrepSDKCall_SetSignature", PrepSDKCall_SetSignature}, {"PrepSDKCall_SetAddress", PrepSDKCall_SetAddress}, + {"PrepSDKCall_SetAddressFromMemoryPointer", PrepSDKCall_SetAddressFromMemoryPointer}, {"PrepSDKCall_SetFromConf", PrepSDKCall_SetFromConf}, {"PrepSDKCall_SetReturnInfo", PrepSDKCall_SetReturnInfo}, {"PrepSDKCall_AddParameter", PrepSDKCall_AddParameter}, diff --git a/extensions/sdktools/vdecoder.cpp b/extensions/sdktools/vdecoder.cpp index 881fa667a9..466cd23cd7 100644 --- a/extensions/sdktools/vdecoder.cpp +++ b/extensions/sdktools/vdecoder.cpp @@ -34,9 +34,36 @@ #include "vdecoder.h" #include "vcallbuilder.h" +#include + using namespace SourceMod; using namespace SourcePawn; +class ForeignMemoryPointer : public IMemoryPointer +{ +public: + ForeignMemoryPointer(void* ptr) : m_ptr(ptr) + { + } + + virtual void Delete() override + { + delete this; + } + + virtual void* Get() override + { + return m_ptr; + } + + virtual cell_t GetSize() override + { + return 0; + } +protected: + void* m_ptr; +}; + /** * For object pointers, the data looks like this instead: * 4 bytes: POINTER TO LATER @@ -164,6 +191,21 @@ size_t ValveParamToBinParam(ValveType type, return sizeof(float); } } + case Valve_MemoryPointer: + { + 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 +318,37 @@ DataStatus EncodeValveParam(IPluginContext *pContext, *addr = *(bool *)buffer ? 1 : 0; + return Data_Okay; + } + case Valve_MemoryPointer: + { + cell_t *addr; + pContext->LocalToPhysAddr(param, &addr); + + if (data->flags & PASSFLAG_ASPOINTER) + { + buffer = *(void ***)buffer; + } + + auto ptr = *(void **)buffer; + + if (ptr == nullptr) + { + *addr = 0; + return Data_Okay; + } + + HandleError err = HandleError_None; + Handle_t hndl = handlesys->CreateHandle(g_MemPtrHandle, new ForeignMemoryPointer(ptr), pContext->GetIdentity(), myself->GetIdentity(), &err); + + if (err != HandleError_None) + { + pContext->ThrowNativeError("Failed to create MemoryPointer while decoding (error: %d)", err); + return Data_Fail; + } + + *addr = hndl; + return Data_Okay; } } @@ -579,6 +652,39 @@ DataStatus DecodeValveParam(IPluginContext *pContext, *(char **)buffer = addr; return Data_Okay; } + case Valve_MemoryPointer: + { + IMemoryPointer* ptr = nullptr; + + HandleSecurity security; + security.pIdentity = myself->GetIdentity(); + security.pOwner = pContext->GetIdentity(); + + cell_t* addr; + pContext->LocalToPhysAddr(param, &addr); + Handle_t hndl = (Handle_t)*addr; + + if (hndl == 0) + { + if (data->decflags & VDECODE_FLAG_ALLOWNULL) + { + *(void **)buffer = nullptr; + return Data_Okay; + } + pContext->ThrowNativeError("Null/Invalid Handle MemoryPointer isn't allowed"); + return Data_Fail; + } + + HandleError err = HandleError_None; + if ((err = handlesys->ReadHandle(hndl, g_MemPtrHandle, &security, (void **)&ptr)) != HandleError_None) + { + pContext->ThrowNativeError("Could not read MemoryPointer Handle %x (error %d)", hndl, err); + return Data_Fail; + } + + *(void **)buffer = ptr->Get(); + return Data_Okay; + } } return Data_Fail; diff --git a/extensions/sdktools/vdecoder.h b/extensions/sdktools/vdecoder.h index 16478cf101..890945a89d 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_MemoryPointer, /**< Sourcemod's IMemoryPointer */ 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_MemoryPointer /**< Thiscall (Sourcemod's IMemoryPointer handle implicit first parameter */ }; /** diff --git a/plugins/include/entity.inc b/plugins/include/entity.inc index 0bebb6a1a3..d48c9555c1 100644 --- a/plugins/include/entity.inc +++ b/plugins/include/entity.inc @@ -751,8 +751,18 @@ stock void SetEntDataArray(int entity, int offset, const any[] array, int arrayS * @return Address of the entity. * @error Invalid entity. */ +#pragma deprecated Use GetEntityMemoryPointer instead native Address GetEntityAddress(int entity); +/** + * Gets the memory address of an entity and wraps into a new MemoryPointer handle. + * + * @param entity Entity index. + * @return New MemoryPointer handle. + * @error Invalid entity. + */ +native MemoryPointer GetEntityMemoryPointer(int entity); + /** * Retrieves the classname of an entity. * This is like GetEdictClassname(), except it works for ALL @@ -774,6 +784,7 @@ stock bool GetEntityClassname(int entity, char[] clsname, int maxlength) * @param addr Address to a memory location. * @return Entity index at the given location. If there is no entity, or the stored entity is invalid, then -1 is returned. */ +#pragma deprecated Use MemoryPointer.LoadEntityFromHandle instead native int LoadEntityFromHandleAddress(Address addr); /** @@ -782,4 +793,5 @@ native int LoadEntityFromHandleAddress(Address addr); * @param addr Address to a memory location. * @param entity Entity index to set, or -1 to clear. */ +#pragma deprecated Use MemoryPointer.StoreEntityToHandle instead native void StoreEntityToHandleAddress(Address addr, int entity); diff --git a/plugins/include/sdktools.inc b/plugins/include/sdktools.inc index 07fc55f82f..92fb31a796 100644 --- a/plugins/include/sdktools.inc +++ b/plugins/include/sdktools.inc @@ -62,7 +62,8 @@ enum SDKCallType SDKCall_EntityList, /**< CGlobalEntityList call */ SDKCall_Raw, /**< |this| pointer with an arbitrary address */ SDKCall_Server, /**< CBaseServer call */ - SDKCall_Engine /**< CVEngineServer call */ + SDKCall_Engine, /**< CVEngineServer call */ + SDKCall_MemoryPointer /**< |this| pointer retrieved from a MemoryPointer handle */ }; 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_MemoryPointer /**< Sourcemod MemoryPointer handle */ }; enum SDKPassMethod @@ -138,8 +140,18 @@ native bool PrepSDKCall_SetSignature(SDKLibrary lib, const char[] signature, int * @param addr Address of function to use. * @return True on success, false on failure. */ +#pragma deprecated Use PrepSDKCall_SetAddressFromMemoryPointer instead native bool PrepSDKCall_SetAddress(Address addr); +/** + * Uses the given pointer value as function address for the SDK call. + * + * @param handle Handle to a MemoryPointer. + * @return True on success, false on failure. + * @error Invalid handle. + */ +native bool PrepSDKCall_SetAddressFromMemoryPointer(MemoryPointer handle); + /** * Finds an address or virtual function index in a GameConfig file and sets it as * the calling information for the SDK call. diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index 1927015680..9e949197fb 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -113,13 +113,28 @@ methodmap GameData < Handle // // @param name Name of the property to find. // @return An address calculated on success, or 0 on failure. + #pragma deprecated Use GameData.GetAddressEx instead public native Address GetAddress(const char[] name); + // Finds an address calculation in a GameConfig file, + // performs LoadFromAddress on it as appropriate, then returns the final address as a new MemoryPointer handle. + // + // @param name Name of the property to find. + // @return New MemoryPointer handle containing the address calculated on success, or null on failure. + public native MemoryPointer GetAddressEx(const char[] name); + // Returns a function address calculated from a signature. // // @param name Name of the property to find. // @return An address calculated on success, or 0 on failure. + #pragma deprecated Use GameData.GetMemSigEx instead public native Address GetMemSig(const char[] name); + + // Returns a function address calculated from a signature as new MemoryPointer handle. + // + // @param name Name of the property to find. + // @return New MemoryPointer handle containing the address calculated on success, or null on failure. + public native MemoryPointer GetMemSigEx(const char[] name); }; /** @@ -467,6 +482,7 @@ native bool GameConfGetKeyValue(Handle gc, const char[] key, char[] buffer, int * @param name Name of the property to find. * @return An address calculated on success, or 0 on failure. */ +#pragma deprecated Use GameData.GetAddressEx instead native Address GameConfGetAddress(Handle gameconf, const char[] name); /** @@ -739,6 +755,7 @@ enum Address * @return The value that is stored at that address. * @error Address is null or pointing to reserved memory. */ +#pragma deprecated Use MemoryPointer.Load instead native any LoadFromAddress(Address addr, NumberType size); /** @@ -752,8 +769,57 @@ native any LoadFromAddress(Address addr, NumberType size); * on the memory page being written to. * @error Address is null or pointing to reserved memory. */ +#pragma deprecated Use MemoryPointer.Store instead native void StoreToAddress(Address addr, any data, NumberType size, bool updateMemAccess = true); +methodmap MemoryPointer < Handle { + // Creates a memory block of the given bytes size. + // And wrap its pointer into a MemoryPointer handle. + // @return New Handle to a memory pointer. + public native MemoryPointer(int size); + + // Stores the given data at the provided offset. + // @param data The data to store. + // @param size Size of the data to store. + // @param offset Offset in bytes from the start. + // @param updateMemAccess If true, SourceMod will set read / write / exec permissions + // on the memory page being written to. + public native void Store(any data, NumberType size, int offset = 0, bool updateMemAccess = true); + + // Retrieves the data at the provided offset. + // @param size Size of the data to store. + // @param offset Offset in bytes from the start. + // @return The data that was stored. + public native any Load(NumberType size, int offset = 0); + + // Stores the given entity index into a CHandle at the provided offset. + // @param entity Entity index to store, -1 to clear. + // @param offset Offset in bytes from the start. + public native void StoreEntityToHandle(int entity, int offset = 0); + + // Retrieves the entity index from the CHandle at the provided offset. + // @param offset Offset in bytes from the start. + // @return Entity index at the given location. If there is no entity, or the stored entity is invalid, then -1 is returned. + public native int LoadEntityFromHandle(int offset = 0); + + // Stores a memory pointer at the provided offset. + // @param handle Handle to the memory pointer to store. + // @param offset Offset in bytes from the start. + // @param updateMemAccess If true, SourceMod will set read / write / exec permissions + // on the memory page being written to. + public native void StoreMemoryPointer(MemoryPointer handle, int offset = 0, bool updateMemAccess = true); + + // Wraps the data loaded at the provided offset into a new MemoryPointer handle. + // @param offset Offset in bytes from the start. + // @return New Handle to a memory pointer. + public native MemoryPointer LoadMemoryPointer(int offset = 0); + + // Creates a new MemoryPointer handle by offsetting the base pointer. + // @param size Offset from base pointer in bytes. + // @return New Handle to a memory pointer. + public native MemoryPointer Offset(int offset); +}; + methodmap FrameIterator < Handle { // Creates a stack frame iterator to build your own stack traces. // @return New handle to a FrameIterator. diff --git a/public/IMemoryPointer.h b/public/IMemoryPointer.h new file mode 100644 index 0000000000..968dce1296 --- /dev/null +++ b/public/IMemoryPointer.h @@ -0,0 +1,147 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2024 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * 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$ + */ + +#pragma once + +#include +#include +#include +#include + +namespace SourceMod +{ + class IMemoryPointer + { + public: + virtual ~IMemoryPointer() = default; + + /** + * @brief Deletes the Memory pointer. + */ + virtual void Delete() = 0; + + /** + * @brief Retrieves the underlying pointer. + * + * @return The underlying pointer. + */ + virtual void* Get() = 0; + + /** + * @brief Approximate size in bytes of the memory block pointed by the pointer. + * + * @return The pointed memory size in bytes, 0 if size is unknown. + */ + virtual cell_t GetSize() = 0; + + /** + * @brief Stores data at the given offset. + * + * @param data The data to store. + * @param byteSize Size of the data in bytes. + * @param offset Offset in bytes to store the data at. + * @param updateMemAccess Whether or not to update the memory access before writing. + */ + void Store(cell_t data, unsigned int byteSize, cell_t offset, bool updateMemAccess); + + /** + * @brief Loads data at the given offset. + * + * @param byteSize Size of the data in bytes. + * @param offset Offset in bytes to read the data at. + * @return The data stored at the given offset. + */ + cell_t Load(unsigned int byteSize, cell_t offset); + + /** + * @brief Stores a pointer at the given offset. + * + * @param data The pointer to store. + * @param offset Offset in bytes to store the data at. + * @param updateMemAccess Whether or not to update the memory access before writing. + */ + void StorePtr(void* data, cell_t offset, bool updateMemAccess); + + /** + * @brief Loads pointer at the given offset. + * + * @param offset Offset in bytes to read the data at. + * @return The pointer stored at the given offset. + */ + void* LoadPtr(cell_t offset); + }; + + inline void IMemoryPointer::StorePtr(void* data, cell_t offset, bool updateMemAccess) + { + auto ptr = &(((std::int8_t*)this->Get())[offset]); + if (updateMemAccess) + { + SourceHook::SetMemAccess(ptr, sizeof(void*), SH_MEM_READ|SH_MEM_WRITE|SH_MEM_EXEC); + } + + *(void**)ptr = data; + } + + inline void* IMemoryPointer::LoadPtr(cell_t offset) + { + auto ptr = &(((std::int8_t*)this->Get())[offset]); + + return *(void**)ptr; + } + + inline void IMemoryPointer::Store(cell_t data, unsigned int byteSize, cell_t offset, bool updateMemAccess) + { + auto ptr = &(((std::int8_t*)this->Get())[offset]); + if (updateMemAccess) + { + SourceHook::SetMemAccess(ptr, byteSize, SH_MEM_READ|SH_MEM_WRITE|SH_MEM_EXEC); + } + + memcpy(ptr, &data, byteSize); + } + + inline cell_t IMemoryPointer::Load(unsigned int byteSize, cell_t offset) + { + auto ptr = &(((std::int8_t*)this->Get())[offset]); + + switch(byteSize) + { + case 1: + return *(std::int8_t*)(ptr); + case 2: + return *(std::int16_t*)(ptr); + case 4: + return *(std::int32_t*)(ptr); + default: + return 0; + } + } +} \ No newline at end of file