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(); };