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