From 6251a1d5ace9b28bf39525992388bd53777b8d52 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 11 Mar 2022 14:30:00 +0000 Subject: [PATCH 1/7] Add sp commands --- src/game/game.cpp | 35 ++++++++- src/game/game.hpp | 13 ++++ src/game/structs.hpp | 88 ++++++++++++++++++++++ src/module/client_command.cpp | 2 +- src/module/command.cpp | 137 +++++++++++++++++++++++++++++++++- src/module/command.hpp | 6 ++ 6 files changed, 273 insertions(+), 8 deletions(-) diff --git a/src/game/game.cpp b/src/game/game.cpp index 78235a8..dcec3b1 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -13,6 +13,8 @@ namespace game DB_LoadXAssets_t DB_LoadXAssets; + Dvar_SetIntByName_t Dvar_SetIntByName; + Dvar_SetFromStringByName_t Dvar_SetFromStringByName; G_RunFrame_t G_RunFrame; @@ -53,6 +55,8 @@ namespace game XUIDToString_t XUIDToString; + SEH_LocalizeTextMessage_t SEH_LocalizeTextMessage; + decltype(longjmp)* _longjmp; CmdArgs* sv_cmd_args; @@ -88,6 +92,11 @@ namespace game client_t* svs_clients; } + namespace sp + { + sp::gentity_s* g_entities; + } + void AddRefToValue(VariableValue* value) { if (value->type == SCRIPT_OBJECT) @@ -539,6 +548,18 @@ namespace game (0x50D840)(player, origin, angles); } } + + void CG_GameMessage(LocalClientNum_t localClientNum, const char* msg) + { + if (is_mp()) + { + reinterpret_cast(0x456DC0)(localClientNum, msg); + } + else if (is_sp()) + { + reinterpret_cast(0x4228A0)(localClientNum, msg, 17); + } + } } launcher::mode mode = launcher::mode::none; @@ -580,6 +601,8 @@ namespace game native::DB_LoadXAssets = native::DB_LoadXAssets_t(SELECT_VALUE(0x48A8E0, 0x4CD020, 0x44F770)); + native::Dvar_SetIntByName = native::Dvar_SetIntByName_t(SELECT_VALUE(0x5396B0, 0x5BF560, 0x0)); + native::Dvar_SetFromStringByName = native::Dvar_SetFromStringByName_t( SELECT_VALUE(0x4DD090, 0x5BF740, 0x518DF0)); @@ -595,7 +618,7 @@ namespace game native::Scr_AddEntityNum = native::Scr_AddEntityNum_t(SELECT_VALUE(0x0, 0x56ABC0, 0x4EA2F0)); - native::Scr_Notify = native::Scr_Notify_t(SELECT_VALUE(0x0, 0x52B190, 0x0)); + native::Scr_Notify = native::Scr_Notify_t(SELECT_VALUE(0x4895B0, 0x52B190, 0x0)); native::Sys_ShowConsole = native::Sys_ShowConsole_t(SELECT_VALUE(0x470AF0, 0x5CF590, 0)); @@ -617,15 +640,18 @@ namespace game native::SV_Cmd_EndTokenizedString = native::SV_Cmd_EndTokenizedString_t(SELECT_VALUE(0x0, 0x545D70, 0x0)); - native::SV_GameSendServerCommand = native::SV_GameSendServerCommand_t(SELECT_VALUE(0x0, 0x573220, 0x0)); + native::SV_GameSendServerCommand = native::SV_GameSendServerCommand_t(SELECT_VALUE(0x402130, 0x573220, 0x0)); - native::SV_SendServerCommand = native::SV_SendServerCommand_t(SELECT_VALUE(0x0, 0x575DE0, 0x4FD5A0)); + native::SV_SendServerCommand = native::SV_SendServerCommand_t(SELECT_VALUE(0x4F6990, 0x575DE0, 0x4FD5A0)); native::XUIDToString = native::XUIDToString_t(SELECT_VALUE(0x4FAA30, 0x55CC20, 0x0)); + native::SEH_LocalizeTextMessage = native::SEH_LocalizeTextMessage_t( + SELECT_VALUE(0x41EA20, 0x57E240, 0x0)); + native::_longjmp = reinterpret_cast(SELECT_VALUE(0x73AC20, 0x7363BC, 0x655558)); - native::sv_cmd_args = reinterpret_cast(SELECT_VALUE(0x0, 0x1CAA998, 0x1B5E7D8)); + native::sv_cmd_args = reinterpret_cast(SELECT_VALUE(0x1757218, 0x1CAA998, 0x1B5E7D8)); native::cmd_args = reinterpret_cast(SELECT_VALUE(0x1750750, 0x1C978D0, 0x1B455F8)); native::scrVarGlob = reinterpret_cast(SELECT_VALUE(0x19AFC80, 0x1E72180, 0x1D3C800)); @@ -653,5 +679,6 @@ namespace game native::dedi::svs_clients = reinterpret_cast(0x4A12E90); native::g_entities = reinterpret_cast(SELECT_VALUE(0, 0x1A66E28, 0x191B900)); + native::sp::g_entities = reinterpret_cast(0x1197AD8); } } diff --git a/src/game/game.hpp b/src/game/game.hpp index 9fa6bee..1f692fb 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -23,6 +23,9 @@ namespace game typedef void (*DB_LoadXAssets_t)(XZoneInfo* zoneInfo, unsigned int zoneCount, int sync); extern DB_LoadXAssets_t DB_LoadXAssets; + typedef void (*Dvar_SetIntByName_t)(const char* dvarName, int value); + extern Dvar_SetIntByName_t Dvar_SetIntByName; + typedef void (*Dvar_SetFromStringByName_t)(const char* dvarName, const char* string); extern Dvar_SetFromStringByName_t Dvar_SetFromStringByName; @@ -83,6 +86,9 @@ namespace game typedef void (*XUIDToString_t)(const unsigned __int64* xuid, char* str); extern XUIDToString_t XUIDToString; + typedef char* (*SEH_LocalizeTextMessage_t)(const char* pszInputBuffer, const char* pszMessageType, msgLocErrType_t errType); + extern SEH_LocalizeTextMessage_t SEH_LocalizeTextMessage; + extern decltype(longjmp)* _longjmp; constexpr auto CMD_MAX_NESTING = 8; @@ -119,6 +125,11 @@ namespace game extern client_t* svs_clients; } + namespace sp + { + extern sp::gentity_s* g_entities; + } + void AddRefToValue(VariableValue* value); void Conbuf_AppendText(const char* message); @@ -156,6 +167,8 @@ namespace game void Cbuf_AddText(LocalClientNum_t localClientNum, const char* text); void TeleportPlayer(gentity_s* player, float* origin, float* angles); + + void CG_GameMessage(LocalClientNum_t localClientNum, const char* msg); } bool is_mp(); diff --git a/src/game/structs.hpp b/src/game/structs.hpp index c6002f9..831da31 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -391,6 +391,12 @@ namespace game LOCAL_CLIENT_INVALID = -1, }; + enum msgLocErrType_t + { + LOCMSG_SAFE, + LOCMSG_NOERR, + }; + struct cmd_function_t { cmd_function_t* next; @@ -578,6 +584,12 @@ namespace game dvar_t* hashNext; }; + struct Bounds + { + float midPoint[3]; + float halfSize[3]; + }; + struct usercmd_s { int serverTime; @@ -638,6 +650,26 @@ namespace game FL_MOVER_SLIDE = 0x8000000 }; + enum entityType + { + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + ET_MISSILE, + ET_INVISIBLE, + ET_SCRIPTMOVER, + ET_SOUND_BLEND, + ET_PRIMARY_LIGHT, + ET_TURRET, + ET_VEHICLE, + ET_VEHICLE_COLLMAP, + ET_VEHICLE_CORPSE, + ET_VEHICLE_SPAWNER, + ET_ACTOR, + ET_ACTOR_SPAWNER, + ET_ACTOR_CORPSE, + }; + struct entityState_s { int number; @@ -792,5 +824,61 @@ namespace game static_assert(sizeof(dedi::client_t) == 0x78690); } + + namespace sp + { + struct gclient_s + { + unsigned char __pad0[0xAE04]; + int flags; + }; // Warning, incorrect size + + struct entityState_s + { + int eType; + unsigned char __pad0[0x80]; + int number; + unsigned char __pad1[0x28]; + }; + + static_assert(sizeof(entityState_s) == 0xB0); + + struct entityShared_t + { + unsigned __int8 isLinked; + unsigned __int8 modelType; + unsigned __int8 svFlags; + unsigned __int8 eventType; + unsigned __int8 isInUse; + Bounds box; + int contents; + Bounds absBox; + float currentOrigin[3]; + float currentAngles[3]; + EntHandle ownerNum; + int eventTime; + }; + + static_assert(sizeof(entityShared_t) == 0x5C); + + struct gentity_s + { + entityState_s s; + entityShared_t r; + sp::gclient_s* client; // 0x10C + unsigned char __pad0[0x2C]; + int flags; + int clipmask; + int processedFrame; + EntHandle parent; + int nextthink; + int health; + int maxHealth; + int damage; + unsigned char __pad1[0x114]; + }; + + static_assert(sizeof(gentity_s) == 0x270); + } } } diff --git a/src/module/client_command.cpp b/src/module/client_command.cpp index e0f02a2..cf1aad3 100644 --- a/src/module/client_command.cpp +++ b/src/module/client_command.cpp @@ -26,7 +26,7 @@ private: game::native::SV_SendServerCommand(&game::native::dedi::svs_clients[client_num], type, string); } - else if (game::is_mp()) + else { game::native::SV_GameSendServerCommand(client_num, type, string); } diff --git a/src/module/command.cpp b/src/module/command.cpp index 14e125a..ce1a724 100644 --- a/src/module/command.cpp +++ b/src/module/command.cpp @@ -7,6 +7,7 @@ utils::memory::allocator command::allocator_; std::unordered_map> command::handlers; std::unordered_map> command::handlers_sv; +std::unordered_map> command::handlers_sp_sv; command::params::params() : nesting_(game::native::cmd_args->nesting) @@ -112,12 +113,22 @@ void command::add_sv(const char* name, std::function callback) +{ + const auto command = utils::string::to_lower(name); + + if (handlers_sp_sv.find(command) == handlers_sp_sv.end()) + { + handlers_sp_sv[command] = callback; + } +} + void command::main_handler() { params params; const auto command = utils::string::to_lower(params[0]); - const auto got = handlers.find(command); + const auto got = command::handlers.find(command); if (got != handlers.end()) { @@ -137,7 +148,7 @@ void command::client_command_stub(int client_num) params_sv params; const auto command = utils::string::to_lower(params[0]); - const auto got = handlers_sv.find(command); + const auto got = command::handlers_sv.find(command); if (got != handlers_sv.end()) { @@ -148,6 +159,45 @@ void command::client_command_stub(int client_num) game::native::ClientCommand(client_num); } +void command::client_command_sp(int client_num, const char* s) +{ + auto* entity = &game::native::sp::g_entities[client_num]; + + assert(entity != nullptr); // On sp it should only be an assertion + + params_sv params; + + const auto command = utils::string::to_lower(params[0]); + const auto got = command::handlers_sp_sv.find(command); + + if (got != handlers_sp_sv.end()) + { + got->second(entity, params); + } +} + +__declspec(naked) void command::client_command_sp_stub() +{ + __asm + { + pushad + + push [esp + 0x20 + 0x8] + push [esp + 0x20 + 0x8] + call command::client_command_sp + add esp, 0x8 + + popad + + // Code our hook skipped + mov eax, [esp + 0x8] + sub esp, 0x400 + + push 0x44BB5A // ClientCommand + retn + } +} + __declspec(naked) void command::client_command_dedi_stub() { __asm @@ -163,6 +213,81 @@ __declspec(naked) void command::client_command_dedi_stub() } } +// Between ufo/noclip functions and their mp counterpart is that I reversed the 'CG' type +void command::add_sp_commands() +{ + add("noclip", []() + { + const auto* ent = &game::native::sp::g_entities[0]; + + if (ent->health < 1) + return; + + ent->client->flags ^= 1; + + const auto* msg = (ent->client->flags & 1) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"; + printf("%s\n", game::native::SEH_LocalizeTextMessage(msg, "noclip print", game::native::LOCMSG_SAFE)); + }); + + add("ufo", []() + { + const auto* ent = &game::native::sp::g_entities[0]; + + if (ent->health < 1) + return; + + ent->client->flags ^= 2; + + const auto* msg = (ent->client->flags & 2) ? "GAME_UFOON" : "GAME_UFOOFF"; + printf("%s\n", game::native::SEH_LocalizeTextMessage(msg, "ufo print", game::native::LOCMSG_SAFE)); + }); + + add("god", []() + { + auto* ent = &game::native::sp::g_entities[0]; + + if (ent->health < 1) + return; + + assert(ent->s.eType == game::native::ET_PLAYER); + + ent->flags ^= game::native::FL_GODMODE; + + const auto* msg = (ent->flags & game::native::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF"; + printf("%s\n", game::native::SEH_LocalizeTextMessage(msg, "god print", game::native::LOCMSG_SAFE)); + }); + + add("demigod", []() + { + auto* ent = &game::native::sp::g_entities[0]; + + if (ent->health < 1) + return; + + assert(ent->s.eType == game::native::ET_PLAYER); + + ent->flags ^= game::native::FL_DEMI_GODMODE; + + const auto* msg = (ent->flags & game::native::FL_DEMI_GODMODE) ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF"; + printf("%s\n", game::native::SEH_LocalizeTextMessage(msg, "demigod print", game::native::LOCMSG_SAFE)); + }); + + add("notarget", []() + { + auto* ent = &game::native::sp::g_entities[0]; + + if (ent->health < 1) + return; + + assert(ent->s.eType == game::native::ET_PLAYER); + + ent->flags ^= game::native::FL_NOTARGET; + + const auto* msg = (ent->flags & game::native::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF"; + printf("%s\n", game::native::SEH_LocalizeTextMessage(msg, "notarget print", game::native::LOCMSG_SAFE)); + }); +} + void command::post_load() { if (game::is_mp()) @@ -172,7 +297,13 @@ void command::post_load() else if (game::is_dedi()) { utils::hook(0x4F96B5, &command::client_command_dedi_stub, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommand - } + } + else + { + utils::hook(0x44BB50, &command::client_command_sp_stub, HOOK_JUMP).install()->quick(); + utils::hook::nop(0x44BB55, 5); // Nop skipped instructions + add_sp_commands(); + } } REGISTER_MODULE(command); diff --git a/src/module/command.hpp b/src/module/command.hpp index 3720d64..6bbbbfa 100644 --- a/src/module/command.hpp +++ b/src/module/command.hpp @@ -49,6 +49,7 @@ public: static void add(const char* name, const std::function& callback); static void add_sv(const char* name, std::function callback); + static void add_sp_sv(const char* name, std::function callback); void post_load() override; @@ -57,11 +58,16 @@ private: static std::unordered_map> handlers; static std::unordered_map> handlers_sv; + static std::unordered_map> handlers_sp_sv; static void main_handler(); static void client_command_stub(int client_num); + static void client_command_sp(int client_num, const char* s); + static void client_command_sp_stub(); static void client_command_dedi_stub(); static void add_raw(const char* name, void (*callback)()); + + static void add_sp_commands(); }; From 123691e550366d3f0fe884c8cbde7a65ac9313cf Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 11 Mar 2022 14:39:19 +0000 Subject: [PATCH 2/7] Fix assertion --- src/module/command.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module/command.cpp b/src/module/command.cpp index ce1a724..c8c38b0 100644 --- a/src/module/command.cpp +++ b/src/module/command.cpp @@ -163,7 +163,7 @@ void command::client_command_sp(int client_num, const char* s) { auto* entity = &game::native::sp::g_entities[client_num]; - assert(entity != nullptr); // On sp it should only be an assertion + assert(entity->client != nullptr); // On sp it should only be an assertion params_sv params; From 1c647007b6d189e2c2156667aa62d35f8fbd677a Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 11 Mar 2022 16:06:14 +0000 Subject: [PATCH 3/7] Fix cg_function --- src/game/game.cpp | 5 +++-- src/game/game.hpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/game/game.cpp b/src/game/game.cpp index dcec3b1..1e30101 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -549,7 +549,8 @@ namespace game } } - void CG_GameMessage(LocalClientNum_t localClientNum, const char* msg) + // SP takes one extra argument, all possible values are undocumented + void CG_GameMessage(LocalClientNum_t localClientNum, const char* msg, int flags) { if (is_mp()) { @@ -557,7 +558,7 @@ namespace game } else if (is_sp()) { - reinterpret_cast(0x4228A0)(localClientNum, msg, 17); + reinterpret_cast(0x4228A0)(localClientNum, msg, flags); } } } diff --git a/src/game/game.hpp b/src/game/game.hpp index 1f692fb..6479014 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -168,7 +168,7 @@ namespace game void TeleportPlayer(gentity_s* player, float* origin, float* angles); - void CG_GameMessage(LocalClientNum_t localClientNum, const char* msg); + void CG_GameMessage(LocalClientNum_t localClientNum, const char* msg, int flags = 0); } bool is_mp(); From 41e58fae2aed2c0e02f3ae19dc8b8dd3b06df665 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 11 Mar 2022 17:34:59 +0000 Subject: [PATCH 4/7] Forgot the assertions here --- src/module/command.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/module/command.cpp b/src/module/command.cpp index c8c38b0..0b1afcc 100644 --- a/src/module/command.cpp +++ b/src/module/command.cpp @@ -223,6 +223,8 @@ void command::add_sp_commands() if (ent->health < 1) return; + assert(ent->s.eType == game::native::ET_PLAYER); + ent->client->flags ^= 1; const auto* msg = (ent->client->flags & 1) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"; @@ -236,6 +238,8 @@ void command::add_sp_commands() if (ent->health < 1) return; + assert(ent->s.eType == game::native::ET_PLAYER); + ent->client->flags ^= 2; const auto* msg = (ent->client->flags & 2) ? "GAME_UFOON" : "GAME_UFOOFF"; From c1b8ceab7fa2d2548536a7af7acbb22c28923fb7 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 11 Mar 2022 18:35:59 +0000 Subject: [PATCH 5/7] Fix artifacts not being found by workflow :hammer: --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d4ecb3e..9afe1d6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,5 +51,5 @@ jobs: with: name: ${{matrix.configuration}} binaries path: | - build/bin/x32/${{matrix.configuration}}/open-iw5.exe - build/bin/x32/${{matrix.configuration}}/open-iw5.pdb + build/bin/Win32/${{matrix.configuration}}/open-iw5.exe + build/bin/Win32/${{matrix.configuration}}/open-iw5.pdb From 418f79045e7829e2e0ee80b330a74d2eec5aaff8 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 11 Mar 2022 19:03:15 +0000 Subject: [PATCH 6/7] Remove double includes :ambulance: --- src/module/client_command.cpp | 2 -- src/module/fastfiles.cpp | 1 - src/module/test_clients.hpp | 2 -- 3 files changed, 5 deletions(-) diff --git a/src/module/client_command.cpp b/src/module/client_command.cpp index cf1aad3..7236959 100644 --- a/src/module/client_command.cpp +++ b/src/module/client_command.cpp @@ -1,8 +1,6 @@ #include #include "loader/module_loader.hpp" #include "command.hpp" - -#include "game/structs.hpp" #include "game/game.hpp" #include "utils/string.hpp" diff --git a/src/module/fastfiles.cpp b/src/module/fastfiles.cpp index 9485bf4..b089166 100644 --- a/src/module/fastfiles.cpp +++ b/src/module/fastfiles.cpp @@ -1,6 +1,5 @@ #include #include "loader/module_loader.hpp" -#include "game/structs.hpp" #include "game/game.hpp" #include "utils/hook.hpp" diff --git a/src/module/test_clients.hpp b/src/module/test_clients.hpp index 64e0a81..ff99abd 100644 --- a/src/module/test_clients.hpp +++ b/src/module/test_clients.hpp @@ -1,7 +1,5 @@ #pragma once #include "loader/module_loader.hpp" - -#include "game/structs.hpp" #include "game/game.hpp" class test_clients final : public module From cd9e0ec1595b29d7e92bb2906e629b784c24b84a Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 11 Mar 2022 20:43:11 +0000 Subject: [PATCH 7/7] Add infinite ammo dvar --- src/game/game.cpp | 8 ++++++ src/game/game.hpp | 7 +++++ src/game/structs.hpp | 52 ++++++++++++++++++++++++++++++++++ src/module/player_movement.cpp | 51 +++++++++++++++++++++++++++++++++ src/module/player_movement.hpp | 21 ++++++++++++++ 5 files changed, 139 insertions(+) create mode 100644 src/module/player_movement.cpp create mode 100644 src/module/player_movement.hpp diff --git a/src/game/game.cpp b/src/game/game.cpp index 1e30101..34bf66d 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -13,6 +13,8 @@ namespace game DB_LoadXAssets_t DB_LoadXAssets; + Dvar_RegisterBool_t Dvar_RegisterBool; + Dvar_SetIntByName_t Dvar_SetIntByName; Dvar_SetFromStringByName_t Dvar_SetFromStringByName; @@ -57,6 +59,8 @@ namespace game SEH_LocalizeTextMessage_t SEH_LocalizeTextMessage; + PM_WeaponUseAmmo_t PM_WeaponUseAmmo; + decltype(longjmp)* _longjmp; CmdArgs* sv_cmd_args; @@ -602,6 +606,8 @@ namespace game native::DB_LoadXAssets = native::DB_LoadXAssets_t(SELECT_VALUE(0x48A8E0, 0x4CD020, 0x44F770)); + native::Dvar_RegisterBool = native::Dvar_RegisterBool_t(SELECT_VALUE(0x4914D0, 0x5BE9F0, 0x0)); + native::Dvar_SetIntByName = native::Dvar_SetIntByName_t(SELECT_VALUE(0x5396B0, 0x5BF560, 0x0)); native::Dvar_SetFromStringByName = native::Dvar_SetFromStringByName_t( @@ -650,6 +656,8 @@ namespace game native::SEH_LocalizeTextMessage = native::SEH_LocalizeTextMessage_t( SELECT_VALUE(0x41EA20, 0x57E240, 0x0)); + native::PM_WeaponUseAmmo = native::PM_WeaponUseAmmo_t(SELECT_VALUE(0x463F80, 0x42E930, 0x0)); + native::_longjmp = reinterpret_cast(SELECT_VALUE(0x73AC20, 0x7363BC, 0x655558)); native::sv_cmd_args = reinterpret_cast(SELECT_VALUE(0x1757218, 0x1CAA998, 0x1B5E7D8)); diff --git a/src/game/game.hpp b/src/game/game.hpp index 6479014..bd0dbd6 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -23,6 +23,10 @@ namespace game typedef void (*DB_LoadXAssets_t)(XZoneInfo* zoneInfo, unsigned int zoneCount, int sync); extern DB_LoadXAssets_t DB_LoadXAssets; + typedef const dvar_t* (*Dvar_RegisterBool_t)(const char* dvarName, bool value, + unsigned __int16 flags, const char* description); + extern Dvar_RegisterBool_t Dvar_RegisterBool; + typedef void (*Dvar_SetIntByName_t)(const char* dvarName, int value); extern Dvar_SetIntByName_t Dvar_SetIntByName; @@ -89,6 +93,9 @@ namespace game typedef char* (*SEH_LocalizeTextMessage_t)(const char* pszInputBuffer, const char* pszMessageType, msgLocErrType_t errType); extern SEH_LocalizeTextMessage_t SEH_LocalizeTextMessage; + typedef void (*PM_WeaponUseAmmo_t)(playerState_s* ps, const Weapon weapon, bool isAlternate, int amount, PlayerHandIndex hand); + extern PM_WeaponUseAmmo_t PM_WeaponUseAmmo; + extern decltype(longjmp)* _longjmp; constexpr auto CMD_MAX_NESTING = 8; diff --git a/src/game/structs.hpp b/src/game/structs.hpp index 831da31..7e71567 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -533,6 +533,31 @@ namespace game const char* name; }; + enum dvar_flags : std::uint16_t + { + DVAR_ARCHIVE = 0x1, + DVAR_CHEAT = 0x4, + DVAR_CODINFO = 0x8, + DVAR_SCRIPTINFO = 0x10, + DVAR_SERVERINFO = 0x400, + DVAR_WRITEPROTECTED = 0x800, + DVAR_READONLY = 0x2000, + }; // Incomplete + + enum dvar_type : std::int8_t + { + DVAR_TYPE_BOOL = 0x0, + DVAR_TYPE_FLOAT = 0x1, + DVAR_TYPE_FLOAT_2 = 0x2, + DVAR_TYPE_FLOAT_3 = 0x3, + DVAR_TYPE_FLOAT_4 = 0x4, + DVAR_TYPE_INT = 0x5, + DVAR_TYPE_ENUM = 0x6, + DVAR_TYPE_STRING = 0x7, + DVAR_TYPE_COLOR = 0x8, + DVAR_TYPE_FLOAT_3_COLOR = 0x9, + }; + union DvarValue { bool enabled; @@ -610,6 +635,33 @@ namespace game static_assert(sizeof(usercmd_s) == 0x2C); + enum PlayerHandIndex + { + WEAPON_HAND_RIGHT = 0, + WEAPON_HAND_LEFT = 1, + NUM_WEAPON_HANDS = 2, + WEAPON_HAND_DEFAULT = 0, + }; + + struct Weapon_s + { + unsigned int padding : 8; + unsigned int scopeVariation : 3; + unsigned int weaponOthers : 4; + unsigned int weaponUnderBarrels : 2; + unsigned int weaponScopes : 3; + unsigned int weaponIdx : 8; + unsigned int weaponVariation : 4; + }; + + union Weapon + { + Weapon_s _s_0; + unsigned int data; + }; + + static_assert(sizeof(Weapon) == 4); + struct playerState_s { unsigned char __pad0[0x4EC]; diff --git a/src/module/player_movement.cpp b/src/module/player_movement.cpp new file mode 100644 index 0000000..338d264 --- /dev/null +++ b/src/module/player_movement.cpp @@ -0,0 +1,51 @@ +#include +#include "player_movement.hpp" + +#include "utils/hook.hpp" + +const game::native::dvar_t* player_movement::player_sustainAmmo; + +void player_movement::post_load() +{ + if (game::is_mp()) this->patch_mp(); + else if (game::is_sp()) this->patch_sp(); +} + +void player_movement::pm_weapon_use_ammo(game::native::playerState_s* ps, const game::native::Weapon weapon, + bool is_alternate, int amount, game::native::PlayerHandIndex hand) +{ + if (!player_movement::player_sustainAmmo->current.enabled) + { + game::native::PM_WeaponUseAmmo(ps, weapon, is_alternate, amount, hand); + } +} + +const game::native::dvar_t* player_movement::dvar_register_player_sustain_ammo(const char* dvar_name, + bool value, unsigned __int16 /*flags*/, const char* description) +{ + player_movement::player_sustainAmmo = game::native::Dvar_RegisterBool(dvar_name, + value, game::native::DVAR_CODINFO, description); + + return player_movement::player_sustainAmmo; +} + +void player_movement::patch_mp() +{ + utils::hook(0x418D9C, &player_movement::dvar_register_player_sustain_ammo, HOOK_CALL).install()->quick(); + + utils::hook(0x42B5DA, &player_movement::pm_weapon_use_ammo, HOOK_CALL).install()->quick(); + utils::hook(0x42B2BD, &player_movement::pm_weapon_use_ammo, HOOK_CALL).install()->quick(); + utils::hook(0x42AE95, &player_movement::pm_weapon_use_ammo, HOOK_CALL).install()->quick(); +} + +void player_movement::patch_sp() +{ + player_movement::player_sustainAmmo = game::native::Dvar_RegisterBool("player_sustainAmmo", + false, game::native::DVAR_CODINFO, "Firing weapon will not decrease clip ammo"); + + utils::hook(0x648C3A, &player_movement::pm_weapon_use_ammo, HOOK_CALL).install()->quick(); + utils::hook(0x64891D, &player_movement::pm_weapon_use_ammo, HOOK_CALL).install()->quick(); + utils::hook(0x6484E2, &player_movement::pm_weapon_use_ammo, HOOK_CALL).install()->quick(); +} + +REGISTER_MODULE(player_movement); diff --git a/src/module/player_movement.hpp b/src/module/player_movement.hpp new file mode 100644 index 0000000..1066761 --- /dev/null +++ b/src/module/player_movement.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "loader/module_loader.hpp" +#include "game/game.hpp" + +class player_movement final : public module +{ +public: + void post_load() override; + +private: + static const game::native::dvar_t* player_sustainAmmo; + + static void pm_weapon_use_ammo(game::native::playerState_s* ps, const game::native::Weapon weapon, + bool isAlternate, int amount, game::native::PlayerHandIndex hand); + + static const game::native::dvar_t* dvar_register_player_sustain_ammo(const char* dvar_name, + bool value, unsigned __int16 flags, const char* description); + + static void patch_mp(); + static void patch_sp(); +};