372 lines
11 KiB
C++
372 lines
11 KiB
C++
#include <std_include.hpp>
|
|
#include "loader/component_loader.hpp"
|
|
#include "game/game.hpp"
|
|
#include "game/dvars.hpp"
|
|
|
|
#include "command.hpp"
|
|
#include "console.hpp"
|
|
#include "dvars.hpp"
|
|
#include "filesystem.hpp"
|
|
#include "scheduler.hpp"
|
|
|
|
#include <utils/hook.hpp>
|
|
#include <utils/nt.hpp>
|
|
|
|
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<void>(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<void>();
|
|
}
|
|
|
|
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_t* 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_t* 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<void>(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<std::uint8_t>(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()
|
|
{
|
|
// Register dvars
|
|
com_register_dvars_hook.create(0x140413A90, &com_register_dvars_stub);
|
|
|
|
// Use name dvar and add "saved" flags to it
|
|
utils::hook::set<uint8_t>(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<uint8_t>(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);
|
|
|
|
// 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)
|