#include #include "loader/component_loader.hpp" #include "game/game.hpp" #include "game/dvars.hpp" #include "game/engine/sv_game.hpp" #include "command.hpp" #include "console.hpp" #include "dvars.hpp" #include "filesystem.hpp" #include "scheduler.hpp" #include #include namespace patches { namespace { utils::hook::detour live_get_local_client_name_hook; const char* live_get_local_client_name() { return game::Dvar_FindVar("name")->current.string; } utils::hook::detour sv_kick_client_num_hook; void sv_kick_client_num(const int clientNum, const char* reason) { // Don't kick bot to equalize team balance. if (reason == "EXE_PLAYERKICKED_BOT_BALANCE"s) { return; } return sv_kick_client_num_hook.invoke(clientNum, reason); } std::string get_login_username() { char username[UNLEN + 1]; DWORD username_len = UNLEN + 1; if (!GetUserNameA(username, &username_len)) { return "Unknown Soldier"; } return std::string{ username, username_len - 1 }; } utils::hook::detour com_register_dvars_hook; void com_register_dvars_stub() { if (game::environment::is_mp()) { // Make name save game::Dvar_RegisterString("name", get_login_username().data(), game::DVAR_FLAG_SAVED, "Player name."); } return com_register_dvars_hook.invoke(); } game::dvar_t* register_network_fps_stub(const char* name, int, int, int, unsigned int flags, const char* desc) { return game::Dvar_RegisterInt(name, 1000, 20, 1000, flags, desc); } bool cmd_exec_patch() { const command::params exec_params{}; if (exec_params.size() == 2) { std::string file_name = exec_params.get(1); if (file_name.find(".cfg") == std::string::npos) file_name.append(".cfg"); const auto file = filesystem::file(file_name); if (file.exists()) { game::Cbuf_ExecuteBufferInternal(0, 0, file.get_buffer().data(), game::Cmd_ExecuteSingleCommand); return true; } } return false; } auto cmd_exec_stub_mp = utils::hook::assemble([](utils::hook::assembler& a) { const auto success = a.newLabel(); a.pushad64(); a.call_aligned(cmd_exec_patch); a.test(al, al); a.popad64(); a.jz(success); a.mov(edx, 0x18000); a.jmp(0x1403F7530); a.bind(success); a.jmp(0x1403F7574); }); auto cmd_exec_stub_sp = utils::hook::assemble([](utils::hook::assembler& a) { const auto success = a.newLabel(); a.pushad64(); a.call_aligned(cmd_exec_patch); a.test(al, al); a.popad64(); a.jz(success); a.mov(edx, 0x18000); a.jmp(0x1403B39C0); a.bind(success); a.jmp(0x1403B3A04); }); int dvar_command_patch() // game makes this return an int and compares with eax instead of al -_- { const command::params args{}; if (args.size() <= 0) return 0; auto* dvar = game::Dvar_FindVar(args.get(0)); if (dvar) { if (args.size() == 1) { const std::string current = game::Dvar_ValueToString(dvar, dvar->current); const std::string reset = game::Dvar_ValueToString(dvar, dvar->reset); console::info("\"%s\" is: \"%s^7\" default: \"%s^7\"\n", dvar->name, current.data(), reset.data()); console::info(" %s\n", dvars::dvar_get_domain(dvar->type, dvar->domain).data()); } else { char command[0x1000]{}; game::Dvar_GetCombinedString(command, 1); game::Dvar_SetCommand(args.get(0), command); } return 1; } return 0; } game::Font_s* get_chat_font_handle() { return game::R_RegisterFont("fonts/bigFont"); } void aim_assist_add_to_target_list(void* a1, void* a2) { if (!dvars::aimassist_enabled->current.enabled) return; game::AimAssist_AddToTargetList(a1, a2); } game::dvar_t* register_cg_fov_stub(const char* name, float value, float min, float /*max*/, const unsigned int flags, const char* description) { return game::Dvar_RegisterFloat(name, value, min, 160, flags | 1, description); } void bsp_sys_error_stub(const char* error, const char* arg1) { if (game::environment::is_dedi()) { game::Sys_Error(error, arg1); } else { scheduler::once([]() { command::execute("reconnect"); }, scheduler::pipeline::main, 1s); game::Com_Error(game::ERR_DROP, error, arg1); } } utils::hook::detour cmd_lui_notify_server_hook; void cmd_lui_notify_server_stub(game::mp::gentity_s* ent) { command::params_sv params{}; const auto menu_id = atoi(params.get(1)); const auto client = &game::mp::svs_clients[ent->s.clientNum]; // 9 => "end_game" if (menu_id == 9 && client->header.netchan.remoteAddress.type != game::NA_LOOPBACK) { return; } cmd_lui_notify_server_hook.invoke(ent); } void update_last_seen_players_stub(utils::hook::assembler& a) { const auto safe_continue = a.newLabel(); // (game's code) a.mov(rax, ptr(rbx)); // g_entities pointer // Avoid crash if pointer is nullptr a.test(rax, rax); a.jz(safe_continue); // Jump back in (game's code) a.mov(dword_ptr(rax, 0x34F8), 0); // Continue to next iter in this loop a.bind(safe_continue); a.jmp(0x1403A1A10); } } class component final : public component_interface { public: void post_unpack() override { // Increment ref-count on these LoadLibraryA("PhysXDevice64.dll"); LoadLibraryA("PhysXUpdateLoader64.dll"); // Unlock fps in main menu utils::hook::set(SELECT_VALUE(0x140242DDB, 0x1402CF58B), 0xEB); // Unlock cg_fov utils::hook::call(SELECT_VALUE(0x1401F3E96, 0x14027273C), register_cg_fov_stub); if (game::environment::is_sp()) { utils::hook::call(0x1401F3EC7, register_cg_fov_stub); } // changed max value from 85 -> 1000 if (!game::environment::is_dedi()) { dvars::override::register_int("com_maxfps", 85, 0, 1000, game::DVAR_FLAG_SAVED); } if (!game::environment::is_sp()) { //increased max limit for sv_network_fps, the lower limit is the default one. Original range is from 20 to 200 times a second. utils::hook::call(0x140476F4F, register_network_fps_stub); } // register cg_gun_ dvars with new values and flags // maybe _x can stay usable within a reasonable range? it can make scoped weapons DRASTICALLY better on high FOVs dvars::override::register_float("cg_gun_x", 0.0f, -1.0f, 2.0f, game::DVAR_FLAG_SAVED); dvars::override::register_float("cg_gun_y", 0.0f, 0.0f, 0.0f, game::DVAR_FLAG_NONE); dvars::override::register_float("cg_gun_z", 0.0f, 0.0f, 0.0f, game::DVAR_FLAG_NONE); // Register cg_fovscale with new params dvars::override::register_float("cg_fovScale", 1.0f, 0.1f, 5.0f, game::DVAR_FLAG_SAVED); // Patch Dvar_Command to print out values how CoD4 does it utils::hook::jump(SELECT_VALUE(0x1403BFCB0, 0x140416A60), dvar_command_patch); // Allow executing custom cfg files with the "exec" command utils::hook::jump(SELECT_VALUE(0x1403B39BB, 0x1403F752B), SELECT_VALUE(0x1403B3A12, 0x1403F7582)); //Use a relative jump to empty memory first utils::hook::jump(SELECT_VALUE(0x1403B3A12, 0x1403F7582), SELECT_VALUE(cmd_exec_stub_sp, cmd_exec_stub_mp), true); // Use empty memory to go to our stub first (can't do close jump, so need space for 12 bytes) // Fix mouse lag utils::hook::nop(SELECT_VALUE(0x14043E6CB, 0x140504A2B), 6); scheduler::loop([]() { SetThreadExecutionState(ES_DISPLAY_REQUIRED); }, scheduler::pipeline::main); if (game::environment::is_sp()) { patch_sp(); } else { patch_mp(); } } static void patch_mp() { // Bypass Arxan function utils::hook::nop(0x1404758C0, 16); utils::hook::jump(0x1404758C0, game::engine::SV_GameSendServerCommand, true); utils::hook::call(0x140477399, game::engine::SV_SendServerCommand); // Register dvars com_register_dvars_hook.create(0x140413A90, &com_register_dvars_stub); // Use name dvar and add "saved" flags to it utils::hook::set(0x1402C836D, 0x01); live_get_local_client_name_hook.create(0x1404FDAA0, &live_get_local_client_name); // Patch SV_KickClientNum sv_kick_client_num_hook.create(0x14046F730, &sv_kick_client_num); // Block changing name in-game utils::hook::set(0x140470300, 0xC3); // Unlock all DLC items utils::hook::set(0x1404EAC50, 0xC301B0); // Content_DoWeHaveDLCPackByName utils::hook::set(0x140599890, 0xC301B0); // Entitlements_IsIDUnlocked // Enable DLC items, extra loadouts and map selection in extinction dvars::override::register_int("extinction_map_selection_enabled", 1, 0, 1, 0); dvars::override::register_int("extendedLoadoutsEnable", 1, 0, 1, 0); dvars::override::register_int("igs_announcer", 3, 3, 3, 0); dvars::override::register_int("igs_swp", 1, 0, 1, 0); dvars::override::register_int("igs_shp", 1, 0, 1, 0); dvars::override::register_int("igs_svp", 1, 0, 1, 0); dvars::override::register_int("igs_sve", 1, 0, 1, 0); dvars::override::register_int("igs_svs", 1, 0, 1, 0); dvars::override::register_int("igs_svr", 1, 0, 1, 0); dvars::override::register_int("igs_swap", 1, 0, 1, 0); dvars::override::register_int("igs_fo", 1, 0, 1, 0); dvars::override::register_int("igs_td", 1, 0, 1, 0); dvars::override::register_int("igs_sripper", 1, 0, 1, 0); dvars::override::register_int("igs_smappacks", 1, 0, 1, 0); dvars::override::register_int("igs_sosp", 1, 0, 1, 0); dvars::override::register_int("igs_s1", 1, 0, 1, 0); dvars::override::register_int("igs_crossgame", 1, 0, 1, 0); // Required by UI scripts. Missing when joining a dedi and causes crashes game::Dvar_RegisterInt("scr_gun_winlimit", 1, 0, 10, game::DVAR_FLAG_REPLICATED, "Win limit for Gun Game"); game::Dvar_RegisterInt("scr_gun_scorelimit", 18, 1, 1000, game::DVAR_FLAG_REPLICATED, "Score limit for Gun Game"); // Patch game chat on resolutions higher than 1080p to use the right font utils::hook::call(0x14025C825, get_chat_font_handle); utils::hook::call(0x1402BC42F, get_chat_font_handle); utils::hook::call(0x1402C3699, get_chat_font_handle); dvars::aimassist_enabled = game::Dvar_RegisterBool("aimassist_enabled", true, game::DVAR_FLAG_SAVED, "Enables aim assist for controllers"); // Client side aim assist dvar utils::hook::call(0x14013B9AC, aim_assist_add_to_target_list); // Patch "Couldn't find the bsp for this map." error to not be fatal in mp utils::hook::call(0x14031E8AB, bsp_sys_error_stub); // isProfanity utils::hook::set(0x1402F61B0, 0xC3C033); // Don't register every replicated dvar as a network dvar utils::hook::nop(0x1403E984E, 5); // Dvar_ForEach // Prevent clients from ending the game as non host by sending 'end_game' lui notification cmd_lui_notify_server_hook.create(0x1403926A0, cmd_lui_notify_server_stub); // Remove cheat protection from r_umbraExclusive dvars::override::register_bool("r_umbraExclusive", false, game::DVAR_FLAG_NONE); // Patch crash caused by the server trying to kick players for 'invalid password' utils::hook::jump(0x1403A1A03, utils::hook::assemble(update_last_seen_players_stub), true); utils::hook::nop(0x1403A1A0F, 1); // ^^ utils::hook::nop(0x1403A072F, 5); // LiveStorage_RecordMovementInMatchdata } static void patch_sp() { // SP doesn't initialize WSA WSADATA wsa_data; WSAStartup(MAKEWORD(2, 2), &wsa_data); } }; } REGISTER_COMPONENT(patches::component)