From bc0372a2491907d1aae6dc7e26f0511ac670406d Mon Sep 17 00:00:00 2001 From: diamante0018 Date: Mon, 27 Jan 2025 22:28:55 +0100 Subject: [PATCH] feat(mods): add LUI menu --- src/client/component/dvar_cheats.cpp | 2 +- src/client/component/filesystem.cpp | 18 ++++--- src/client/component/mods.cpp | 77 +++++++++++++++++++++++++++ src/client/component/ui_scripting.cpp | 21 ++++++++ src/client/game/structs.hpp | 18 +++++-- src/client/game/symbols.hpp | 6 ++- 6 files changed, 127 insertions(+), 15 deletions(-) diff --git a/src/client/component/dvar_cheats.cpp b/src/client/component/dvar_cheats.cpp index 72ce8af..a06000b 100644 --- a/src/client/component/dvar_cheats.cpp +++ b/src/client/component/dvar_cheats.cpp @@ -11,7 +11,7 @@ namespace dvar_cheats { - void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::dvar_value* value) + void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::DvarValue* value) { if (dvar && dvar->name == "sv_cheats"s) { diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp index dee28a3..6abf5e5 100644 --- a/src/client/component/filesystem.cpp +++ b/src/client/component/filesystem.cpp @@ -83,20 +83,22 @@ namespace filesystem void startup() { - register_path("iw6"); + const auto base = std::filesystem::current_path(); + + register_path(base / "iw6"); register_path(get_binary_directory() + "\\data"); - if (get_binary_directory() != std::filesystem::current_path()) + if (get_binary_directory() != base) { - register_path(std::filesystem::current_path() / "data"); + register_path(base / "data"); } // game's search paths - register_path("devraw"); - register_path("devraw_shared"); - register_path("raw_shared"); - register_path("raw"); - register_path("main"); + register_path(base / "devraw"); + register_path(base / "devraw_shared"); + register_path(base / "raw_shared"); + register_path(base / "raw"); + register_path(base / "main"); } void check_for_startup() diff --git a/src/client/component/mods.cpp b/src/client/component/mods.cpp index fd588b4..e6612a3 100644 --- a/src/client/component/mods.cpp +++ b/src/client/component/mods.cpp @@ -3,9 +3,12 @@ #include "game/game.hpp" #include "game/dvars.hpp" +#include "command.hpp" +#include "console.hpp" #include "mods.hpp" #include +#include namespace mods { @@ -98,6 +101,40 @@ namespace mods a.and_(ebp, r15d); a.jmp(0x1403217F6); }); + + bool fs_game_dir_domain_func(game::dvar_t* dvar, game::DvarValue new_value) + { + if (*new_value.string == '\0') + { + return true; + } + + if (game::I_strnicmp(new_value.string, "mods", 4) != 0) + { + game::LiveStorage_StatsWriteNotNeeded(game::CONTROLLER_INDEX_0); + console::error("ERROR: Invalid server value '%s' for '%s'\n", new_value.string, dvar->name); + return false; + } + + if (5 < std::strlen(new_value.string) && (new_value.string[4] == '\\' || new_value.string[4] == '/')) + { + const auto* s1 = std::strstr(new_value.string, ".."); + const auto* s2 = std::strstr(new_value.string, "::"); + if (s1 == nullptr && s2 == nullptr) + { + return true; + } + + game::LiveStorage_StatsWriteNotNeeded(game::CONTROLLER_INDEX_0); + console::error("ERROR: Invalid server value '%s' for '%s'\n", new_value.string, dvar->name); + return false; + } + + // Invalid path specified + game::LiveStorage_StatsWriteNotNeeded(game::CONTROLLER_INDEX_0); + console::error("ERROR: Invalid server value '%s' for '%s'\n", new_value.string, dvar->name); + return false; + } } bool is_using_mods() @@ -136,6 +173,8 @@ namespace mods // Remove DVAR_INIT from fs_game utils::hook::set(SELECT_VALUE(0x14041C085 + 2, 0x1404DDA45 + 2), SELECT_VALUE(game::DVAR_FLAG_NONE, game::DVAR_FLAG_SERVERINFO)); + utils::hook::inject(SELECT_VALUE(0x14041C097 + 3, 0x1404DDA57 + 3), &fs_game_dir_domain_func); + if (game::environment::is_sp()) { return; @@ -150,6 +189,44 @@ namespace mods // Load mod.ff utils::hook::call(0x1405E7113, db_load_x_assets_stub); // R_LoadGraphicsAssets According to myself but I don't remember where I got it from + + command::add("loadmod", [](const command::params& params) -> void + { + if (params.size() != 2) + { + console::info("USAGE: %s \"mods/\"", params.get(0)); + return; + } + + std::string mod_name = utils::string::to_lower(params.get(1)); + + if (!mod_name.empty() && !mod_name.starts_with("mods/")) + { + mod_name = "mods/" + mod_name; + } + + // change fs_game if needed + if (mod_name != (*dvars::fs_gameDirVar)->current.string) + { + game::Dvar_SetString((*dvars::fs_gameDirVar), mod_name.c_str()); + command::execute("vid_restart\n"); + } + }); + + command::add("unloadmod", [](const command::params& params) -> void + { + if (*dvars::fs_gameDirVar == nullptr || *(*dvars::fs_gameDirVar)->current.string == '\0') + { + return; + } + + game::Dvar_SetString(*dvars::fs_gameDirVar, ""); + command::execute("vid_restart\n"); + }); + + // TODO: without a way to monitor all the ways fs_game can be changed there is no way to detect when we + // should unregister the path from the internal filesystem we use + // HINT: It could be done in fs_game_dir_domain_func, but I haven't tested if that's the best place to monitor for changes and register/unregister the mods folder } }; } diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index 5a1f7cc..ac1e879 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -195,6 +195,27 @@ namespace ui_scripting setup_functions(); lua["print"] = function(reinterpret_cast(0x14017B120)); // hks::base_print + + lua["directoryexists"] = [](const std::string& string) + { + return utils::io::directory_exists(string); + }; + + lua["listfiles"] = [](const std::string& string) + { + return utils::io::list_files(string); + }; + + lua["directoryisempty"] = [](const std::string& string) + { + return utils::io::directory_is_empty(string); + }; + + lua["fileexists"] = [](const std::string& string) + { + return utils::io::file_exists(string); + }; + lua["table"]["unpack"] = lua["unpack"]; lua["luiglobals"] = lua; diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 10220df..7eba9b1 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -10,6 +10,14 @@ namespace game typedef vec_t vec3_t[3]; typedef vec_t vec4_t[4]; + enum ControllerIndex_t + { + INVALID_CONTROLLER_PORT = -1, + CONTROLLER_INDEX_0 = 0x0, + CONTROLLER_INDEX_FIRST = 0x0, + CONTROLLER_INDEX_COUNT = 0x1, + }; + enum { FL_GODMODE = 0x1, @@ -1171,7 +1179,7 @@ namespace game rgb = 9 // Color without alpha }; - union dvar_value + union DvarValue { bool enabled; int integer; @@ -1214,9 +1222,9 @@ namespace game unsigned int flags; //08 dvar_type type; //0C bool modified; //0D - dvar_value current; //10 - dvar_value latched; - dvar_value reset; + DvarValue current; //10 + DvarValue latched; + DvarValue reset; dvar_limits domain; }; @@ -1967,7 +1975,7 @@ namespace game { const char* szInternalName; WeaponDef* weapDef; - }; // Incomplete + }; union XAssetHeader { diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index fdcb525..b4abdc0 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -20,6 +20,7 @@ namespace game WEAK symbol BG_GetWeaponNameComplete{0, 0x140239370}; WEAK symbol BG_ClearWeaponDef{0x0, 0x140238D20}; + WEAK symbol BG_BotsConnectType{0x0, 0x140217080}; WEAK symbol Com_Frame_Try_Block_Function{0x1403BC980, 0x1404131A0}; WEAK symbol Com_Parse{0x1404313E0, 0x1404F50E0}; @@ -87,7 +88,7 @@ namespace game WEAK symbol Dvar_SetString{0x14042D6E0, 0x1404F08E0}; WEAK symbol Dvar_SetFromStringByNameFromSource{0x14042D000, 0x1404F00B0}; WEAK symbol Dvar_Sort{0x14042DEF0, 0x1404F1210}; - WEAK symbol Dvar_ValueToString{0x14042E710, 0x1404F1A30}; + WEAK symbol Dvar_ValueToString{0x14042E710, 0x1404F1A30}; WEAK symbol FS_ReadFile{0x14041D0B0, 0x1404DE900}; WEAK symbol FS_FreeFile{0x14041D0A0, 0x1404DE8F0}; @@ -120,6 +121,7 @@ namespace game WEAK symbol Key_KeynumToString{0x14023D9A0, 0x1402C40E0}; WEAK symbol Live_SyncOnlineDataFlags{0, 0x1405ABF70}; + WEAK symbol LiveStorage_StatsWriteNotNeeded{0x1403BA420, 0x140409120}; WEAK symbol LiveStorage_PlayerDataSetIntByName{0x1403B8C20, 0x140404730}; WEAK symbol LiveStorage_PlayerDataSetReservedInt{0x1403B8D00, 0x140404820}; @@ -262,6 +264,8 @@ namespace game WEAK symbol LargeLocalResetToMark{0x140423B50, 0x1404E4D00}; + WEAK symbol I_strnicmp{0x140432840, 0x1404F67D0}; + WEAK symbol longjmp{0x14062E030, 0x140738060}; WEAK symbol _setjmp{0x14062F030, 0x140739060};