From ab06edf44dd008d03faf6096a2157521e0a47cbc Mon Sep 17 00:00:00 2001 From: FutureRave Date: Thu, 28 Apr 2022 13:03:22 +0100 Subject: [PATCH 1/2] Expose entity and client flags to GSC --- src/Components/Modules/ScriptExtension.cpp | 144 +++++++++++++++++++++ src/Components/Modules/ScriptExtension.hpp | 17 +++ src/Game/Functions.cpp | 6 + src/Game/Functions.hpp | 15 +++ src/Game/Structs.hpp | 60 ++++++++- 5 files changed, 241 insertions(+), 1 deletion(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index b2c7a526..90a5c46c 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -4,6 +4,115 @@ namespace Components { const char* ScriptExtension::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" }; + std::unordered_map ScriptExtension::CustomEntityFields; + std::unordered_map ScriptExtension::CustomClientFields; + + void ScriptExtension::AddEntityField(const char* name, Game::fieldtype_t type, + const Game::ScriptCallbackEnt& setter, const Game::ScriptCallbackEnt& getter) + { + static std::uint16_t fieldOffsetStart = 15; // fields count + assert((fieldOffsetStart & Game::ENTFIELD_MASK) == Game::ENTFIELD_ENTITY); + + ScriptExtension::CustomEntityFields[fieldOffsetStart] = {name, fieldOffsetStart, type, setter, getter}; + ++fieldOffsetStart; + } + + void ScriptExtension::AddClientField(const char* name, Game::fieldtype_t type, + const Game::ScriptCallbackClient& setter, const Game::ScriptCallbackClient& getter) + { + static std::uint16_t fieldOffsetStart = 21; // fields count + assert((fieldOffsetStart & Game::ENTFIELD_MASK) == Game::ENTFIELD_ENTITY); + + const auto offset = fieldOffsetStart | Game::ENTFIELD_CLIENT; // This is how client field's offset is calculated + + // Use 'index' in 'array' as map key. It will be used later in Scr_SetObjectFieldStub + ScriptExtension::CustomClientFields[fieldOffsetStart] = {name, offset, type, setter, getter}; + ++fieldOffsetStart; + } + + void ScriptExtension::GScr_AddFieldsForEntityStub() + { + for (const auto& [offset, field] : ScriptExtension::CustomEntityFields) + { + Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs); + } + + Utils::Hook::Call(0x4A7CF0)(); // GScr_AddFieldsForClient + + for (const auto& [offset, field] : ScriptExtension::CustomClientFields) + { + Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs); + } + } + + // Because some functions are inlined we have to hook this function instead of Scr_SetEntityField + int ScriptExtension::Scr_SetObjectFieldStub(unsigned int classnum, int entnum, int offset) + { + if (classnum == Game::ClassNum::CLASS_NUM_ENTITY) + { + const auto entity_offset = static_cast(offset); + + const auto got = ScriptExtension::CustomEntityFields.find(entity_offset); + if (got != ScriptExtension::CustomEntityFields.end()) + { + got->second.setter(&Game::g_entities[entnum], offset); + return 1; + } + } + + // No custom generic field was found, let the game handle it + return Game::Scr_SetObjectField(classnum, entnum, offset); + } + + // Offset was already converted to array 'index' following binop offset & 0xFFFF1FFF + void ScriptExtension::Scr_SetClientFieldStub(Game::gclient_s* client, int offset) + { + const auto client_offset = static_cast(offset); + + const auto got = ScriptExtension::CustomClientFields.find(client_offset); + if (got != ScriptExtension::CustomClientFields.end()) + { + got->second.setter(client, &got->second); + return; + } + + // No custom field client was found, let the game handle it + Game::Scr_SetClientField(client, offset); + } + + void ScriptExtension::Scr_GetEntityFieldStub(int entnum, int offset) + { + if ((offset & Game::ENTFIELD_MASK) == Game::ENTFIELD_CLIENT) + { + // If we have a ENTFIELD_CLIENT offset we need to check g_entity is actually a fully connected client + if (Game::g_entities[entnum].client != nullptr) + { + const auto client_offset = static_cast(offset & 0xFFFF1FFF); + + const auto got = ScriptExtension::CustomClientFields.find(client_offset); + if (got != ScriptExtension::CustomClientFields.end()) + { + // Game functions probably don't ever need to use the reference to client_fields_s... + got->second.getter(Game::g_entities[entnum].client, &got->second); + return; + } + } + } + + // Regular entity offsets can be searched directly in our custom handler + const auto entity_offset = static_cast(offset); + + const auto got = ScriptExtension::CustomEntityFields.find(entity_offset); + if (got != ScriptExtension::CustomEntityFields.end()) + { + got->second.getter(&Game::g_entities[entnum], offset); + return; + } + + // No custom generic field was found, let the game handle it + Game::Scr_GetEntityField(entnum, offset); + } + void ScriptExtension::AddFunctions() { // File functions @@ -247,11 +356,46 @@ namespace Components Game::Scr_AddIString(value); } + void ScriptExtension::AddEntityFields() + { + ScriptExtension::AddEntityField("entityflags", Game::fieldtype_t::F_INT, + [](Game::gentity_s* ent, [[maybe_unused]] int offset) + { + ent->flags = Game::Scr_GetInt(0); + }, + [](Game::gentity_s* ent, [[maybe_unused]] int offset) + { + Game::Scr_AddInt(ent->flags); + }); + } + + void ScriptExtension::AddClientFields() + { + ScriptExtension::AddClientField("clientflags", Game::fieldtype_t::F_INT, + [](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField) + { + pSelf->flags = Game::Scr_GetInt(0); + }, + [](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField) + { + Game::Scr_AddInt(pSelf->flags); + }); + } + ScriptExtension::ScriptExtension() { ScriptExtension::AddFunctions(); ScriptExtension::AddMethods(); + ScriptExtension::AddEntityFields(); + ScriptExtension::AddClientFields(); + // Correct builtin function pointer Utils::Hook::Set(0x79A90C, ScriptExtension::Scr_TableLookupIStringByRow); + + Utils::Hook(0x4EC721, ScriptExtension::GScr_AddFieldsForEntityStub, HOOK_CALL).install()->quick(); // GScr_AddFieldsForEntity + + Utils::Hook(0x41BED2, ScriptExtension::Scr_SetObjectFieldStub, HOOK_CALL).install()->quick(); // SetEntityFieldValue + Utils::Hook(0x5FBF01, ScriptExtension::Scr_SetClientFieldStub, HOOK_CALL).install()->quick(); // Scr_SetObjectField + Utils::Hook(0x4FF413, ScriptExtension::Scr_GetEntityFieldStub, HOOK_CALL).install()->quick(); // Scr_GetObjectField } } diff --git a/src/Components/Modules/ScriptExtension.hpp b/src/Components/Modules/ScriptExtension.hpp index 6b050159..b4722ea9 100644 --- a/src/Components/Modules/ScriptExtension.hpp +++ b/src/Components/Modules/ScriptExtension.hpp @@ -7,11 +7,28 @@ namespace Components public: ScriptExtension(); + static void AddEntityField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackEnt& setter, const Game::ScriptCallbackEnt& getter); + static void AddClientField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackClient& setter, const Game::ScriptCallbackClient& getter); + private: static const char* QueryStrings[]; + static std::unordered_map CustomEntityFields; + static std::unordered_map CustomClientFields; + + static void GScr_AddFieldsForEntityStub(); + + // Two hooks because it makes our code cleaner (luckily functions were not inlined) + static int Scr_SetObjectFieldStub(unsigned int classnum, int entnum, int offset); + static void Scr_SetClientFieldStub(Game::gclient_s* client, int offset); + + // One hook because functions were inlined + static void Scr_GetEntityFieldStub(int entnum, int offset); + static void AddFunctions(); static void AddMethods(); + static void AddEntityFields(); + static void AddClientFields(); static void Scr_TableLookupIStringByRow(); }; } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index ecebf15a..e5d95eb9 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -296,6 +296,12 @@ namespace Game Scr_ClearOutParams_t Scr_ClearOutParams = Scr_ClearOutParams_t(0x4386E0); + Scr_GetObjectField_t Scr_GetObjectField = Scr_GetObjectField_t(0x4FF3D0); + Scr_SetObjectField_t Scr_SetObjectField = Scr_SetObjectField_t(0x4F20F0); + Scr_GetEntityField_t Scr_GetEntityField = Scr_GetEntityField_t(0x4E8390); + Scr_SetClientField_t Scr_SetClientField = Scr_SetClientField_t(0x4A6DF0); + Scr_AddClassField_t Scr_AddClassField = Scr_AddClassField_t(0x4C0E70); + GetEntity_t GetEntity = GetEntity_t(0x4BC270); GetPlayerEntity_t GetPlayerEntity = GetPlayerEntity_t(0x49C4A0); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 8da04c3d..5d705227 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -747,6 +747,21 @@ namespace Game typedef void(__cdecl * Scr_ParamError_t)(unsigned int paramIndex, const char*); extern Scr_ParamError_t Scr_ParamError; + typedef void(__cdecl * Scr_GetObjectField_t)(unsigned int classnum, int entnum, int offset); + extern Scr_GetObjectField_t Scr_GetObjectField; + + typedef int(__cdecl * Scr_SetObjectField_t)(unsigned int classnum, int entnum, int offset); + extern Scr_SetObjectField_t Scr_SetObjectField; + + typedef void(__cdecl * Scr_SetClientField_t)(gclient_s* client, int offset); + extern Scr_SetClientField_t Scr_SetClientField; + + typedef void(__cdecl * Scr_GetEntityField_t)(int entnum, int offset); + extern Scr_GetEntityField_t Scr_GetEntityField; + + typedef void(__cdecl * Scr_AddClassField_t)(unsigned int classnum, const char* name, unsigned int offset); + extern Scr_AddClassField_t Scr_AddClassField; + typedef gentity_s*(__cdecl * GetPlayerEntity_t)(scr_entref_t entref); extern GetPlayerEntity_t GetPlayerEntity; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index e4cee00b..70beb4a9 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -231,6 +231,17 @@ namespace Game FL_MOVER_SLIDE = 0x8000000 }; + enum ClassNum : unsigned int + { + CLASS_NUM_ENTITY = 0x0, + CLASS_NUM_HUDELEM = 0x1, + CLASS_NUM_PATHNODE = 0x2, + CLASS_NUM_VEHICLENODE = 0x3, + CLASS_NUM_VEHTRACK_SEGMENT = 0x4, + CLASS_NUM_FXENTITY = 0x5, + CLASS_NUM_COUNT = 0x6, + }; + typedef enum { HITLOC_NONE, @@ -5705,6 +5716,53 @@ namespace Game static_assert(sizeof(gentity_s) == 0x274); + enum $1C4253065710F064DA9E4D59ED6EC544 + { + ENTFIELD_ENTITY = 0x0, + ENTFIELD_SENTIENT = 0x2000, + ENTFIELD_ACTOR = 0x4000, + ENTFIELD_CLIENT = 0x6000, + ENTFIELD_VEHICLE = 0x8000, + ENTFIELD_MASK = 0xE000, + }; + + enum fieldtype_t + { + F_INT = 0x0, + F_SHORT = 0x1, + F_BYTE = 0x2, + F_FLOAT = 0x3, + F_CSTRING = 0x4, + F_STRING = 0x5, + F_VECTOR = 0x6, + F_ENTITY = 0x7, + F_ENTHANDLE = 0x8, + F_ANGLES_YAW = 0x9, + F_OBJECT = 0xA, + F_MODEL = 0xB, + }; + + struct ent_field_t + { + const char* name; + int ofs; + fieldtype_t type; + void(__cdecl * setter)(gentity_s*, int); + void(__cdecl * getter)(gentity_s*, int); + }; + + struct client_fields_s + { + const char* name; + int ofs; + fieldtype_t type; + void(__cdecl * setter)(gclient_s*, const client_fields_s*); + void(__cdecl * getter)(gclient_s*, const client_fields_s*); + }; + + typedef void(__cdecl * ScriptCallbackEnt)(gentity_s*, int); + typedef void(__cdecl * ScriptCallbackClient)(gclient_s*, const client_fields_s*); + struct lockonFireParms { bool lockon; @@ -6966,7 +7024,7 @@ namespace Game SHELLSHOCK_VIEWTYPE_NONE = 0x2, }; - struct shellshock_parms_t + struct shellshock_parms_t { struct { From 8123e21767cb574bd6464d2eef5e75d6f6621d6a Mon Sep 17 00:00:00 2001 From: FutureRave Date: Thu, 28 Apr 2022 14:25:53 +0100 Subject: [PATCH 2/2] Reverse flag --- src/Components/Modules/ScriptExtension.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 90a5c46c..68e80a7a 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -64,7 +64,7 @@ namespace Components return Game::Scr_SetObjectField(classnum, entnum, offset); } - // Offset was already converted to array 'index' following binop offset & 0xFFFF1FFF + // Offset was already converted to array 'index' following binop offset & ~Game::ENTFIELD_MASK void ScriptExtension::Scr_SetClientFieldStub(Game::gclient_s* client, int offset) { const auto client_offset = static_cast(offset); @@ -87,7 +87,7 @@ namespace Components // If we have a ENTFIELD_CLIENT offset we need to check g_entity is actually a fully connected client if (Game::g_entities[entnum].client != nullptr) { - const auto client_offset = static_cast(offset & 0xFFFF1FFF); + const auto client_offset = static_cast(offset & ~Game::ENTFIELD_MASK); const auto got = ScriptExtension::CustomClientFields.find(client_offset); if (got != ScriptExtension::CustomClientFields.end())