From 328e6c611ace74b4aa27bb4c1a15fa1aa876ed39 Mon Sep 17 00:00:00 2001 From: m Date: Sat, 3 Sep 2022 20:40:06 -0500 Subject: [PATCH] Add legacy menus (#249) * fix legacy menu files * minor changes * make setclientdvar behave more like older games Co-authored-by: quaK <38787176+Joelrau@users.noreply.github.com> --- src/client/component/menus.cpp | 220 +++++++++++++++++++++++++++++++ src/client/component/menus.hpp | 6 + src/client/component/patches.cpp | 72 +++++++++- src/client/game/structs.hpp | 1 + src/client/game/symbols.hpp | 12 +- 5 files changed, 303 insertions(+), 8 deletions(-) create mode 100644 src/client/component/menus.cpp create mode 100644 src/client/component/menus.hpp diff --git a/src/client/component/menus.cpp b/src/client/component/menus.cpp new file mode 100644 index 00000000..de61187c --- /dev/null +++ b/src/client/component/menus.cpp @@ -0,0 +1,220 @@ +#include +#include "loader/component_loader.hpp" + +#include "menus.hpp" + +#include "game/game.hpp" + +#include "console.hpp" +#include "command.hpp" + +#include "utils/hook.hpp" +#include "utils/string.hpp" + +namespace menus +{ + void* ui_info_array; + std::string script_main_menu; + + bool UI_AllowScriptMenuResponse() + { + return *reinterpret_cast(0x3532A7C_b); + } + + bool UI_Started() + { + return *reinterpret_cast(0x2ED2074_b); + } + + bool UI_KeysBypassMenu() + { + game::dvar_t* cl_bypassMouseInput = game::Dvar_FindVar("cl_bypassMouseInput"); + if (cl_bypassMouseInput && cl_bypassMouseInput->current.enabled) + { + return true; + } + return false; + } + + void CL_ShowSystemCursor(int a1) + { + return utils::hook::invoke(0x5BAA60_b, a1); + } + + void CL_GetCursorPos(tagPOINT* position) + { + return utils::hook::invoke(0x5BA800_b, position); + } + + int Menu_Count() + { + return *reinterpret_cast(0x352F9B8_b); + } + + void* Menus_FindByName(void* dc, const char* name) + { + return utils::hook::invoke(0x1AC810_b, dc, name); + } + + void Menus_Open(void* dc, void* menu, int a3) + { + return utils::hook::invoke(0x1E1296_b, dc, menu, a3); + } + + void Display_MouseMove(void* dc) + { + return utils::hook::invoke(0x180B70_b, dc); + } + + namespace + { + game::XAssetHeader load_script_menu_internal(const char* menu) + { + const char* menu_file = utils::string::va("ui_mp/scriptmenus/%s.menu", menu); + return game::DB_FindXAssetHeader(game::ASSET_TYPE_MENUFILE, menu_file, 1); + } + + bool load_script_menu(int client_num, const char* menu) + { + game::XAssetHeader asset = load_script_menu_internal(menu); + if (asset.data) + { + game::UI_AddMenuList(ui_info_array, asset.data, 1); + return true; + } + return false; + } + + void cg_precache_script_menu(int client_num, int config_string_index) + { + const char* menu = game::CL_GetConfigString(config_string_index); + if (menu) + { + if (!load_script_menu(client_num, menu)) + { + game::Com_Error(game::ERR_DROP, "Could not load script menu file %s", menu); + } + } + } + + utils::hook::detour cg_set_config_values_hook; + void cg_set_config_values_stub(int client_num) + { + cg_set_config_values_hook.invoke(client_num); + + auto nesting = game::R_PopRemoteScreenUpdate(); + for (auto i = 3432; i < (3432 + 50); i++) + { + cg_precache_script_menu(client_num, i); + } + game::R_PushRemoteScreenUpdate(nesting); + } + + void ui_mouse_event(int client_num, int x, int y) + { + auto scrPlaceFull = game::ScrPlace_GetViewPlacement(); + auto vX = x / (game::ScrPlace_HiResGetScaleX() * scrPlaceFull->scaleVirtualToFull[0]); + auto vY = y / (game::ScrPlace_HiResGetScaleY() * scrPlaceFull->scaleVirtualToFull[1]); + *reinterpret_cast(0x352E590_b) = vX; // cursorX + *reinterpret_cast(0x352E594_b) = vY; // cursorY + int isCursorVisible = vX >= 0.0 && vX <= 640.0 && vY >= 0.0 && vY <= 480.0; + + if (isCursorVisible) + { + auto menu_count = Menu_Count(); + if (menu_count > 0) + { + *reinterpret_cast(reinterpret_cast(ui_info_array) + 16) = vX; // cursor X + *reinterpret_cast(reinterpret_cast(ui_info_array) + 20) = vY; // cursor Y + + *reinterpret_cast(reinterpret_cast(ui_info_array) + 24) = game::Sys_Milliseconds() + 200; // cursor time until ready to move + + *reinterpret_cast(reinterpret_cast(ui_info_array) + 28) = isCursorVisible; // ingame cursor visible + + Display_MouseMove(ui_info_array); + } + } + } + + int ui_mouse_fix(int cx_, int cy_, int dx_, int dy_) + { + if ((*game::keyCatchers & 0x10) != 0 && !UI_KeysBypassMenu()) + { + tagPOINT cursor; + + CL_ShowSystemCursor(0); + CL_GetCursorPos(&cursor); + + ui_mouse_event(0, cursor.x, cursor.y); + return 0; + } + return utils::hook::invoke(0x1384C0_b, cx_, cy_, dx_, dy_); + } + + bool open_script_main_menu() + { + if (!script_main_menu.empty()) + { + void* menu = Menus_FindByName(ui_info_array, script_main_menu.data()); + if (menu) + { + Menus_Open(ui_info_array, menu, 0); + return true; + } + } + return false; + } + + void ui_set_active_menu_stub(int client_num, int idx) + { + if (open_script_main_menu()) + { + *game::keyCatchers = *game::keyCatchers & 1 | 0x10; + return; + } + return utils::hook::invoke(0x1E4D80_b, client_num, idx); // UI_SetActiveMenu + } + } + + void set_script_main_menu(const std::string& menu) + { + script_main_menu = menu; + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + if (!game::environment::is_mp()) + { + return; + } + + ui_info_array = reinterpret_cast(0x352E580_b); + + // add back legacy menu precache + cg_set_config_values_hook.create(0x11AC50_b, cg_set_config_values_stub); + + // add legacy menu mouse fix + utils::hook::call(0x5BA535_b, ui_mouse_fix); + + // add script main menu (ESC) + utils::hook::call(0x135C82_b, ui_set_active_menu_stub); + + command::add("openmenu", [](const command::params& params) + { + if (params.size() != 2) + { + console::info("usage: openmenu \n"); + return; + } + + *game::keyCatchers = *game::keyCatchers & 1 | 0x10; + game::Menus_OpenByName(0, params.get(1)); + }); + } + }; +} + +REGISTER_COMPONENT(menus::component) \ No newline at end of file diff --git a/src/client/component/menus.hpp b/src/client/component/menus.hpp new file mode 100644 index 00000000..d817f790 --- /dev/null +++ b/src/client/component/menus.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace menus +{ + void set_script_main_menu(const std::string& menu); +} \ No newline at end of file diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index e5b0b822..5bf534be 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -9,6 +9,7 @@ #include "network.hpp" #include "scheduler.hpp" #include "filesystem.hpp" +#include "menus.hpp" #include "game/game.hpp" #include "game/dvars.hpp" @@ -63,17 +64,66 @@ namespace patches return com_register_dvars_hook.invoke(); } - utils::hook::detour set_client_dvar_from_server_hook; + utils::hook::detour cg_set_client_dvar_from_server_hook; - void set_client_dvar_from_server_stub(void* clientNum, void* cgameGlob, const char* dvar, const char* value) + void cg_set_client_dvar_from_server_stub(void* clientNum, void* cgameGlob, const char* dvar_hash, const char* value) { - const auto dvar_lowercase = utils::string::to_lower(dvar); - if (dvar_lowercase == "cg_fov"s || dvar_lowercase == "cg_fovMin"s) + int hash = atoi(dvar_hash); + auto* dvar = game::Dvar_FindMalleableVar(hash); + + if (hash == game::generateHashValue("cg_fov") || + hash == game::generateHashValue("cg_fovMin") || + hash == game::generateHashValue("cg_fovScale")) { return; } - set_client_dvar_from_server_hook.invoke(0x11AA90_b, clientNum, cgameGlob, dvar, value); + if (hash == game::generateHashValue("g_scriptMainMenu")) + { + menus::set_script_main_menu(value); + } + + // register new dvar + if (!dvar) + { + game::Dvar_RegisterString(hash, "", value, game::DVAR_FLAG_EXTERNAL); + return; + } + + // only set if dvar has no flags or has cheat flag or has external flag + if (dvar->flags == game::DVAR_FLAG_NONE || + (dvar->flags & game::DVAR_FLAG_CHEAT) != 0 || + (dvar->flags & game::DVAR_FLAG_EXTERNAL) != 0) + { + game::Dvar_SetFromStringFromSource(dvar, value, game::DvarSetSource::DVAR_SOURCE_EXTERNAL); + } + + // original code + int index = 0; + auto result = utils::hook::invoke(0x4745E0_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(clientNum, cgameGlob, 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.hash = game::generateHashValue(name); + return &dummy; + } + return dvar; + } + + bool get_client_dvar_hash(game::dvar_t* dvar, int* hash) + { + *hash = dvar->hash; + return true; } const char* db_read_raw_file_stub(const char* filename, char* buf, const int size) @@ -386,11 +436,19 @@ namespace patches utils::hook::inject(0x54DCE5_b, VERSION); // prevent servers overriding our fov - set_client_dvar_from_server_hook.create(0x11AA90_b, set_client_dvar_from_server_stub); utils::hook::nop(0x17DA96_b, 0x16); utils::hook::nop(0xE00BE_b, 0x17); utils::hook::set(0x307F39_b, 0xEB); + // make setclientdvar behave like older games + cg_set_client_dvar_from_server_hook.create(0x11AA90_b, cg_set_client_dvar_from_server_stub); + utils::hook::call(0x407EC5_b, get_client_dvar_hash); // setclientdvar + utils::hook::call(0x4087C1_b, get_client_dvar_hash); // setclientdvars + utils::hook::call(0x407E8E_b, get_client_dvar); // setclientdvar + utils::hook::call(0x40878A_b, get_client_dvar); // setclientdvars + utils::hook::set(0x407EB6_b, 0xEB); // setclientdvar + utils::hook::set(0x4087B2_b, 0xEB); // setclientdvars + // some [data validation] anti tamper thing that kills performance dvars::override::register_int("dvl", 0, 0, 0, game::DVAR_FLAG_READ); @@ -439,4 +497,4 @@ namespace patches }; } -REGISTER_COMPONENT(patches::component) +REGISTER_COMPONENT(patches::component) \ No newline at end of file diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 8b2d7ac6..15dab835 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -882,6 +882,7 @@ namespace game DVAR_FLAG_SAVED = 0x1, DVAR_FLAG_LATCHED = 0x2, DVAR_FLAG_CHEAT = 0x4, + DVAR_FLAG_EXTERNAL = 0x100, DVAR_FLAG_REPLICATED = 0x8, DVAR_FLAG_WRITE = 0x800, DVAR_FLAG_READ = 0x2000, diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index d3cea690..e1256dfc 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -60,6 +60,7 @@ namespace game WEAK symbol CL_IsCgameInitialized{0x1A3210, 0x33C640}; WEAK symbol CL_VirtualLobbyShutdown{0x0, 0x0}; + WEAK symbol CL_GetConfigString{0x0, 0x33B820}; WEAK symbol Dvar_SetCommand{0x41BAD0, 0x1857D0}; WEAK symbol Dvar_FindVar{0x41A600, 0x183EB0}; @@ -70,6 +71,7 @@ namespace game WEAK symbol Dvar_Reset{0x41B5F0, 0x185390}; WEAK symbol Dvar_SetFromStringByNameFromSource{0x41BD90, 0x185BD0}; + WEAK symbol Dvar_SetFromStringFromSource{0x0, 0x185C60}; WEAK symbol Dvar_RegisterBool{0x419220, 0x182340}; @@ -139,6 +141,9 @@ namespace game #define R_AddCmdDrawTextWithCursor(TXT, MC, F, UNK, X, Y, XS, YS, R, C, S, CP, CC) \ H1_AddBaseDrawTextCmd(TXT, MC, F, game::R_GetFontHeight(F), X, Y, XS, YS, R, C, S, CP, CC, game::R_GetSomething(S)) + WEAK symbol R_PopRemoteScreenUpdate{0x0, 0x6A6D60}; + WEAK symbol R_PushRemoteScreenUpdate{0x0, 0x6A6E60}; + WEAK symbol Image_Setup{0x560740, 0x683890}; @@ -157,6 +162,8 @@ namespace game WEAK symbol Scr_SetObjectField{0x2E8FC0, 0x459CD0}; WEAK symbol ScrPlace_GetViewPlacement{0x1BCED0, 0x362840}; + WEAK symbol ScrPlace_HiResGetScaleX{0x0, 0x362910}; + WEAK symbol ScrPlace_HiResGetScaleY{0x0, 0x362930}; WEAK symbol DB_EnumXAssets_Internal{0x1F0BF0, 0x394C60}; @@ -174,6 +181,8 @@ namespace game WEAK symbol LUI_LeaveCriticalSection{0xF6C40, 0x26BDC0}; WEAK symbol Menu_IsMenuOpenAndVisible{0x4F43C0, 0x389F70}; + WEAK symbol Menus_OpenByName{0x0, 0x1E1270}; + WEAK symbol Menus_CloseByName{0x0, 0x1DA4C0}; WEAK symbol SL_FindString{0x3C0F50, 0x507FD0}; WEAK symbol SL_GetString{0x3C1210, 0x5083A0}; @@ -216,6 +225,7 @@ namespace game WEAK symbol UI_GetGameTypeDisplayName{0x0, 0x4DD8C0}; WEAK symbol UI_RunMenuScript{0x3F3AA0, 0x1E35B0}; WEAK symbol UI_TextWidth{0x3F5D90, 0x0}; + WEAK symbol UI_AddMenuList{0x0, 0x1D9960}; WEAK symbol UI_SafeTranslateString{0x3840A0, 0x4E8BC0}; @@ -270,7 +280,7 @@ namespace game WEAK symbol svs_numclients{0x0, 0x2DC338C}; WEAK symbol gameTime{0x0, 0x7361F9C}; - WEAK symbol sv_serverId_value{0x0, 0x0}; + WEAK symbol sv_serverId_value{0x0, 0xB7F9630}; WEAK symbol virtualLobby_loaded{0x0, 0x2E6EC9D};