diff --git a/modules/fakemeta/AMBuilder b/modules/fakemeta/AMBuilder index bf6c495f..710be0d5 100644 --- a/modules/fakemeta/AMBuilder +++ b/modules/fakemeta/AMBuilder @@ -13,6 +13,7 @@ binary.sources = [ 'engfunc.cpp', 'fakemeta_amxx.cpp', 'pdata.cpp', + 'pdata_gc.cpp', 'forward.cpp', 'fm_tr.cpp', 'pev.cpp', diff --git a/modules/fakemeta/fakemeta_amxx.cpp b/modules/fakemeta/fakemeta_amxx.cpp index 77f2f9da..21c3358d 100644 --- a/modules/fakemeta/fakemeta_amxx.cpp +++ b/modules/fakemeta/fakemeta_amxx.cpp @@ -16,6 +16,11 @@ edict_t *g_player_edicts[33]; // Used for INDEXENT() forward. +IGameConfig *CommonConfig; +IGameConfigManager *ConfigManager; + +HLTypeConversion TypeConversion; + void OnAmxxAttach() { initialze_offsets(); @@ -33,6 +38,25 @@ void OnAmxxAttach() g_kvd_glb.kvd.szKeyName = const_cast(g_kvd_glb.key.chars()); g_kvd_glb.kvd.szValue = const_cast(g_kvd_glb.val.chars()); g_kvd_glb.kvd.fHandled = 0; + + ConfigManager = MF_GetConfigManager(); + + char error[256]; + error[0] = '\0'; + + if (!ConfigManager->LoadGameConfigFile("common.games", &CommonConfig, error, sizeof(error)) && error[0] != '\0') + { + MF_Log("Could not read common.games gamedata: %s", error); + MF_Log("get/set/find_ent_data* natives have been disabled"); + return; + } + + MF_AddNatives(pdata_gc_natives); +} + +void OnPluginsLoaded() +{ + TypeConversion.init(); } extern CStack g_FreeTRs; diff --git a/modules/fakemeta/fakemeta_amxx.h b/modules/fakemeta/fakemeta_amxx.h index 094d4bac..ea0757c7 100644 --- a/modules/fakemeta/fakemeta_amxx.h +++ b/modules/fakemeta/fakemeta_amxx.h @@ -25,6 +25,8 @@ #include "glb.h" #include #include +#include +#include extern edict_t *g_player_edicts[33]; @@ -55,13 +57,14 @@ inline edict_t* INDEXENT2( int iEdictNum ) } #endif -#define CHECK_ENTITY(x) if (x != 0 && (FNullEnt(INDEXENT2(x)) || x < 0 || x > gpGlobals->maxEntities)) { MF_LogError(amx, AMX_ERR_NATIVE, "Invalid entity"); return 0; } +#define CHECK_ENTITY(x) if (x != 0 && (FNullEnt(TypeConversion.id_to_edict(x)) || x < 0 || x > gpGlobals->maxEntities)) { MF_LogError(amx, AMX_ERR_NATIVE, "Invalid entity"); return 0; } #define CHECK_OFFSET(x) if (x < 0) { MF_LogError(amx, AMX_ERR_NATIVE, "Invalid offset"); return 0; } extern AMX_NATIVE_INFO engfunc_natives[]; extern AMX_NATIVE_INFO dllfunc_natives[]; extern AMX_NATIVE_INFO forward_natives[]; extern AMX_NATIVE_INFO pdata_natives[]; +extern AMX_NATIVE_INFO pdata_gc_natives[]; extern AMX_NATIVE_INFO tr_Natives[]; extern AMX_NATIVE_INFO pev_natives[]; extern AMX_NATIVE_INFO glb_natives[]; @@ -80,5 +83,10 @@ extern enginefuncs_t *g_pengfuncsTable_Post; extern NEW_DLL_FUNCTIONS *g_pNewFunctionsTable; extern NEW_DLL_FUNCTIONS *g_pNewFunctionsTable_Post; +extern IGameConfig *CommonConfig; +extern IGameConfigManager *ConfigManager; + +extern HLTypeConversion TypeConversion; + #endif //_FAKEMETA_INCLUDE_H diff --git a/modules/fakemeta/moduleconfig.h b/modules/fakemeta/moduleconfig.h index 9f7d0e2b..052ead4b 100644 --- a/modules/fakemeta/moduleconfig.h +++ b/modules/fakemeta/moduleconfig.h @@ -68,7 +68,7 @@ /** All plugins loaded * Do forward functions init here (MF_RegisterForward) */ -//#define FN_AMXX_PLUGINSLOADED OnPluginsLoaded +#define FN_AMXX_PLUGINSLOADED OnPluginsLoaded /** All plugins are about to be unloaded */ //#define FN_AMXX_PLUGINSUNLOADING OnPluginsUnloading diff --git a/modules/fakemeta/msvc12/fakemeta.vcxproj b/modules/fakemeta/msvc12/fakemeta.vcxproj index cb50a1ce..1e611b80 100644 --- a/modules/fakemeta/msvc12/fakemeta.vcxproj +++ b/modules/fakemeta/msvc12/fakemeta.vcxproj @@ -54,7 +54,7 @@ Disabled - ..\;..\..\..\public;..\..\..\public\sdk; ..\..\..\public\amtl\include;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories) + ..\;..\..\..\public;..\..\..\public\sdk;..\..\..\public\amtl\include;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;FAKEMETA_EXPORTS;HAVE_STDINT_H;%(PreprocessorDefinitions) true EnableFastChecks @@ -77,7 +77,7 @@ - ..\;..\..\..\public;..\..\..\public\sdk; ..\..\..\public\amtl\include;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories) + ..\;..\..\..\public;..\..\..\public\sdk;..\..\..\public\amtl\include;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;FAKEMETA_EXPORTS;HAVE_STDINT_H;%(PreprocessorDefinitions) MultiThreaded false @@ -103,12 +103,14 @@ + + diff --git a/modules/fakemeta/msvc12/fakemeta.vcxproj.filters b/modules/fakemeta/msvc12/fakemeta.vcxproj.filters index 401868f4..3a04028d 100644 --- a/modules/fakemeta/msvc12/fakemeta.vcxproj.filters +++ b/modules/fakemeta/msvc12/fakemeta.vcxproj.filters @@ -68,6 +68,9 @@ Module SDK\SDK Base + + Source Files + @@ -103,6 +106,9 @@ Module SDK\SDK Base + + Header Files + diff --git a/modules/fakemeta/pdata_gc.cpp b/modules/fakemeta/pdata_gc.cpp new file mode 100644 index 00000000..e61ef5d4 --- /dev/null +++ b/modules/fakemeta/pdata_gc.cpp @@ -0,0 +1,537 @@ +// vim: set ts=4 sw=4 tw=99 noet: +// +// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). +// Copyright (C) The AMX Mod X Development Team. +// +// This software is licensed under the GNU General Public License, version 3 or higher. +// Additional exceptions apply. For full license details, see LICENSE.txt or visit: +// https://alliedmods.net/amxmodx-license + +// +// Fakemeta Module +// + +#include "fakemeta_amxx.h" +#include + +enum class BaseFieldType +{ + None, + Integer, + Float, + Vector, + Entity, + String, +}; + +static const char *BaseFieldTypeName[] = +{ + "none", + "integer", + "float", + "vector", + "entity", + "string", +}; + +static BaseFieldType GetBaseDataType(TypeDescription &data) +{ + switch (data.fieldType) + { + case FieldType::FIELD_INTEGER: + case FieldType::FIELD_STRINGINT: + case FieldType::FIELD_SHORT: + case FieldType::FIELD_CHARACTER: + case FieldType::FIELD_CLASS: + case FieldType::FIELD_STRUCTURE: + case FieldType::FIELD_POINTER: + case FieldType::FIELD_FUNCTION: + case FieldType::FIELD_BOOLEAN: + { + return BaseFieldType::Integer; + } + case FieldType::FIELD_FLOAT: + { + return BaseFieldType::Float; + } + case FieldType::FIELD_VECTOR: + { + return BaseFieldType::Vector; + } + case FieldType::FIELD_CLASSPTR: + case FieldType::FIELD_ENTVARS: + case FieldType::FIELD_EDICT: + case FieldType::FIELD_EHANDLE: + { + return BaseFieldType::Entity; + } + case FieldType::FIELD_STRINGPTR: + case FieldType::FIELD_STRING: + { + return BaseFieldType::String; + } + } + + return BaseFieldType::None; +} + +#define GET_TYPE_DESCRIPTION(position, data, baseType) \ + int classLength, memberLength; \ + const char *className = MF_GetAmxString(amx, params[position], 0, &classLength); \ + const char *memberName = MF_GetAmxString(amx, params[position + 1], 1, &memberLength); \ + if (!classLength || !memberLength) \ + { \ + MF_LogError(amx, AMX_ERR_NATIVE, "Either class (\"%s\") or member (\"%s\") is empty", className, memberName); \ + return 0; \ + } \ + else if (!CommonConfig->GetOffsetByClass(className, memberName, &data)) \ + { \ + MF_LogError(amx, AMX_ERR_NATIVE, "Could not find class \"%s\" and/or member \"%s\" in gamedata", className, memberName); \ + return 0; \ + } \ + else if (data.fieldOffset < 0) \ + { \ + MF_LogError(amx, AMX_ERR_NATIVE, "Invalid offset %d retrieved from \"%s\" member", data.fieldOffset, memberName); \ + return 0; \ + } \ + else if (baseType > BaseFieldType::None && baseType != GetBaseDataType(data)) \ + { \ + MF_LogError(amx, AMX_ERR_NATIVE, "Data field is not %s-based", BaseFieldTypeName[static_cast(baseType)]); \ + return 0; \ + } + +#define CHECK_ELEMENT(element) \ + if (element < 0 || (element > 0 && element >= data.fieldSize)) \ + { \ + MF_LogError(amx, AMX_ERR_NATIVE, "Invalid element index %d, value must be between 0 and %d", element, data.fieldSize); \ + return 0; \ + } \ + else if (element > 0 && !data.fieldSize) \ + { \ + MF_LogError(amx, AMX_ERR_NATIVE, "Member \"%s\" is not an array. Element %d is invalid.", memberName, element);\ + return 0; \ + } + +// native any:get_ent_data(entity, const class[], const member[], element = 0); +static cell AMX_NATIVE_CALL get_ent_data(AMX *amx, cell *params) +{ + int entity = params[1]; + CHECK_ENTITY(entity); + + TypeDescription data; + GET_TYPE_DESCRIPTION(2, data, BaseFieldType::Integer); + + int element = params[4]; + CHECK_ELEMENT(element); + + auto pEntity = TypeConversion.id_to_edict(entity); + + switch (data.fieldType) + { + case FieldType::FIELD_INTEGER: + case FieldType::FIELD_STRINGINT: + { + return get_pdata(pEntity, data.fieldOffset, element); + } + case FieldType::FIELD_CLASS: + case FieldType::FIELD_STRUCTURE: + { + return reinterpret_cast(reinterpret_cast(pEntity->pvPrivateData) + data.fieldOffset); + } + case FieldType::FIELD_POINTER: + case FieldType::FIELD_FUNCTION: + { + return reinterpret_cast(get_pdata(pEntity, data.fieldOffset, element)); + } + case FieldType::FIELD_SHORT: + { + if (data.fieldUnsigned) + { + return get_pdata(pEntity, data.fieldOffset, element); + } + else + { + return get_pdata(pEntity, data.fieldOffset, element); + } + } + case FieldType::FIELD_CHARACTER: + { + if (data.fieldUnsigned) + { + return get_pdata(pEntity, data.fieldOffset, element); + } + else + { + return get_pdata(pEntity, data.fieldOffset, element); + } + } + case FieldType::FIELD_BOOLEAN: + { + return get_pdata(pEntity, data.fieldOffset, element) ? 1 : 0; + } + } + + return 0; +} + +// native set_ent_data(entity, const class[], const member[], any:value, element = 0); +static cell AMX_NATIVE_CALL set_ent_data(AMX *amx, cell *params) +{ + int entity = params[1]; + CHECK_ENTITY(entity); + + TypeDescription data; + GET_TYPE_DESCRIPTION(2, data, BaseFieldType::Integer); + + int element = params[5]; + CHECK_ELEMENT(element); + + auto pEntity = TypeConversion.id_to_edict(entity); + auto value = params[4]; + + switch (data.fieldType) + { + case FieldType::FIELD_INTEGER: + case FieldType::FIELD_STRINGINT: + { + set_pdata(pEntity, data.fieldOffset, static_cast(value), element); + break; + } + case FieldType::FIELD_CLASS: + case FieldType::FIELD_STRUCTURE: + { + MF_LogError(amx, AMX_ERR_NATIVE, "Setting directly to a class or structure address is not available"); + return 0; + } + case FieldType::FIELD_POINTER: + case FieldType::FIELD_FUNCTION: + { + set_pdata(pEntity, data.fieldOffset, reinterpret_cast(value), element); + break; + } + case FieldType::FIELD_SHORT: + { + if (data.fieldUnsigned) + { + set_pdata(pEntity, data.fieldOffset, static_cast(value), element); + } + else + { + set_pdata(pEntity, data.fieldOffset, static_cast(value), element); + } + break; + } + case FieldType::FIELD_CHARACTER: + { + if (data.fieldUnsigned) + { + set_pdata(pEntity, data.fieldOffset, static_cast(value), element); + } + else + { + set_pdata(pEntity, data.fieldOffset, static_cast(value), element); + } + break; + } + case FieldType::FIELD_BOOLEAN: + { + set_pdata(pEntity, data.fieldOffset, value != 0, element); + break; + } + } + + return 0; +} + + +// native Float:get_ent_data_float(entity, const class[], const member[], element = 0); +static cell AMX_NATIVE_CALL get_ent_data_float(AMX *amx, cell *params) +{ + int entity = params[1]; + CHECK_ENTITY(entity); + + TypeDescription data; + GET_TYPE_DESCRIPTION(2, data, BaseFieldType::Float); + + int element = params[4]; + CHECK_ELEMENT(element); + + return amx_ftoc(get_pdata(TypeConversion.id_to_edict(entity), data.fieldOffset, element)); +} + +// native set_ent_data_float(entity, const classname[], const member[], Float:value, element = 0); +static cell AMX_NATIVE_CALL set_ent_data_float(AMX *amx, cell *params) +{ + int entity = params[1]; + CHECK_ENTITY(entity); + + TypeDescription data; + GET_TYPE_DESCRIPTION(2, data, BaseFieldType::Float); + + int element = params[5]; + CHECK_ELEMENT(element); + + set_pdata(TypeConversion.id_to_edict(entity), data.fieldOffset, amx_ctof(params[4]), element); + + return 1; +} + + +// native get_ent_data_vector(entity, const class[], const member[], Float:value[3], element = 0); +static cell AMX_NATIVE_CALL get_ent_data_vector(AMX *amx, cell *params) +{ + int entity = params[1]; + CHECK_ENTITY(entity); + + TypeDescription data; + GET_TYPE_DESCRIPTION(2, data, BaseFieldType::Vector); + + int element = params[5]; + CHECK_ELEMENT(element); + + auto refvec = MF_GetAmxAddr(amx, params[4]); + auto vector = get_pdata(TypeConversion.id_to_edict(entity), data.fieldOffset, element); + + refvec[0] = amx_ftoc(vector.x); + refvec[1] = amx_ftoc(vector.y); + refvec[2] = amx_ftoc(vector.z); + + return 1; +} + +// native set_ent_data_vector(entity, const class[], const member[], Float:value[3], element = 0); +static cell AMX_NATIVE_CALL set_ent_data_vector(AMX *amx, cell *params) +{ + int entity = params[1]; + CHECK_ENTITY(entity); + + TypeDescription data; + GET_TYPE_DESCRIPTION(2, data, BaseFieldType::Vector); + + int element = params[5]; + CHECK_ELEMENT(element); + + auto refvec = MF_GetAmxAddr(amx, params[4]); + Vector vector(amx_ctof(refvec[0]), amx_ctof(refvec[1]), amx_ctof(refvec[2])); + + set_pdata(TypeConversion.id_to_edict(entity), data.fieldOffset, vector, element); + + return 1; +} + + +// native get_ent_data_entity(entity, const class[], const member[], element = 0); +static cell AMX_NATIVE_CALL get_ent_data_entity(AMX *amx, cell *params) +{ + int entity = params[1]; + CHECK_ENTITY(entity); + + TypeDescription data; + GET_TYPE_DESCRIPTION(2, data, BaseFieldType::Entity); + + int element = params[4]; + CHECK_ELEMENT(element); + + auto pEntity = TypeConversion.id_to_edict(entity); + + switch (data.fieldType) + { + case FieldType::FIELD_CLASSPTR: + { + return TypeConversion.cbase_to_id(get_pdata(pEntity, data.fieldOffset, element)); + } + case FieldType::FIELD_ENTVARS: + { + return TypeConversion.entvars_to_id(get_pdata(pEntity, data.fieldOffset, element)); + } + case FieldType::FIELD_EDICT: + { + return TypeConversion.edict_to_id(get_pdata(pEntity, data.fieldOffset, element)); + } + case FieldType::FIELD_EHANDLE: + { + return TypeConversion.edict_to_id(get_pdata(pEntity, data.fieldOffset, element).Get()); + } + } + + return 0; +} + +// native set_ent_data_entity(entity, const class[], const member[], value, element = 0); +static cell AMX_NATIVE_CALL set_ent_data_entity(AMX *amx, cell *params) +{ + int entity = params[1]; + int value = params[4]; + + CHECK_ENTITY(entity); + + if (value != -1) + { + CHECK_ENTITY(value); + } + + TypeDescription data; + GET_TYPE_DESCRIPTION(2, data, BaseFieldType::Entity); + + int element = params[5]; + CHECK_ELEMENT(element); + + auto pEntity = TypeConversion.id_to_edict(entity); + + switch (data.fieldType) + { + case FieldType::FIELD_CLASSPTR: + { + set_pdata(pEntity, data.fieldOffset, value != -1 ? TypeConversion.id_to_cbase(value) : nullptr, element); + break; + } + case FieldType::FIELD_ENTVARS: + { + set_pdata(pEntity, data.fieldOffset, value != -1 ? TypeConversion.id_to_entvars(value) : nullptr, element); + break; + } + case FieldType::FIELD_EDICT: + { + set_pdata(pEntity, data.fieldOffset, value != -1 ? TypeConversion.id_to_edict(value) : nullptr, element); + break; + } + case FieldType::FIELD_EHANDLE: + { + get_pdata(pEntity, data.fieldOffset, element).Set(value != -1 ? TypeConversion.id_to_edict(value) : nullptr); + break; + } + } + + return 0; +} + + +// native get_ent_data_string(entity, const class[], const member[], value[], maxlen, element = 0); +static cell AMX_NATIVE_CALL get_ent_data_string(AMX *amx, cell *params) +{ + int entity = params[1]; + CHECK_ENTITY(entity); + + TypeDescription data; + GET_TYPE_DESCRIPTION(2, data, BaseFieldType::String); + + int element = params[6]; + CHECK_ELEMENT(element); + + auto pEntity = TypeConversion.id_to_edict(entity); + + cell buffer = params[4]; + int maxlen = params[5]; + + switch (data.fieldType) + { + case FieldType::FIELD_STRING: + { + maxlen = ke::Min(maxlen, static_cast(data.fieldSize)); + char *string = get_pdata_direct(pEntity, data.fieldOffset, element, data.fieldSize); + + return MF_SetAmxStringUTF8Char(amx, buffer, string ? string : "", string ? strlen(string) : 0, maxlen); + } + case FieldType::FIELD_STRINGPTR: + { + char *string = get_pdata(pEntity, data.fieldOffset, element); + + return MF_SetAmxStringUTF8Char(amx, buffer, string ? string : "", string ? strlen(string) : 0, maxlen); + } + } + + return 0; +} + +// native set_ent_data_string(entity, const class[], const member[], const value[], element = 0); +static cell AMX_NATIVE_CALL set_ent_data_string(AMX *amx, cell *params) +{ + int entity = params[1]; + CHECK_ENTITY(entity); + + TypeDescription data; + GET_TYPE_DESCRIPTION(2, data, BaseFieldType::String); + + int element = params[5]; + CHECK_ELEMENT(element); + + auto pEntity = TypeConversion.id_to_edict(entity); + + int length; + const char *value = MF_GetAmxString(amx, params[4], 0, &length); + + switch (data.fieldType) + { + case FieldType::FIELD_STRING: + { + auto buffer = reinterpret_cast(pEntity->pvPrivateData) + data.fieldOffset; + return strncopy(buffer, value, ke::Min(length + 1, data.fieldSize)); + } + case FieldType::FIELD_STRINGPTR: + { + auto buffer = get_pdata(pEntity, data.fieldOffset, element); + + if (!buffer || length > static_cast(strlen(buffer))) + { + if (buffer) + { + free(buffer); + } + + buffer = reinterpret_cast(malloc(length + 1)); + set_pdata(pEntity, data.fieldOffset, buffer, element); + } + + return strncopy(buffer, value, length + 1); + } + } + + return 0; +} + + +// native get_ent_data_size(const class[], const member[]); +static cell AMX_NATIVE_CALL get_ent_data_size(AMX *amx, cell *params) +{ + TypeDescription data; + GET_TYPE_DESCRIPTION(0, data, BaseFieldType::None); + + return data.fieldSize; +} + +// native find_ent_data_info(const class[], const member[], &FieldType:type = FIELD_NONE, &arraysize = 0, &bool:unsigned = false); +static cell AMX_NATIVE_CALL find_ent_data_info(AMX *amx, cell *params) +{ + TypeDescription data; + GET_TYPE_DESCRIPTION(1, data, BaseFieldType::None); + + *MF_GetAmxAddr(amx, params[3]) = static_cast(data.fieldType); + *MF_GetAmxAddr(amx, params[4]) = ke::Max(0, data.fieldSize); + *MF_GetAmxAddr(amx, params[5]) = data.fieldUnsigned != 0; + + return data.fieldOffset; +} + + +AMX_NATIVE_INFO pdata_gc_natives[] = +{ + { "get_ent_data" , get_ent_data }, + { "set_ent_data" , set_ent_data }, + + { "get_ent_data_float" , get_ent_data_float }, + { "set_ent_data_float" , set_ent_data_float }, + + { "get_ent_data_vector", get_ent_data_vector }, + { "set_ent_data_vector", set_ent_data_vector }, + + { "get_ent_data_entity", get_ent_data_entity }, + { "set_ent_data_entity", set_ent_data_entity }, + + { "get_ent_data_string", get_ent_data_string }, + { "set_ent_data_string", set_ent_data_string }, + + { "get_ent_data_size" , get_ent_data_size }, + { "find_ent_data_info" , find_ent_data_info }, + + { nullptr , nullptr } +}; diff --git a/plugins/include/fakemeta.inc b/plugins/include/fakemeta.inc index 26e34cd7..ff7d4531 100755 --- a/plugins/include/fakemeta.inc +++ b/plugins/include/fakemeta.inc @@ -557,11 +557,350 @@ native lookup_sequence(entity, const name[], &Float:framerate = 0.0, &bool:loops */ native Float:set_controller(entity, controller, Float:value); +/** + * Retrieves an integer value from an entity's private data based off a class + * and member name. + * + * @note Unlike the [get|set]_pdata_* natives that require compiling the class + * member offset into the plugin, this native instead retrieves the + * necessary offset from the AMXX gamedata files at runtime, based on the + * provided class and member name. + * @note This native is safer than [get|set]_pdata_* as it can perform stricter + * offset and typing checks. + * @note This native is used to access the following (C++/engine) data types: + * integer, boolean, short, character, pointer, structure, class, + * stringint and function. Unsigned variants (if applicable) are supported + * and will be converted automatically. + * + * @param entity Entity index + * @param class Class name + * @param member Member name + * @param element Element to retrieve (starting from 0) if member is an array + * + * @return Integer value + * @error If an invalid entity is provided, either class or member is + * empty, no offset is found or an invalid offset is retrieved, + * or the data type does not match, an error will be thrown. + */ +native any:get_ent_data(entity, const class[], const member[], element = 0); + +/** + * Sets an integer value to an entity's private data based off a class + * and member name. + * + * @note Unlike the [get|set]_pdata_* natives that require compiling the class + * member offset into the plugin, this native instead retrieves the + * necessary offset from the AMXX gamedata files at runtime, based on the + * provided class and member name. + * @note This native is safer than [get|set]_pdata_* as it can perform stricter + * offset and typing checks. + * @note This native is used to access the following (C++/engine) data types: + * integer, boolean, short, character, pointer, stringint and function. + * Unsigned variants (if applicable) are supported and will be converted + * automatically. + * + * @param entity Entity index + * @param class Class name + * @param member Member name + * @param value Value to set + * @param element Element to set (starting from 0) if member is an array + * + * @noreturn + * @error If an invalid entity is provided, either class or member is + * empty, no offset is found or an invalid offset is retrieved, + * or the data type does not match, an error will be thrown. + */ +native set_ent_data(entity, const class[], const member[], any:value, element = 0); + +/** + * Retrieves a float value from an entity's private data based off a class + * and member name. + * + * @note Unlike the [get|set]_pdata_* natives that require compiling the class + * member offset into the plugin, this native instead retrieves the + * necessary offset from the AMXX gamedata files at runtime, based on the + * provided class and member name. + * @note This native is safer than [get|set]_pdata_* as it can perform stricter + * offset and typing checks. + * + * @param entity Entity index + * @param class Class name + * @param member Member name + * @param element Element to retrieve (starting from 0) if member is an array + * + * @return Float value + * @error If an invalid entity is provided, either class or member is + * empty, no offset is found or an invalid offset is retrieved, + * or the data type does not match, an error will be thrown. + */ +native Float:get_ent_data_float(entity, const class[], const member[], element = 0); + +/** + * Sets a float value to an entity's private data based off a class + * and member name. + * + * @note Unlike the [get|set]_pdata_* natives that require compiling the class + * member offset into the plugin, this native instead retrieves the + * necessary offset from the AMXX gamedata files at runtime, based on the + * provided class and member name. + * @note This native is safer than [get|set]_pdata_* as it can perform stricter + * offset and typing checks. + * + * @param entity Entity index + * @param class Class name + * @param member Member name + * @param value Value to set + * @param element Element to set (starting from 0) if member is an array + * + * @noreturn + * @error If an invalid entity is provided, either class or member is + * empty, no offset is found or an invalid offset is retrieved, + * or the data type does not match, an error will be thrown. + */ +native set_ent_data_float(entity, const class[], const member[], Float:value, element = 0); + +/** + * Retrieves a vector from an entity's private data based off a class and member name. + * + * @note Unlike the [get|set]_pdata_* natives that require compiling the class + * member offset into the plugin, this native instead retrieves the + * necessary offset from the AMXX gamedata files at runtime, based on the + * provided class and member name. + * @note This native is safer than [get|set]_pdata_* as it can perform stricter + * offset and typing checks. + * + * @param entity Entity index + * @param class Class name + * @param member Member name + * @param value Vector buffer to store data in + * @param element Element to retrieve (starting from 0) if member is an array + * + * @noreturn + * @error If an invalid entity is provided, either class or member is + * empty, no offset is found or an invalid offset is retrieved, + * or the data type does not match, an error will be thrown. + */ +native get_ent_data_vector(entity, const class[], const member[], Float:value[3], element = 0); + +/** + * Sets a vector to an entity's private data based off a class and member name. + * + * @note Unlike the [get|set]_pdata_* natives that require compiling the class + * member offset into the plugin, this native instead retrieves the + * necessary offset from the AMXX gamedata files at runtime, based on the + * provided class and member name. + * @note This native is safer than [get|set]_pdata_* as it can perform stricter + * offset and typing checks. + * + * @param entity Entity index + * @param class Class name + * @param member Member name + * @param value Vector to set + * @param element Element to set (starting from 0) if member is an array + * + * @noreturn + * @error If an invalid entity is provided, either class or member is + * empty, no offset is found or an invalid offset is retrieved, + * or the data type does not match, an error will be thrown. + */ +native set_ent_data_vector(entity, const class[], const member[], Float:value[3], element = 0); + +/** + * Retrieves an entity index from an entity's private data based off a class + * and member name. + * + * @note Unlike the [get|set]_pdata_* natives that require compiling the class + * member offset into the plugin, this native instead retrieves the + * necessary offset from the AMXX gamedata files at runtime, based on the + * provided class and member name. + * @note This native is safer than [get|set]_pdata_* as it can perform stricter + * offset and typing checks. + * @note This native is used to access the following (C++/engine) data types: + * classptr, entvars, edict and ehandle. + * + * @param entity Entity index + * @param class Class name + * @param member Member name + * @param element Element to retrieve (starting from 0) if member is an array + * + * @return Entity index if found, -1 otherwise + * @error If an invalid entity is provided, either class or member is + * empty, no offset is found or an invalid offset is retrieved, + * or the data type does not match, an error will be thrown. + */ +native get_ent_data_entity(entity, const class[], const member[], element = 0); + +/** + * Sets an entity index to an entity's private data based off a class + * and member name. + * + * @note Unlike the [get|set]_pdata_* natives that require compiling the class + * member offset into the plugin, this native instead retrieves the + * necessary offset from the AMXX gamedata files at runtime, based on the + * provided class and member name. + * @note This native is safer than [get|set]_pdata_* as it can perform stricter + * offset and typing checks. + * @note This native is used to access the following (C++/engine) data types: + * classptr, entvars, edict and ehandle. + * @note Pass -1 as value to act as C++ NULL. + * + * @param entity Entity index + * @param class Class name + * @param member Member name + * @param value Entity index to set + * @param element Element to set (starting from 0) if member is an array + * + * @noreturn + * @error If an invalid entity or value is provided, either class or member + * is empty, no offset is found or an invalid offset is retrieved, + * or the data type does not match, an error will be thrown. + */ +native set_ent_data_entity(entity, const class[], const member[], value, element = 0); + +/** + * Retrieves a string from an entity's private data based off a class and member name. + * + * @note Unlike the [get|set]_pdata_* natives that require compiling the class + * member offset into the plugin, this native instead retrieves the + * necessary offset from the AMXX gamedata files at runtime, based on the + * provided class and member name. + * @note This native is safer than [get|set]_pdata_* as it can perform stricter + * offset and typing checks. + * @note This native is used to access the following (C++/engine) data types: + * string, stringptr. + * + * @param entity Entity index + * @param class Class name + * @param member Member name + * @param value Buffer to store data in + * @param maxlen Maximum size of the buffer + * @param element Element to retrieve (starting from 0) if member is an array + * + * @return Number of cells written to buffer + * @error If an invalid entity is provided, either class or member is + * empty, no offset is found or an invalid offset is retrieved, + * or the data type does not match, an error will be thrown. + */ +native get_ent_data_string(entity, const class[], const member[], value[], maxlen, element = 0); + +/** + * Sets a string to an entity's private data based off a class and member name. + * + * @note Unlike the [get|set]_pdata_* natives that require compiling the class + * member offset into the plugin, this native instead retrieves the + * necessary offset from the AMXX gamedata files at runtime, based on the + * provided class and member name. + * @note This native is safer than [get|set]_pdata_* as it can perform stricter + * offset and typing checks. + * @note This native is used to access the following (C++/engine) data types: + * string, stringptr. + * + * @param entity Entity index + * @param class Class name + * @param member Member name + * @param value String to set + * @param element Element to set (starting from 0) if member is an array + * + * @return Number of cells written to buffer + * @error If an invalid entity is provided, either class or member is + * empty, no offset is found or an invalid offset is retrieved, + * or the data type does not match, an error will be thrown. + */ +native set_ent_data_string(entity, const class[], const member[], const value[], element = 0); + +/** + * Retrieves the size of array of a class member. + * + * @param class Class name + * @param member Member name + * + * @return Size of array (in elements), otherwise 1 if member is not an array + * @error If either class or member is empty, no offset is found or an invalid + * offset is retrieved, an error will be thrown. + */ +native get_ent_data_size(const class[], const member[]); + +/** + * Finds an offset based off a class and member name. + * + * @param class Class name + * @param member Member name + * @param type Optional variable to store member type in (FIELD_* constants) + * @param arraysize Optional variable to store array size in, if member is an array + * @param unsigned Optional variable to store whether member is unsigned (short and char types only) + * + * @return Class member offset + * @error If either class or member is empty, no offset is found or an invalid + * offset is retrieved, an error will be thrown. + */ +native find_ent_data_info(const class[], const member[], &FieldType:type = FIELD_NONE, &arraysize = 0, &bool:unsigned = false); + +/** + * Returns the data field base type based off a specific field type. + * + * @note From an AMXX plugin perspective, the (C++/engine) data types can be grouped + * in five base types: integer, float, vector, entity and string. This stock is + * essentially for convenience and debug purpose. + * + * @param type Class member type (FIELD_* constants) + * @param type_name Optional buffer to store base type name in + * @param maxlen Maximum size of the buffer + * + * @return Base field type (BASEFIELD_* constants) + */ +stock BaseFieldType:get_ent_data_basetype(FieldType:type, type_name[] = "", maxlen = 0) +{ + static baseFieldTypeNames[BaseFieldType][] = + { + "none", + "integer", + "float", + "vector", + "entity", + "string", + }; + + new BaseFieldType:baseType = BASEFIELD_NONE; + + switch (type) + { + case FIELD_INTEGER, FIELD_STRINGINT, FIELD_SHORT , FIELD_CHARACTER, + FIELD_CLASS , FIELD_STRUCTURE, FIELD_POINTER, FIELD_FUNCTION, + FIELD_BOOLEAN: + { + baseType = BASEFIELD_INTEGER; + } + case FIELD_FLOAT: + { + baseType = BASEFIELD_FLOAT; + } + case FIELD_VECTOR: + { + baseType = BASEFIELD_VECTOR; + } + case FIELD_CLASSPTR, FIELD_ENTVARS, FIELD_EDICT, FIELD_EHANDLE: + { + baseType = BASEFIELD_ENTITY; + } + case FIELD_STRINGPTR, FIELD_STRING: + { + baseType = BASEFIELD_STRING; + } + } + + if (maxlen > 0) + { + copy(type_name, maxlen, baseFieldTypeNames[baseType]); + } + + return baseType; +} + enum { - Model_DefaultSize = -2, - Model_CurrentSequence = -1, + Model_DefaultSize = -2, + Model_CurrentSequence = -1, }; /** diff --git a/plugins/include/fakemeta_const.inc b/plugins/include/fakemeta_const.inc index 1fc69162..eba34b6b 100755 --- a/plugins/include/fakemeta_const.inc +++ b/plugins/include/fakemeta_const.inc @@ -753,3 +753,41 @@ enum AlertType at_error, at_logged // Server print to console (only in multiplayer games) }; + +/** + * Data field types for use with find_ent_data_info(). + */ +enum FieldType +{ + FIELD_NONE, + FIELD_FLOAT, // Floating point value + FIELD_STRINGINT, // String ID (return from ALLOC_STRING) + FIELD_STRINGPTR, // String, pointer-to-char + FIELD_STRING, // String, fixed size + FIELD_CLASSPTR, // Classes pointer derived of CBaseEntity + FIELD_CLASS, // Arbitrary classes, direct + FIELD_STRUCTURE, // Arbitrary structures, direct + FIELD_EHANDLE, // Entity handle + FIELD_ENTVARS, // entvars_t* + FIELD_EDICT, // edict_t* + FIELD_VECTOR, // Vector + FIELD_POINTER, // Arbitrary data pointer + FIELD_INTEGER, // Integer or enum + FIELD_FUNCTION, // Class function pointer (Think, Use, etc) + FIELD_BOOLEAN, // Boolean + FIELD_SHORT, // 2 bytes integer + FIELD_CHARACTER, // 1 byte +}; + +/** + * Base data field types for use with get_ent_data_basetype(). + */ +enum BaseFieldType +{ + BASEFIELD_NONE, + BASEFIELD_INTEGER, + BASEFIELD_FLOAT, + BASEFIELD_VECTOR, + BASEFIELD_ENTITY, + BASEFIELD_STRING, +}; diff --git a/public/HLTypeConversion.h b/public/HLTypeConversion.h new file mode 100644 index 00000000..3857b9a4 --- /dev/null +++ b/public/HLTypeConversion.h @@ -0,0 +1,200 @@ +// +// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). +// Copyright (C) The AMX Mod X Development Team. +// +// This software is licensed under the GNU General Public License, version 3 or higher. +// Additional exceptions apply. For full license details, see LICENSE.txt or visit: +// https://alliedmods.net/amxmodx-license + +#ifndef _HL_CONVERSION_TYPE_H_ +#define _HL_CONVERSION_TYPE_H_ + +#include "amxxmodule.h" + +template static inline T& ref_pdata(void *pPrivateData, int offset, int element = 0) +{ + return *reinterpret_cast((reinterpret_cast(pPrivateData) + offset + (element * sizeof(T)))); +} + + +template inline T get_pdata_direct(edict_t *pEntity, int offset, int element = 0, int size = 0) +{ + return reinterpret_cast((reinterpret_cast(pEntity->pvPrivateData) + offset + (element * size))); +} + +template inline T& get_pdata(void *pPrivateData, int offset, int element = 0) +{ + return ref_pdata(pPrivateData, offset, element); +} + +template inline T& get_pdata(edict_t *pEntity, int offset, int element = 0) +{ + return get_pdata(pEntity->pvPrivateData, offset, element); +} + + +template inline void set_pdata(void *pPrivateData, int offset, T value, int element = 0) +{ + ref_pdata(pPrivateData, offset, element) = value; +} + +template inline void set_pdata(edict_t *pEntity, int offset, T value, int element = 0) +{ + set_pdata(pEntity->pvPrivateData, offset, value, element); +} + + +class HLTypeConversion +{ + public: + + HLTypeConversion() : m_FirstEdict(nullptr), m_PevOffset(0) + {} + + public: + + void init() + { + if (!m_FirstEdict) + { + m_FirstEdict = INDEXENT(0); + + search_pev(); + } + } + + public: // Edict -> Index + + int edict_to_id(edict_t *pEdict) + { + if (FNullEnt(pEdict)) + { + return -1; + } + + return static_cast(pEdict - m_FirstEdict); + } + + public: // Entvars -> Edict/Index + + edict_t* entvar_to_edict(entvars_t *pev) + { + if (!pev || FNullEnt(pev->pContainingEntity)) + { + return nullptr; + } + + return pev->pContainingEntity; + } + + int entvars_to_id(entvars_t *pev) + { + return edict_to_id(entvar_to_edict(pev)); + } + + public: // Index -> CBase*/Edict/Entvars + + void* id_to_cbase(int index) + { + edict_t *pEdict = id_to_edict(index); + return pEdict ? pEdict->pvPrivateData : nullptr; + } + + edict_t* id_to_edict(int index) + { + return static_cast(m_FirstEdict + index); + } + + entvars_t* id_to_entvars(int index) + { + edict_t *pEdict = id_to_edict(index); + return pEdict ? VARS(pEdict) : nullptr; + } + + public: // CBase* -> Entvars/Index + + entvars_t* cbase_to_entvar(void *cbase) + { + if (!cbase) + { + return nullptr; + } + + return get_pdata(cbase, m_PevOffset); + } + + int cbase_to_id(void *cbase) + { + return entvars_to_id(cbase_to_entvar(cbase)); + } + + private: + + void search_pev() + { + entvars_t *pev = VARS(m_FirstEdict); + + byte *privateData = reinterpret_cast(m_FirstEdict->pvPrivateData); + + for (size_t i = 0; i < 0xFFF; ++i) + { + entvars_t *val = *(reinterpret_cast(privateData + i)); + + if (val == pev) + { + m_PevOffset = i; + return; + } + } + + m_PevOffset = 0; // This should not happen. + } + + private: + + edict_t* m_FirstEdict; + size_t m_PevOffset; +}; + +class EHANDLE +{ + private: + + edict_t* m_pent; + int m_serialnumber; + + public: + + edict_t* Get(void) + { + if (!FNullEnt(m_pent)) + { + if (m_pent->serialnumber == m_serialnumber) + { + return m_pent; + } + + return nullptr; + } + + return nullptr; + }; + + edict_t* Set(edict_t *pent) + { + if (!FNullEnt(pent)) + { + m_pent = pent; + m_serialnumber = m_pent->serialnumber; + } + else + { + m_pent = nullptr; + m_serialnumber = 0; + } + + return pent; + }; +}; + +#endif // _HL_CONVERSION_TYPE_H_