From 7b3ba9d3d4d3914f26845ce0e30793dc6e0d44c7 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Thu, 10 Mar 2022 19:50:36 +0000 Subject: [PATCH] Refactor command & add client commands --- src/game/game.cpp | 97 +++++++++++++++- src/game/game.hpp | 18 +++ src/game/structs.hpp | 44 ++++++++ src/module/client_command.cpp | 142 +++++++++++++++++++++++ src/module/command.cpp | 204 ++++++++++++++++++++++++++++------ src/module/command.hpp | 58 +++++++++- src/module/fov.cpp | 2 +- src/module/test_clients.cpp | 2 +- 8 files changed, 525 insertions(+), 42 deletions(-) create mode 100644 src/module/client_command.cpp diff --git a/src/game/game.cpp b/src/game/game.cpp index 0acb006..78235a8 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -7,6 +7,8 @@ namespace game { Cmd_AddCommand_t Cmd_AddCommand; + Cmd_RemoveCommand_t Cmd_RemoveCommand; + Com_Error_t Com_Error; DB_LoadXAssets_t DB_LoadXAssets; @@ -45,6 +47,10 @@ namespace game SV_Cmd_EndTokenizedString_t SV_Cmd_EndTokenizedString; + SV_GameSendServerCommand_t SV_GameSendServerCommand; + + SV_SendServerCommand_t SV_SendServerCommand; + XUIDToString_t XUIDToString; decltype(longjmp)* _longjmp; @@ -70,6 +76,8 @@ namespace game int* svs_clientCount; + gentity_s* g_entities; + namespace mp { client_t* svs_clients; @@ -386,7 +394,7 @@ namespace game call func popad - ret + retn } } @@ -443,6 +451,30 @@ namespace game } } + __declspec(naked) void client_command_dedi(int clientNum) + { + static DWORD func = 0x47EA40; + + __asm + { + mov edi, clientNum + call func + retn + } + } + + void ClientCommand(int clientNum) + { + if (is_dedi()) + { + client_command_dedi(clientNum); + } + else if (is_mp()) + { + reinterpret_cast(0x502CB0)(clientNum); + } + } + int GetProtocolVersion() { return 0x507C; @@ -452,6 +484,61 @@ namespace game { addr->type = type; } + + __declspec(naked) void cbuf_add_text_dedi(LocalClientNum_t localClientNum, const char* text) + { + static DWORD func = 0x4CB5D0; + + __asm + { + mov eax, text + push localClientNum + call func + add esp, 4h + retn + } + } + + void Cbuf_AddText(LocalClientNum_t localClientNum, const char* text) + { + if (is_dedi()) + { + cbuf_add_text_dedi(localClientNum, text); + } + else + { + reinterpret_cast + (SELECT_VALUE(0x457C90, 0x545680, 0x0))(localClientNum, text); + } + } + + __declspec(naked) void teleport_player_dedi(gentity_s* player, float* origin, float* angles) + { + static DWORD func = 0x48B840; + + __asm + { + mov eax, player + mov ecx, origin + push angles + call func + add esp, 4h + retn + } + } + + void TeleportPlayer(gentity_s* player, float* origin, float* angles) + { + if (is_dedi()) + { + teleport_player_dedi(player, origin, angles); + } + else if (is_mp()) + { + reinterpret_cast + (0x50D840)(player, origin, angles); + } + } } launcher::mode mode = launcher::mode::none; @@ -487,6 +574,8 @@ namespace game native::Cmd_AddCommand = native::Cmd_AddCommand_t(SELECT_VALUE(0x558820, 0x545DF0, 0)); + native::Cmd_RemoveCommand = native::Cmd_RemoveCommand_t(SELECT_VALUE(0x443A30, 0x545E20, 0x4CC060)); + native::Com_Error = native::Com_Error_t(SELECT_VALUE(0x425540, 0x555450, 0x4D93F0)); native::DB_LoadXAssets = native::DB_LoadXAssets_t(SELECT_VALUE(0x48A8E0, 0x4CD020, 0x44F770)); @@ -528,6 +617,10 @@ 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_SendServerCommand = native::SV_SendServerCommand_t(SELECT_VALUE(0x0, 0x575DE0, 0x4FD5A0)); + native::XUIDToString = native::XUIDToString_t(SELECT_VALUE(0x4FAA30, 0x55CC20, 0x0)); native::_longjmp = reinterpret_cast(SELECT_VALUE(0x73AC20, 0x7363BC, 0x655558)); @@ -558,5 +651,7 @@ namespace game native::mp::svs_clients = reinterpret_cast(0x4B5CF90); native::dedi::svs_clients = reinterpret_cast(0x4A12E90); + + native::g_entities = reinterpret_cast(SELECT_VALUE(0, 0x1A66E28, 0x191B900)); } } diff --git a/src/game/game.hpp b/src/game/game.hpp index a74c659..9fa6bee 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -14,6 +14,9 @@ namespace game typedef void (*Cmd_AddCommand_t)(const char* cmdName, void (*function)(), cmd_function_t* allocedCmd); extern Cmd_AddCommand_t Cmd_AddCommand; + typedef void (*Cmd_RemoveCommand_t)(const char* cmdName); + extern Cmd_RemoveCommand_t Cmd_RemoveCommand; + typedef void (*Com_Error_t)(int code, const char* fmt, ...); extern Com_Error_t Com_Error; @@ -71,11 +74,18 @@ namespace game typedef void (*SV_Cmd_EndTokenizedString_t)(); extern SV_Cmd_EndTokenizedString_t SV_Cmd_EndTokenizedString; + typedef void (*SV_GameSendServerCommand_t)(int clientNum, svscmd_type type, const char* text); + extern SV_GameSendServerCommand_t SV_GameSendServerCommand; + + typedef void (*SV_SendServerCommand_t)(dedi::client_t* cl, svscmd_type type, const char* fmt, ...); + extern SV_SendServerCommand_t SV_SendServerCommand; + typedef void (*XUIDToString_t)(const unsigned __int64* xuid, char* str); extern XUIDToString_t XUIDToString; extern decltype(longjmp)* _longjmp; + constexpr auto CMD_MAX_NESTING = 8; extern CmdArgs* sv_cmd_args; extern CmdArgs* cmd_args; @@ -97,6 +107,8 @@ namespace game extern int* svs_clientCount; + extern gentity_s* g_entities; + namespace mp { extern client_t* svs_clients; @@ -135,9 +147,15 @@ namespace game void SV_DropClient(mp::client_t* drop, const char* reason, bool tellThem); void SV_DropAllBots(); + void ClientCommand(int clientNum); + int GetProtocolVersion(); void NetAdr_SetType(netadr_s* addr, netadrtype_t type); + + void Cbuf_AddText(LocalClientNum_t localClientNum, const char* text); + + void TeleportPlayer(gentity_s* player, float* origin, float* angles); } bool is_mp(); diff --git a/src/game/structs.hpp b/src/game/structs.hpp index 8bb8c89..c6002f9 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -4,6 +4,11 @@ namespace game { namespace native { + typedef float vec_t; + typedef vec_t vec2_t[2]; + typedef vec_t vec3_t[3]; + typedef vec_t vec4_t[4]; + enum bdLobbyErrorCode : uint32_t { BD_NO_ERROR = 0x0, @@ -369,6 +374,23 @@ namespace game }; #pragma pack(pop) + enum svscmd_type + { + SV_CMD_CAN_IGNORE, + SV_CMD_RELIABLE, + }; + + enum LocalClientNum_t + { + LOCAL_CLIENT_0 = 0, + LOCAL_CLIENT_1 = 1, + LOCAL_CLIENT_2 = 2, + LOCAL_CLIENT_3 = 3, + LOCAL_CLIENT_LAST = 3, + LOCAL_CLIENT_COUNT = 4, + LOCAL_CLIENT_INVALID = -1, + }; + struct cmd_function_t { cmd_function_t* next; @@ -594,6 +616,28 @@ namespace game static_assert(sizeof(gclient_s) == 0x3980); + enum entityFlag + { + FL_GODMODE = 0x1, + FL_DEMI_GODMODE = 0x2, + FL_NOTARGET = 0x4, + FL_NO_KNOCKBACK = 0x8, + FL_NO_RADIUS_DAMAGE = 0x10, + FL_SUPPORTS_LINKTO = 0x1000, + FL_NO_AUTO_ANIM_UPDATE = 0x2000, + FL_GRENADE_TOUCH_DAMAGE = 0x4000, + FL_STABLE_MISSILES = 0x20000, + FL_REPEAT_ANIM_UPDATE = 0x40000, + FL_VEHICLE_TARGET = 0x80000, + FL_GROUND_ENT = 0x100000, + FL_CURSOR_HINT = 0x200000, + FL_MISSILE_ATTRACTOR = 0x800000, + FL_WEAPON_BEING_GRABBED = 0x1000000, + FL_DELETE = 0x2000000, + FL_BOUNCE = 0x4000000, + FL_MOVER_SLIDE = 0x8000000 + }; + struct entityState_s { int number; diff --git a/src/module/client_command.cpp b/src/module/client_command.cpp new file mode 100644 index 0000000..5fbad50 --- /dev/null +++ b/src/module/client_command.cpp @@ -0,0 +1,142 @@ +#include +#include "loader/module_loader.hpp" +#include "command.hpp" + +#include "game/structs.hpp" +#include "game/game.hpp" + +#include "utils/string.hpp" + +class client_command final : public module +{ +public: + void post_load() override + { + if (game::is_mp()) + { + add_mp_client_commands(); + } + } + +private: + static void send_msg_to_client(int client_num, game::native::svscmd_type type, const char* string) + { + if (game::is_dedi()) + { + game::native::SV_SendServerCommand(&game::native::dedi::svs_clients[client_num], + type, string); + } + else if (game::is_mp()) + { + game::native::SV_GameSendServerCommand(client_num, type, string); + } + } + + // I know this is supposed to check sv_cheats but it's not even a registered dvar so why bother + static bool cheats_ok(game::native::gentity_s* ent) + { + if (ent->health < 1) + { + send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE, + utils::string::va("%c \"GAME_MUSTBEALIVECOMMAND\"", 0x65)); + return false; + } + + return true; + } + + static void add_mp_client_commands() + { + command::add_sv("noclip", [](game::native::gentity_s* ent, [[maybe_unused]] const command::params_sv& params) + { + if (!cheats_ok(ent)) + return; + + ent->client->flags ^= 1; + + send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE, + utils::string::va("%c \"%s\"", 0x65, (ent->client->flags & 1) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF")); + }); + + command::add_sv("ufo", [](game::native::gentity_s* ent, [[maybe_unused]] const command::params_sv& params) + { + if (!cheats_ok(ent)) + return; + + ent->client->flags ^= 2; + + send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE, + utils::string::va("%c \"%s\"", 0x65, (ent->client->flags & 2) ? "GAME_UFOON" : "GAME_UFOOFF")); + }); + + command::add_sv("god", [](game::native::gentity_s* ent, [[maybe_unused]] const command::params_sv& params) + { + if (!cheats_ok(ent)) + return; + + ent->flags = game::native::FL_GODMODE; + + send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE, + utils::string::va("%c \"%s\"", 0x65, (ent->flags & game::native::FL_GODMODE) + ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF")); + }); + + command::add_sv("demigod", [](game::native::gentity_s* ent, [[maybe_unused]] const command::params_sv& params) + { + if (!cheats_ok(ent)) + return; + + ent->flags = game::native::FL_DEMI_GODMODE; + + send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE, + utils::string::va("%c \"%s\"", 0x65, (ent->flags & game::native::FL_DEMI_GODMODE) + ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF")); + }); + + command::add_sv("notarget", [](game::native::gentity_s* ent, [[maybe_unused]] const command::params_sv& params) + { + if (!cheats_ok(ent)) + return; + + ent->flags = game::native::FL_NOTARGET; + + send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE, + utils::string::va("%c \"%s\"", 0x65, (ent->flags & game::native::FL_NOTARGET) + ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF")); + }); + + command::add_sv("setviewpos", [](game::native::gentity_s* ent, [[maybe_unused]] const command::params_sv& params) + { + if (!cheats_ok(ent)) + return; + + game::native::vec3_t origin, angles{0.f, 0.f, 0.f}; + + if (params.size() < 4 || params.size() > 6) + { + send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE, + utils::string::va("%c \"GAME_USAGE\x15: setviewpos x y z [yaw] [pitch]\"", 0x65)); + return; + } + + for (auto i = 0; i < 3; i++) + { + origin[i] = std::strtof(params.get(i + 1), nullptr); + } + + if (params.size() > 4) + { + angles[1] = std::strtof(params.get(4), nullptr); // Yaw + } + + if (params.size() == 6u) + { + angles[0] = std::strtof(params.get(5), nullptr); // Pitch + } + + game::native::TeleportPlayer(ent, origin, angles); + }); + } +}; + +REGISTER_MODULE(client_command); diff --git a/src/module/command.cpp b/src/module/command.cpp index d39c30f..e2a1860 100644 --- a/src/module/command.cpp +++ b/src/module/command.cpp @@ -1,54 +1,188 @@ #include #include "command.hpp" + #include "utils/string.hpp" -#include "game/structs.hpp" -#include "game/game.hpp" -#include "scheduler.hpp" +#include "utils/hook.hpp" utils::memory::allocator command::allocator_; -std::mutex command::mutex_; -std::unordered_map&)>> command::callbacks_; +std::unordered_map> command::handlers; +std::unordered_map> command::handlers_sv; -void command::add(const std::string& name, const std::function&)>& callback) +command::params::params() + : nesting_(game::native::cmd_args->nesting) { - std::lock_guard _(mutex_); - callbacks_[utils::string::to_lower(name)] = callback; + assert(game::native::cmd_args->nesting < game::native::CMD_MAX_NESTING); +} - const auto cmd_name = allocator_.duplicate_string(name); - const auto cmd_function = allocator_.allocate(); +int command::params::size() const +{ + return game::native::cmd_args->argc[this->nesting_]; +} - game::native::Cmd_AddCommand(cmd_name, dispatcher, cmd_function); +const char* command::params::get(const int index) const +{ + if (index >= this->size()) + { + return ""; + } + + return game::native::cmd_args->argv[this->nesting_][index]; +} + +std::string command::params::join(const int index) const +{ + std::string result; + + for (auto i = index; i < this->size(); i++) + { + if (i > index) result.append(" "); + result.append(this->get(i)); + } + + return result; +} + +command::params_sv::params_sv() + : nesting_(game::native::sv_cmd_args->nesting) +{ + assert(game::native::sv_cmd_args->nesting < game::native::CMD_MAX_NESTING); +} + +int command::params_sv::size() const +{ + return game::native::sv_cmd_args->argc[this->nesting_]; +} + +const char* command::params_sv::get(const int index) const +{ + if (index >= this->size()) + { + return ""; + } + + return game::native::sv_cmd_args->argv[this->nesting_][index]; +} + +std::string command::params_sv::join(const int index) const +{ + std::string result; + + for (auto i = index; i < this->size(); i++) + { + if (i > index) result.append(" "); + result.append(this->get(i)); + } + + return result; +} + +void command::add_raw(const char* name, void (*callback)()) +{ + game::native::Cmd_AddCommand(name, callback, command::allocator_.allocate()); +} + +void command::add(const char* name, const std::function& callback) +{ + const auto command = utils::string::to_lower(name); + + if (handlers.find(command) == handlers.end()) + { + add_raw(name, main_handler); + } + + handlers[command] = callback; +} + +void command::add(const char* name, const std::function& callback) +{ + add(name, [callback](const command::params&) + { + callback(); + }); +} + +void command::add_sv(const char* name, std::function callback) +{ + // Since the console is not usable there is no point in calling add_raw + const auto command = utils::string::to_lower(name); + + if (handlers_sv.find(command) == handlers_sv.end()) + { + handlers_sv[command] = callback; + } +} + +void command::main_handler() +{ + params params; + + const auto command = utils::string::to_lower(params[0]); + const auto got = handlers.find(command); + + if (got != handlers.end()) + { + got->second(params); + } +} + +void command::client_command_stub(int client_num) +{ + const auto entity = &game::native::g_entities[client_num]; + + if (entity->client == nullptr) + { + return; + } + + params_sv params; + + const auto command = utils::string::to_lower(params[0]); + const auto got = handlers_sv.find(command); + + if (got != handlers_sv.end()) + { + got->second(entity, params); + } + + game::native::ClientCommand(client_num); +} + +__declspec(naked) void command::client_command_dedi_stub() +{ + __asm + { + pushad + + push edi + call client_command_stub + add esp, 4h + + popad + retn + } +} + +void command::post_load() +{ + if (game::is_mp()) + { + utils::hook(0x57192A, &command::client_command_stub, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommand + } + else if (game::is_dedi()) + { + utils::hook(0x4F96B5, &command::client_command_dedi_stub, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommand + } } void command::pre_destroy() { - std::lock_guard _(mutex_); - if (!callbacks_.empty()) + for (const auto& [key, val] : command::handlers) { - callbacks_.clear(); - } -} - -void command::dispatcher() -{ - const auto cmd_index = game::native::cmd_args->nesting; - const auto arg_count = game::native::cmd_args->argc[cmd_index]; - - if (arg_count < 1) return; - - const auto command = utils::string::to_lower(game::native::cmd_args->argv[cmd_index][0]); - const auto handler = callbacks_.find(command); - if (handler == callbacks_.end()) return; - - std::vector arguments; - arguments.reserve(arg_count); - - for (auto i = 0; i < game::native::cmd_args->argc[cmd_index]; ++i) - { - arguments.emplace_back(game::native::cmd_args->argv[cmd_index][i]); + handlers.erase(key); + game::native::Cmd_RemoveCommand(key.data()); } - handler->second(arguments); + command::allocator_.clear(); } REGISTER_MODULE(command); diff --git a/src/module/command.hpp b/src/module/command.hpp index c264c7e..f7ec1a4 100644 --- a/src/module/command.hpp +++ b/src/module/command.hpp @@ -1,18 +1,68 @@ #pragma once #include "loader/module_loader.hpp" + +#include "game/structs.hpp" +#include "game/game.hpp" + #include "utils/memory.hpp" class command final : public module { public: - static void add(const std::string& name, const std::function&)>& callback); + class params + { + public: + params(); + int size() const; + const char* get(int index) const; + std::string join(int index) const; + + const char* operator[](const int index) const + { + return this->get(index); + } + + private: + int nesting_; + }; + + class params_sv + { + public: + params_sv(); + + int size() const; + const char* get(int index) const; + std::string join(int index) const; + + const char* operator[](const int index) const + { + return this->get(index); + } + + private: + int nesting_; + }; + + static void add(const char* name, const std::function& callback); + static void add(const char* name, const std::function& callback); + + static void add_sv(const char* name, std::function callback); + + void post_load() override; void pre_destroy() override; private: static utils::memory::allocator allocator_; - static std::mutex mutex_; - static std::unordered_map&)>> callbacks_; - static void dispatcher(); + static std::unordered_map> handlers; + static std::unordered_map> handlers_sv; + + static void main_handler(); + + static void client_command_stub(int client_num); + static void client_command_dedi_stub(); + + static void add_raw(const char* name, void (*callback)()); }; diff --git a/src/module/fov.cpp b/src/module/fov.cpp index ed65f64..2958b12 100644 --- a/src/module/fov.cpp +++ b/src/module/fov.cpp @@ -16,7 +16,7 @@ public: if (game::is_mp()) { // Set dvar limit - static const auto cg_fov_limit = 90.0f; + static const auto cg_fov_limit = 120.0f; utils::hook::set(0x455148, &cg_fov_limit); // Prevent value change via internal scripts diff --git a/src/module/test_clients.cpp b/src/module/test_clients.cpp index 1e88224..13b2896 100644 --- a/src/module/test_clients.cpp +++ b/src/module/test_clients.cpp @@ -158,7 +158,7 @@ void test_clients::post_load() if (game::is_mp()) this->patch_mp(); else return; // No sp/dedi bots for now :( - command::add("spawnBot", []([[maybe_unused]] const std::vector& params) + command::add("spawnBot", []() { // Because I am unable to expand the scheduler at the moment // we only get one bot at the time