diff --git a/src/client/component/dvars.cpp b/src/client/component/dvars.cpp index 4f40cd3a..2a356c99 100644 --- a/src/client/component/dvars.cpp +++ b/src/client/component/dvars.cpp @@ -77,41 +77,283 @@ namespace dvars namespace disable { + static std::unordered_set re_register_disables; + static std::unordered_set de_register_disables; + void re_register(const std::string& name) + { + re_register_disables.emplace(name); + } + + void de_register(const std::string& name) + { + de_register_disables.emplace(name); + } } namespace override { + static std::unordered_map register_bool_overrides; + static std::unordered_map register_float_overrides; + static std::unordered_map register_int_overrides; + static std::unordered_map register_string_overrides; + static std::unordered_map register_vector2_overrides; + static std::unordered_map register_vector3_overrides; + void register_bool(const std::string& name, const bool value, const unsigned int flags) + { + dvar_bool values; + values.value = value; + values.flags = flags; + register_bool_overrides[name] = std::move(values); + } + + void register_float(const std::string& name, const float value, const float min, const float max, + const unsigned int flags) + { + dvar_float values; + values.value = value; + values.min = min; + values.max = max; + values.flags = flags; + register_float_overrides[name] = std::move(values); + } + + void register_int(const std::string& name, const int value, const int min, const int max, + const unsigned int flags) + { + dvar_int values; + values.value = value; + values.min = min; + values.max = max; + values.flags = flags; + register_int_overrides[name] = std::move(values); + } + + void register_string(const std::string& name, const std::string& value, + const unsigned int flags) + { + dvar_string values; + values.value = value; + values.flags = flags; + register_string_overrides[name] = std::move(values); + } + + void register_vec2(const std::string& name, float x, float y, float min, float max, + const unsigned int flags) + { + dvar_vector2 values; + values.x = x; + values.y = y; + values.min = min; + values.max = max; + values.flags = flags; + register_vector2_overrides[name] = std::move(values); + } + + void register_vec3(const std::string& name, float x, float y, float z, float min, + float max, const unsigned int flags) + { + dvar_vector3 values; + values.x = x; + values.y = y; + values.z = z; + values.min = min; + values.max = max; + values.flags = flags; + register_vector3_overrides[name] = std::move(values); + } } - std::unordered_map> dvar_on_register_function_map; - void on_register(const std::string& name, const std::function& callback) + namespace callback { - dvar_on_register_function_map[name] = callback; + std::unordered_map> dvar_on_register_function_map; + std::unordered_map> dvar_on_re_register_function_map; + std::unordered_map> dvar_on_de_register_function_map; + + void on_register(const std::string& name, const std::function& callback) + { + dvar_on_register_function_map[name] = callback; + } + + void on_re_register(const std::string& name, const std::function& callback) + { + dvar_on_re_register_function_map[name] = callback; + } + + void on_de_register(const std::string& name, const std::function& callback) + { + dvar_on_de_register_function_map[name] = callback; + } } + utils::hook::detour dvar_register_bool_hook; + utils::hook::detour dvar_register_float_hook; + utils::hook::detour dvar_register_int_hook; + utils::hook::detour dvar_register_string_hook; + utils::hook::detour dvar_register_vector2_hook; + utils::hook::detour dvar_register_vector3_hook; + + game::dvar_t* dvar_register_bool(const char* name, bool value, unsigned int flags, const char* description) + { + auto* var = find_dvar(override::register_bool_overrides, name); + if (var) + { + value = var->value; + flags = var->flags; + } + + return dvar_register_bool_hook.invoke(name, value, flags, description); + } + + game::dvar_t* dvar_register_float(const char* name, float value, float min, float max, unsigned int flags, const char* description) + { + auto* var = find_dvar(override::register_float_overrides, name); + if (var) + { + value = var->value; + min = var->min; + max = var->max; + flags = var->flags; + } + + return dvar_register_float_hook.invoke(name, value, min, max, flags, description); + } + + game::dvar_t* dvar_register_int(const char* name, int value, int min, int max, unsigned int flags, const char* description) + { + auto* var = find_dvar(override::register_int_overrides, name); + if (var) + { + value = var->value; + min = var->min; + max = var->max; + flags = var->flags; + } + + return dvar_register_int_hook.invoke(name, value, min, max, flags, description); + } + + game::dvar_t* dvar_register_string(const char* name, const char* value, unsigned int flags, const char* description) + { + auto* var = find_dvar(override::register_string_overrides, name); + if (var) + { + value = var->value.data(); + flags = var->flags; + } + + return dvar_register_string_hook.invoke(name, value, flags, description); + } + + game::dvar_t* dvar_register_vector2(const char* name, float x, float y, float min, float max, + unsigned int flags, const char* description) + { + auto* var = find_dvar(override::register_vector2_overrides, name); + if (var) + { + x = var->x; + y = var->y; + min = var->min; + max = var->max; + flags = var->flags; + } + + return dvar_register_vector2_hook.invoke(name, x, y, min, max, flags, description); + } + + game::dvar_t* dvar_register_vector3(const char* name, float x, float y, float z, float min, + float max, unsigned int flags, const char* description) + { + auto* var = find_dvar(override::register_vector3_overrides, name); + if (var) + { + x = var->x; + y = var->y; + z = var->z; + min = var->min; + max = var->max; + flags = var->flags; + } + + return dvar_register_vector3_hook.invoke(name, x, y, z, min, max, flags, description); + } + + std::recursive_mutex _mutex; + utils::hook::detour dvar_register_new_hook; + utils::hook::detour dvar_re_register_hook; + utils::hook::detour dvar_de_register_hook; + utils::hook::detour dvar_register_variant_hook; + game::dvar_t* dvar_register_new(const char* name, unsigned int checksum, game::DvarType type, unsigned int flags, game::DvarValue* value, game::DvarLimits* domain, char level, const char* description) { + std::lock_guard $(_mutex); auto* dvar = dvar_register_new_hook.invoke(name, checksum, type, flags, value, domain, level, description); - if (dvar && name && dvar_on_register_function_map.find(name) != dvar_on_register_function_map.end()) + if (dvar && name) { - dvar_on_register_function_map[name](); - dvar_on_register_function_map.erase(name); + auto* callback = find_dvar(callback::dvar_on_register_function_map, name); + if (callback) + { + (*callback)(); + } } return dvar; } - std::recursive_mutex register_var_lock; - utils::hook::detour dvar_register_variant_hook; + void dvar_re_register(game::dvar_t* dvar, const char* name, game::DvarType type, unsigned int flags, + game::DvarValue* resetValue, game::DvarLimits* domain, char level, const char* description) + { + std::lock_guard $(_mutex); + + if (dvar && name) + { + const auto disabled = find_dvar(disable::re_register_disables, name); + if (disabled) + { + return; + } + + auto* callback = find_dvar(callback::dvar_on_re_register_function_map, name); + if (callback) + { + (*callback)(); + } + } + + return dvar_re_register_hook.invoke(dvar, name, type, flags, resetValue, domain, level, description); + } + + game::dvar_t* dvar_de_register(game::dvar_t* dvar) + { + std::lock_guard $(_mutex); + + auto name = dvars::dvar_get_name(dvar); + if (dvar && !name.empty()) + { + const auto disabled = find_dvar(disable::de_register_disables, name); + if (disabled) + { + return dvar; + } + + auto* callback = find_dvar(callback::dvar_on_de_register_function_map, name); + if (callback) + { + (*callback)(); + } + } + + return dvar_de_register_hook.invoke(dvar); + } + game::dvar_t* dvar_register_variant(const char* name, unsigned int checksum, game::DvarType type, unsigned int flags, game::DvarValue* value, game::DvarLimits* domain, const char* description) { - std::lock_guard $(register_var_lock); + std::lock_guard $(_mutex); auto* dvar = dvar_register_variant_hook.invoke(name, checksum, type, flags, value, domain, description); if (dvar) @@ -134,7 +376,16 @@ namespace dvars public: void post_unpack() override { + dvar_register_bool_hook.create(0xCEB380_b, &dvar_register_bool); + dvar_register_float_hook.create(0xCEB890_b, &dvar_register_float); + dvar_register_int_hook.create(0xCEB920_b, &dvar_register_int); + dvar_register_string_hook.create(0xCEBD50_b, &dvar_register_string); + dvar_register_vector2_hook.create(0xCEBF50_b, &dvar_register_vector2); + dvar_register_vector3_hook.create(0xCEBFE0_b, &dvar_register_vector3); + dvar_register_new_hook.create(0xCEBA60_b, dvar_register_new); + dvar_re_register_hook.create(0xCEC210_b, dvar_re_register); + dvar_de_register_hook.create(0xCE9F30_b, dvar_de_register); dvar_register_variant_hook.create(0xCEBDD0_b, dvar_register_variant); } diff --git a/src/client/component/dvars.hpp b/src/client/component/dvars.hpp index e7638c03..1903a5eb 100644 --- a/src/client/component/dvars.hpp +++ b/src/client/component/dvars.hpp @@ -4,10 +4,8 @@ namespace dvars { namespace disable { - void set_bool(const std::string& name); - void set_float(const std::string& name); - void set_int(const std::string& name); - void set_string(const std::string& name); + void re_register(const std::string& name); + void de_register(const std::string& name); } namespace override @@ -18,13 +16,12 @@ namespace dvars void register_string(const std::string& name, const std::string& value, const unsigned int flags); void register_vec2(const std::string& name, float x, float y, float min, float max, const unsigned int flags); void register_vec3(const std::string& name, float x, float y, float z, float min, float max, const unsigned int flags); - - void set_bool(const std::string& name, bool boolean); - void set_float(const std::string& name, float fl); - void set_int(const std::string& name, int integer); - void set_string(const std::string& name, const std::string& string); - void set_from_string(const std::string& name, const std::string& value); } - void on_register(const std::string& name, const std::function& callback); + namespace callback + { + void on_register(const std::string& name, const std::function& callback); + void on_re_register(const std::string& name, const std::function& callback); + void on_de_register(const std::string& name, const std::function& callback); + } } \ No newline at end of file diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp index c5d049d7..101b524b 100644 --- a/src/client/component/fastfiles.cpp +++ b/src/client/component/fastfiles.cpp @@ -69,6 +69,21 @@ namespace fastfiles } } + bool exists(const std::string& zone) + { + const auto is_localized = game::DB_IsLocalized(zone.data()); + const auto handle = game::Sys_CreateFile((is_localized ? game::SF_ZONE_LOC : game::SF_ZONE), + utils::string::va("%s.ff", zone.data())); + + if (handle != INVALID_HANDLE_VALUE) + { + CloseHandle(handle); + return true; + } + + return false; + } + class component final : public component_interface { public: diff --git a/src/client/component/fastfiles.hpp b/src/client/component/fastfiles.hpp index a8709ece..f1f515dc 100644 --- a/src/client/component/fastfiles.hpp +++ b/src/client/component/fastfiles.hpp @@ -3,4 +3,5 @@ namespace fastfiles { std::string get_current_fastfile(); + bool exists(const std::string& zone); } \ No newline at end of file diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index 2aef36ee..c3ad07cc 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -4,6 +4,9 @@ #include "game/game.hpp" #include "game/dvars.hpp" +#include "fastfiles.hpp" +#include "dvars.hpp" + #include #include @@ -11,6 +14,56 @@ namespace patches { namespace { + utils::hook::detour com_register_common_dvars_hook; + utils::hook::detour cg_set_client_dvar_from_server_hook; + utils::hook::detour live_get_map_index_hook; + utils::hook::detour content_do_we_have_content_pack_hook; + + 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 }; + } + + game::dvar_t* name_dvar; + game::dvar_t* com_maxfps; + game::dvar_t* cg_fov; + game::dvar_t* cg_fovScale; + void com_register_common_dvars_stub() + { + name_dvar = game::Dvar_RegisterString("name", get_login_username().data(), game::DVAR_FLAG_SAVED, "Player name."); + com_maxfps = game::Dvar_RegisterInt("com_maxfps", 0, 0, 1000, game::DVAR_FLAG_SAVED, "Cap frames per second"); + cg_fov = game::Dvar_RegisterFloat("cg_fov", 65.0f, 1.0f, 160.f, game::DVAR_FLAG_SAVED, + "The field of view angle in degrees"); + cg_fovScale = game::Dvar_RegisterFloat("cg_fovScale", 1.0f, 0.1f, 2.0f, game::DVAR_FLAG_SAVED, + "Scale applied to the field of view"); + + *reinterpret_cast(0x6005758_b) = com_maxfps; + dvars::disable::re_register("com_maxfps"); + dvars::disable::de_register("com_maxfps"); + + *reinterpret_cast(0x1FA6DA0_b) = cg_fov; + dvars::disable::re_register("cg_fov"); + dvars::disable::de_register("cg_fov"); + + *reinterpret_cast(0x1FA6DB0_b) = cg_fovScale; + dvars::disable::re_register("cg_fovScale"); + dvars::disable::de_register("cg_fovScale"); + + return com_register_common_dvars_hook.invoke(); + } + + const char* live_get_local_client_name() + { + return game::Dvar_FindVar("name")->current.string; + } + void dvar_write_single_variable_stub(const game::dvar_t* dvar, int* user_data) { if ((dvar->flags & game::DVAR_FLAG_SAVED) != 0) @@ -28,6 +81,83 @@ namespace patches } } } + + void missing_content_error_stub(int, const char*) + { + game::Com_Error(game::ERR_DROP, utils::string::va("MISSING FILE\n%s.ff", + fastfiles::get_current_fastfile().data())); + } + + const char* stored_mapname; + int live_get_map_index_stub(const char* map) + { + stored_mapname = map; + return live_get_map_index_hook.invoke(map); + } + + bool content_do_we_have_content_pack_stub(int index) + { + if (stored_mapname != nullptr && !fastfiles::exists(stored_mapname)) + { + stored_mapname = nullptr; + return false; + } + return content_do_we_have_content_pack_hook.invoke(index); + } + + void cg_set_client_dvar_from_server_stub(void* client_num, void* cgame_glob, const char* dvar_checksum, const char* value) + { + unsigned int checksum = static_cast(atoi(dvar_checksum)); + auto* dvar = game::Dvar_FindMalleableVar(checksum); + + static unsigned int cg_fov_checksum = game::Dvar_GenerateChecksum("cg_fov"); + static unsigned int cg_fovScale_checksum = game::Dvar_GenerateChecksum("cg_fovScale"); + + if (checksum == cg_fov_checksum || + checksum == cg_fovScale_checksum) + { + return; + } + + // register new dvar + if (!dvar) + { + game::Dvar_SetFromStringByChecksum(checksum, value, game::DvarSetSource::DVAR_SOURCE_EXTERNAL); + } + // only set if dvar has no flags or has external flag + else if (dvar->flags == game::DVAR_FLAG_NONE || + (dvar->flags & game::DVAR_FLAG_EXTERNAL) != 0) + { + game::Dvar_SetFromStringFromSource(dvar, value, game::DvarSetSource::DVAR_SOURCE_EXTERNAL); + } + + // original code + unsigned int index = 0; + auto result = utils::hook::invoke<__int64>(0xB7AC60_b, dvar, &index); // NetConstStrings_SV_GetNetworkDvarIndex + if (result) + { + std::string index_str = std::to_string(index); + return cg_set_client_dvar_from_server_hook.invoke(client_num, cgame_glob, index_str.data(), value); + } + } + + game::dvar_t* get_client_dvar(const char* name) + { + game::dvar_t* dvar = game::Dvar_FindVar(name); + if (!dvar) + { + static game::dvar_t dummy{ 0 }; + dummy.checksum = game::Dvar_GenerateChecksum(name); + return &dummy; + } + return dvar; + } + + bool get_client_dvar_checksum(game::dvar_t* dvar, unsigned int* checksum) + { + *checksum = dvar->checksum; + return true; + } } class component final : public component_interface @@ -35,8 +165,34 @@ namespace patches public: void post_unpack() override { + // register custom dvars + com_register_common_dvars_hook.create(0xBADF30_b, com_register_common_dvars_stub); + + // get client name from dvar + utils::hook::jump(0xD32770_b, live_get_local_client_name); + // write better config utils::hook::jump(0xBB2A50_b, dvar_write_single_variable_stub); + + // show missing fastfiles + utils::hook::call(0x3BBD4B_b, missing_content_error_stub); + + // show missing map + stored_mapname = nullptr; + live_get_map_index_hook.create(0xCE72C0_b, live_get_map_index_stub); + content_do_we_have_content_pack_hook.create(0xCE8550_b, content_do_we_have_content_pack_stub); + + // make setclientdvar behave like older games + cg_set_client_dvar_from_server_hook.create(0x856D70_b, cg_set_client_dvar_from_server_stub); + utils::hook::call(0xB0A9BB_b, get_client_dvar_checksum); // setclientdvar + utils::hook::call(0xB0ACD7_b, get_client_dvar_checksum); // setclientdvars + utils::hook::call(0xB0A984_b, get_client_dvar); // setclientdvar + utils::hook::call(0xB0AC9F_b, get_client_dvar); // setclientdvars + utils::hook::set(0xB0A9AC_b, 0xEB); // setclientdvar + utils::hook::set(0xB0ACC8_b, 0xEB); // setclientdvars + + // don't reset our fov + utils::hook::set(0x8A6160_b, 0xC3); } }; } diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index d203bbc8..06ca5891 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -22,6 +22,23 @@ namespace game GAME_TYPE_CP = 0x3, }; + enum errorParm + { + ERR_FATAL = 0, + ERR_DROP = 1, + }; + + enum Sys_Folder + { + SF_ZONE = 0x0, + SF_ZONE_LOC = 0x1, + SF_VIDEO = 0x2, + SF_VIDEO_LOC = 0x3, + SF_PAKFILE = 0x4, + SF_PAKFILE_LOC = 0x5, + SF_COUNT = 0x6, + }; + struct CmdArgs { int nesting; @@ -55,6 +72,8 @@ namespace game DVAR_FLAG_LATCHED = 0x2, DVAR_FLAG_CHEAT = 0x4, DVAR_FLAG_REPLICATED = 0x8, + DVAR_FLAG_NETWORK = 0x10, + DVAR_FLAG_EXTERNAL = 0x100, DVAR_FLAG_WRITE = 0x800, DVAR_FLAG_READ = 0x2000, }; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 5bacc2ac..352ab03e 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -8,6 +8,8 @@ namespace game * Functions **************************************************************/ + WEAK symbol Com_Error{ 0xB8D830 }; + WEAK symbol Com_Quit_f{ 0xBADC90 }; WEAK symbol Com_FrontEndScene_IsActive{ 0x5AEBA0 }; @@ -30,6 +32,8 @@ namespace game WEAK symbol DB_IsXAssetDefault{ 0xA780D0 }; WEAK symbol DB_FindXAssetHeader{ 0xA76E00 }; + WEAK symbol DB_IsLocalized{ 0x3BC500 }; + WEAK symbol Dvar_RegisterBool{ 0xCEB380 }; WEAK symbol Dvar_RegisterVec4{ 0xCEC110 }; + WEAK symbol Dvar_SetFromStringByChecksum{ 0xCECDB0 }; + WEAK symbol Dvar_SetFromStringByName{ 0xCECF30 }; + WEAK symbol Dvar_SetFromStringFromSource{ 0xCECFF0 }; + WEAK symbol Dvar_SetCommand{ 0xCECB30 }; WEAK symbol Dvar_FindVar{ 0xCEA460 }; WEAK symbol Dvar_FindMalleableVar{ 0xCEA3C0 }; @@ -52,9 +60,7 @@ namespace game WEAK symbol Dvar_DisplayableLatchedValue{ 0xCEA1D0 }; WEAK symbol Dvar_GetCombinedString{ 0xBB1F30 }; WEAK symbol Dvar_ValueToString{ 0xCEED00 }; - WEAK symbol Dvar_GenerateChecksum{ 0xCEA520 }; -#define Dvar_GenerateHash(name) \ - Dvar_GenerateChecksum(name); + WEAK symbol Dvar_GenerateChecksum{ 0xCEA520 }; WEAK symbol FS_Printf{ 0xCDD1C0 }; @@ -83,6 +89,8 @@ namespace game WEAK symbol Sys_Milliseconds{ 0xD58110 }; + WEAK symbol Sys_CreateFile{ 0xCFDF50 }; + WEAK symbol ScrPlace_GetViewPlacement{ 0x9E4090 }; WEAK symbol SV_Cmd_TokenizeString{ 0xB7DD00 };