Introduce MemoryPointer

This commit is contained in:
Kenzzer 2024-07-27 13:54:03 +02:00
parent b7feeef350
commit 33102593bc
No known key found for this signature in database
GPG Key ID: A4474D96720FD722
12 changed files with 502 additions and 7 deletions

View File

@ -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':

View File

@ -0,0 +1,61 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#include "MemoryPointer.h"
#include <sourcehook.h>
#include <sh_memory.h>
#include <algorithm>
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)
{
free(m_ptr);
}
}
void* MemoryPointer::Get()
{
return m_ptr;
}
cell_t MemoryPointer::GetSize()
{
return m_size;
}

View File

@ -0,0 +1,53 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#pragma once
#include <memory>
#include <cstdint>
#include <sp_vm_types.h>
#include <IMemoryPointer.h>
class MemoryPointer : public SourceMod::IMemoryPointer
{
public:
MemoryPointer(cell_t size);
MemoryPointer(void* ptr, cell_t size);
// SourceMod::IMemoryPointer
virtual ~MemoryPointer();
virtual void* Get() override;
virtual cell_t GetSize() override;
protected:
void* m_ptr;
bool m_owned;
cell_t m_size;
};

View File

@ -42,6 +42,7 @@
#include <ITranslator.h>
#include <DebugReporter.h>
#include <FrameIterator.h>
#include <MemoryPointer.h>
#include <sourcehook.h>
#include <sh_memory.h>
@ -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,12 @@ public:
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 security;
security.access[HandleAccess_Read] = 0;
security.access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER;
security.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
g_MemoryPtr = handlesys->CreateType("MemoryPointer", this, 0, NULL, &security, g_pCoreIdent, NULL);
g_OnLogAction = forwardsys->CreateForward("OnLogAction",
ET_Hook,
5,
@ -100,7 +108,11 @@ public:
}
void OnHandleDestroy(HandleType_t type, void *object)
{
if (type == g_FrameIter)
if (type == g_MemoryPtr)
{
delete (IMemoryPointer *) object;
}
else if (type == g_FrameIter)
{
delete (SafeFrameIterator *) object;
}
@ -115,6 +127,7 @@ public:
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 +984,90 @@ 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 FrameIterator_Create(IPluginContext *pContext, const cell_t *params)
{
IFrameIterator *it = pContext->CreateFrameIterator();
@ -1160,6 +1257,10 @@ REGISTER_NATIVES(coreNatives)
{"IsNullVector", IsNullVector},
{"IsNullString", IsNullString},
{"LogStackTrace", LogStackTrace},
{"MemoryPointer.MemoryPointer", MemoryPointer_Create},
{"MemoryPointer.Store", MemoryPointer_Store},
{"MemoryPointer.Load", MemoryPointer_Load},
{"FrameIterator.FrameIterator", FrameIterator_Create},
{"FrameIterator.Next", FrameIterator_Next},

View File

@ -92,6 +92,7 @@ SourceHook::CallClass<IVEngineServer> *enginePatch = NULL;
SourceHook::CallClass<IEngineSound> *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

View File

@ -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 */

View File

@ -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 {
@ -413,6 +415,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 +469,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 +547,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)

View File

@ -34,9 +34,31 @@
#include "vdecoder.h"
#include "vcallbuilder.h"
#include <IMemoryPointer.h>
using namespace SourceMod;
using namespace SourcePawn;
class ForeignMemoryPointer : public IMemoryPointer
{
public:
ForeignMemoryPointer(void* ptr) : m_ptr(ptr)
{
}
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 +186,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 +313,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 +647,37 @@ 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();
Handle_t hndl = (Handle_t)param;
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;

View File

@ -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 */
};
/**

View File

@ -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

View File

@ -754,6 +754,25 @@ native any LoadFromAddress(Address addr, NumberType size);
*/
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.
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);
};
methodmap FrameIterator < Handle {
// Creates a stack frame iterator to build your own stack traces.
// @return New handle to a FrameIterator.

107
public/IMemoryPointer.h Normal file
View File

@ -0,0 +1,107 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#pragma once
#include <sourcehook.h>
#include <sh_memory.h>
#include <cstdint>
#include <sp_vm_types.h>
namespace SourceMod
{
class IMemoryPointer
{
public:
virtual ~IMemoryPointer() = default;
/**
* @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.
*/
virtual 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.
*/
virtual cell_t Load(unsigned int byteSize, cell_t offset);
};
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;
}
}
}