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