#include #include "command.hpp" #include "utils/string.hpp" #include "utils/hook.hpp" 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) { assert(game::native::cmd_args->nesting < game::native::CMD_MAX_NESTING); } int command::params::size() const { return game::native::cmd_args->argc[this->nesting_]; } 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 game 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::add_sp_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 = command::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 = command::handlers_sv.find(command); if (got != handlers_sv.end()) { got->second(entity, params); return; } 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 { pushad push edi call client_command_stub add esp, 4h popad retn } } // 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()) { 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 } 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);