From d7ffafd4e9b46c9b355d39b2e5778c1af35f81d0 Mon Sep 17 00:00:00 2001 From: m Date: Sat, 3 Sep 2022 00:23:04 -0500 Subject: [PATCH] some h2 mod stuff i'm storing here temp --- src/client/component/command.cpp | 6 + src/client/component/fastfiles.cpp | 53 +++-- src/client/component/filesystem.cpp | 220 +++++++++++++++++---- src/client/component/filesystem.hpp | 28 +-- src/client/component/localized_strings.cpp | 156 ++++++++++++++- src/client/component/localized_strings.hpp | 3 +- src/client/component/mods.cpp | 74 +++++-- src/client/component/patches.cpp | 6 +- src/client/game/structs.hpp | 7 + src/client/game/symbols.hpp | 3 + src/client/main.cpp | 1 + src/client/std_include.hpp | 3 + src/common/utils/nt.cpp | 18 +- src/common/utils/nt.hpp | 2 +- 14 files changed, 472 insertions(+), 108 deletions(-) diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index 8eda387a..90ee1596 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -9,6 +9,7 @@ #include "console.hpp" #include "game_console.hpp" #include "fastfiles.hpp" +#include "filesystem.hpp" #include "scheduler.hpp" #include "logfile.hpp" #include "dvars.hpp" @@ -130,6 +131,11 @@ namespace command dvars::on_register(dvar_name, [dvar_name, value]() { game::Dvar_SetCommand(game::generateHashValue(dvar_name.data()), "", value.data()); + + if (dvar_name == "fs_game") + { + filesystem::register_path(value); + } }); } } diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp index 14fd3562..58216ee2 100644 --- a/src/client/component/fastfiles.cpp +++ b/src/client/component/fastfiles.cpp @@ -143,6 +143,32 @@ namespace fastfiles } } + bool try_load_zone(std::string name, bool localized, bool game = false) + { + if (localized) + { + const auto language = game::SEH_GetCurrentLanguageCode(); + try_load_zone(language + "_"s + name, false); + + if (language != "eng"s) + { + try_load_zone("eng_" + name, false); + } + } + + if (!fastfiles::exists(name)) + { + return false; + } + + game::XZoneInfo info{}; + info.name = name.data(); + info.allocFlags = (game ? game::DB_ZONE_GAME : game::DB_ZONE_COMMON) | game::DB_ZONE_CUSTOM; + info.freeFlags = 0; + game::DB_LoadXAssets(&info, 1u, game::DBSyncMode::DB_LOAD_ASYNC); + return true; + } + utils::hook::detour sys_createfile_hook; HANDLE sys_create_file_stub(game::Sys_Folder folder, const char* base_filename) { @@ -206,12 +232,9 @@ namespace fastfiles // ui // common - if (fastfiles::exists("mod")) - { - data.push_back({ "mod", game::DB_ZONE_COMMON | game::DB_ZONE_CUSTOM, 0 }); - } - game::DB_LoadXAssets(data.data(), static_cast(data.size()), syncMode); + + try_load_zone("mod", true); } void load_ui_zones(game::XZoneInfo* zoneInfo, unsigned int zoneCount, game::DBSyncMode syncMode) @@ -227,8 +250,8 @@ namespace fastfiles bool exists(const std::string& zone) { - auto is_localized = game::DB_IsLocalized(zone.data()); - auto handle = game::Sys_CreateFile((is_localized ? game::SF_ZONE_LOC : game::SF_ZONE), utils::string::va("%s.ff", zone.data())); + 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 != (HANDLE)-1) { CloseHandle(handle); @@ -262,7 +285,6 @@ namespace fastfiles { db_try_load_x_file_internal_hook.create( SELECT_VALUE(0x1F5700_b, 0x39A620_b), &db_try_load_x_file_internal); - db_find_xasset_header_hook.create(game::DB_FindXAssetHeader, db_find_xasset_header_stub); g_dump_scripts = dvars::register_bool("g_dumpScripts", false, game::DVAR_FLAG_NONE, "Dump GSC scripts"); @@ -312,22 +334,11 @@ namespace fastfiles return; } - const char* name = params.get(1); - - if (!fastfiles::exists(name)) + const auto name = params.get(1); + if (!try_load_zone(name, false)) { console::warn("loadzone: zone \"%s\" could not be found!\n", name); - return; } - - game::XZoneInfo info; - info.name = name; - info.allocFlags = game::DB_ZONE_GAME; - info.freeFlags = 0; - - info.allocFlags |= game::DB_ZONE_CUSTOM; // skip extra zones with this flag! - - game::DB_LoadXAssets(&info, 1, game::DBSyncMode::DB_LOAD_ASYNC); }); } }; diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp index ab976b15..233bf416 100644 --- a/src/client/component/filesystem.cpp +++ b/src/client/component/filesystem.cpp @@ -1,72 +1,142 @@ #include #include "loader/component_loader.hpp" + +#include "console.hpp" #include "filesystem.hpp" -#include "game_module.hpp" +#include "localized_strings.hpp" #include "game/game.hpp" -#include "dvars.hpp" -#include -#include #include +#include +#include namespace filesystem { - file::file(std::string name) - : name_(std::move(name)) + namespace { - char* buffer{}; - const auto size = game::FS_ReadFile(this->name_.data(), &buffer); + bool initialized = false; - if (size >= 0 && buffer) + std::deque& get_search_paths_internal() { - this->valid_ = true; - this->buffer_.append(buffer, size); - game::FS_FreeFile(buffer); + static std::deque search_paths{}; + return search_paths; } - } - bool file::exists() const - { - return this->valid_; - } + bool is_fallback_lang() + { + static const auto* loc_language = game::Dvar_FindVar("loc_language"); + const auto id = loc_language->current.integer; + return id == 5 || id == 6 || id == 8 || id == 9 || id == 10 || id == 11 || id == 12 || id == 13 || id == 15; + } - const std::string& file::get_buffer() const - { - return this->buffer_; - } + void fs_startup_stub(const char* name) + { + console::info("[FS] Startup\n"); - const std::string& file::get_name() const - { - return this->name_; - } + initialized = true; - std::unordered_set& get_search_paths() - { - static std::unordered_set search_paths{}; - return search_paths; + filesystem::register_path(L"."); + filesystem::register_path(L"h1-mod"); + filesystem::register_path(L"data"); + + localized_strings::clear(); + + utils::hook::invoke(SELECT_VALUE(0x40D890_b, 0x189A40_b), name); + } + + std::vector get_paths(const std::filesystem::path& path) + { + std::vector paths{}; + + const auto code = game::SEH_GetCurrentLanguageName(); + + paths.push_back(path); + + if (is_fallback_lang()) + { + paths.push_back(path / "fallback"); + } + + paths.push_back(path / code); + + return paths; + } + + bool can_insert_path(const std::filesystem::path& path) + { + for (const auto& path_ : get_search_paths_internal()) + { + if (path_ == path) + { + return false; + } + } + + return true; + } + + const char* sys_default_install_path_stub() + { + static auto current_path = std::filesystem::current_path().string(); + return current_path.data(); + } } std::string read_file(const std::string& path) { - for (const auto& search_path : get_search_paths()) + for (const auto& search_path : get_search_paths_internal()) { - const auto path_ = search_path + "/" + path; - if (utils::io::file_exists(path_)) + const auto path_ = search_path / path; + if (utils::io::file_exists(path_.generic_string())) { - return utils::io::read_file(path_); + return utils::io::read_file(path_.generic_string()); } } return {}; } - bool read_file(const std::string& path, std::string* data) + bool read_file(const std::string& path, std::string* data, std::string* real_path) { - for (const auto& search_path : get_search_paths()) + for (const auto& search_path : get_search_paths_internal()) { - const auto path_ = search_path + "/" + path; - if (utils::io::read_file(path_, data)) + const auto path_ = search_path / path; + if (utils::io::read_file(path_.generic_string(), data)) + { + if (real_path != nullptr) + { + *real_path = path_.generic_string(); + } + + return true; + } + } + + return false; + } + + bool find_file(const std::string& path, std::string* real_path) + { + for (const auto& search_path : get_search_paths_internal()) + { + const auto path_ = search_path / path; + if (utils::io::file_exists(path_.generic_string())) + { + *real_path = path_.generic_string(); + return true; + } + } + + return false; + } + + bool exists(const std::string& path) + { + for (const auto& search_path : get_search_paths_internal()) + { + const auto path_ = search_path / path; + if (utils::io::file_exists(path_.generic_string())) { return true; } @@ -75,14 +145,82 @@ namespace filesystem return false; } + void register_path(const std::filesystem::path& path) + { + if (!initialized) + { + return; + } + + const auto paths = get_paths(path); + for (const auto& path_ : paths) + { + if (can_insert_path(path_)) + { + console::info("[FS] Registering path '%s'\n", path_.generic_string().data()); + get_search_paths_internal().push_front(path_); + } + } + } + + void unregister_path(const std::filesystem::path& path) + { + if (!initialized) + { + return; + } + + const auto paths = get_paths(path); + for (const auto& path_ : paths) + { + auto& search_paths = get_search_paths_internal(); + for (auto i = search_paths.begin(); i != search_paths.end();) + { + if (*i == path_) + { + console::info("[FS] Unregistering path '%s'\n", path_.generic_string().data()); + i = search_paths.erase(i); + } + else + { + ++i; + } + } + } + } + + std::vector get_search_paths() + { + std::vector paths{}; + + for (const auto& path : get_search_paths_internal()) + { + paths.push_back(path.generic_string()); + } + + return paths; + } + + std::vector get_search_paths_rev() + { + std::vector paths{}; + const auto& search_paths = get_search_paths_internal(); + + for (auto i = search_paths.rbegin(); i != search_paths.rend(); ++i) + { + paths.push_back(i->generic_string()); + } + + return paths; + } + class component final : public component_interface { public: void post_unpack() override { - get_search_paths().insert("."); - get_search_paths().insert("h1-mod"); - get_search_paths().insert("data"); + utils::hook::call(SELECT_VALUE(0x40C992_b, 0x18939F_b), fs_startup_stub); + utils::hook::jump(SELECT_VALUE(0x42CE00_b, 0x5B3440_b), sys_default_install_path_stub); // fs_game flags utils::hook::set(SELECT_VALUE(0x40D2A5_b, 0x189275_b), 0); @@ -90,4 +228,4 @@ namespace filesystem }; } -REGISTER_COMPONENT(filesystem::component) \ No newline at end of file +REGISTER_COMPONENT(filesystem::component) diff --git a/src/client/component/filesystem.hpp b/src/client/component/filesystem.hpp index c3d36dc6..67d1d236 100644 --- a/src/client/component/filesystem.hpp +++ b/src/client/component/filesystem.hpp @@ -2,22 +2,14 @@ namespace filesystem { - class file - { - public: - file(std::string name); - - bool exists() const; - const std::string& get_buffer() const; - const std::string& get_name() const; - - private: - bool valid_ = false; - std::string name_; - std::string buffer_; - }; - - std::unordered_set& get_search_paths(); std::string read_file(const std::string& path); - bool read_file(const std::string& path, std::string* data); -} \ No newline at end of file + bool read_file(const std::string& path, std::string* data, std::string* real_path = nullptr); + bool find_file(const std::string& path, std::string* real_path); + bool exists(const std::string& path); + + void register_path(const std::filesystem::path& path); + void unregister_path(const std::filesystem::path& path); + + std::vector get_search_paths(); + std::vector get_search_paths_rev(); +} diff --git a/src/client/component/localized_strings.cpp b/src/client/component/localized_strings.cpp index 1b72bea9..18c23aeb 100644 --- a/src/client/component/localized_strings.cpp +++ b/src/client/component/localized_strings.cpp @@ -1,10 +1,16 @@ #include #include "loader/component_loader.hpp" + +#include "console.hpp" +#include "filesystem.hpp" #include "localized_strings.hpp" + +#include "game/game.hpp" + #include #include #include -#include "game/game.hpp" +#include namespace localized_strings { @@ -12,7 +18,13 @@ namespace localized_strings { utils::hook::detour seh_string_ed_get_string_hook; - using localized_map = std::unordered_map; + struct localize_entry + { + std::string value{}; + bool volatile_{}; + }; + + using localized_map = std::unordered_map; utils::concurrency::container localized_overrides; const char* seh_string_ed_get_string(const char* reference) @@ -22,22 +34,155 @@ namespace localized_strings const auto entry = map.find(reference); if (entry != map.end()) { - return utils::string::va("%s", entry->second.data()); + return utils::string::va("%s", entry->second.value.data()); } return seh_string_ed_get_string_hook.invoke(reference); }); } + + game::XAssetHeader db_find_localize_entry_stub(game::XAssetType type, const char* name, int allow_create_default) + { + const auto value = localized_overrides.access([&](const localized_map& map) + -> const char* + { + const auto entry = map.find(name); + if (entry != map.end()) + { + return utils::string::va("%s", entry->second.value.data()); + } + + return nullptr; + }); + + if (value == nullptr) + { + return game::DB_FindXAssetHeader(type, name, allow_create_default); + } + + static game::LocalizeEntry entry{}; + entry.value = value; + entry.name = name; + + return static_cast(&entry); + } + + bool parse_localized_strings_file(const std::string& data) + { + rapidjson::Document j; + j.Parse(data.data()); + + if (!j.IsObject()) + { + return false; + } + + localized_overrides.access([&](localized_map& map) + { + const auto obj = j.GetObj(); + for (const auto& [key, value] : obj) + { + if (!key.IsString() || !value.IsString()) + { + continue; + } + + const auto name = key.GetString(); + const auto str = value.GetString(); + + const auto entry = map.find(name); + if (entry == map.end() || entry->second.volatile_) + { + map[name] = {str, true}; + } + } + }); + + return true; + } + + bool try_load_file(const std::string& path, const std::string& language) + { + const auto file = utils::string::va("%s/localizedstrings/%s.json", path.data(), language.data()); + if (!utils::io::file_exists(file)) + { + return false; + } + + console::info("Parsing %s\n", file); + const auto data = utils::io::read_file(file); + if (!parse_localized_strings_file(data)) + { + console::error("Invalid language json file\n"); + return false; + } + + return true; + } + + void load_localized_strings() + { + bool found = false; + + const auto search_paths = filesystem::get_search_paths_rev(); + const auto language = game::SEH_GetCurrentLanguageName(); + + for (const auto& path : search_paths) + { + bool found_in_current_path = false; + + if (try_load_file(path, "english")) + { + found_in_current_path = true; + found = true; + } + + if (language != "english"s && !try_load_file(path, language) && found_in_current_path) + { + console::warn("No valid language file found for '%s' in '%s/localizedstrings/', falling back to 'english'\n", + language, path.data()); + } + else + { + found = true; + } + } + + if (!found) + { + console::warn("[Localized strings] No valid language file found!\n"); + } + } } - void override(const std::string& key, const std::string& value) + void override(const std::string& key, const std::string& value, bool volatile_) { localized_overrides.access([&](localized_map& map) { - map[key] = value; + map[key] = {value, volatile_}; }); } + void clear() + { + localized_overrides.access([&](localized_map& map) + { + for (auto i = map.begin(); i != map.end();) + { + if (i->second.volatile_) + { + i = map.erase(i); + } + else + { + ++i; + } + } + }); + + load_localized_strings(); + } + class component final : public component_interface { public: @@ -45,6 +190,7 @@ namespace localized_strings { // Change some localized strings seh_string_ed_get_string_hook.create(SELECT_VALUE(0x3E6CE0_b, 0x585DA0_b), &seh_string_ed_get_string); + utils::hook::call(SELECT_VALUE(0x3E67C9_b, 0x585889_b), db_find_localize_entry_stub); } }; } diff --git a/src/client/component/localized_strings.hpp b/src/client/component/localized_strings.hpp index 01d15907..7a0d8f07 100644 --- a/src/client/component/localized_strings.hpp +++ b/src/client/component/localized_strings.hpp @@ -2,5 +2,6 @@ namespace localized_strings { - void override(const std::string& key, const std::string& value); + void override(const std::string& key, const std::string& value, bool volatile_ = false); + void clear(); } \ No newline at end of file diff --git a/src/client/component/mods.cpp b/src/client/component/mods.cpp index 9e0552ee..7a161e28 100644 --- a/src/client/component/mods.cpp +++ b/src/client/component/mods.cpp @@ -2,15 +2,15 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" -#include "game/dvars.hpp" #include "command.hpp" #include "console.hpp" -#include "scheduler.hpp" #include "filesystem.hpp" -#include "materials.hpp" #include "fonts.hpp" +#include "localized_strings.hpp" +#include "materials.hpp" #include "mods.hpp" +#include "scheduler.hpp" #include #include @@ -32,6 +32,8 @@ namespace mods fonts::clear(); } + localized_strings::clear(); + db_release_xassets_hook.invoke(); } @@ -40,10 +42,27 @@ namespace mods scheduler::once([]() { release_assets = true; + const auto _0 = gsl::finally([]() + { + release_assets = false; + }); + game::Com_Shutdown(""); - release_assets = false; }, scheduler::pipeline::main); } + + void full_restart(const std::string& arg) + { + auto mode = game::environment::is_mp() ? " -multiplayer "s : " -singleplayer "s; + + utils::nt::relaunch_self(mode.append(arg), true); + utils::nt::terminate(); + } + } + + bool mod_requires_restart(const std::string& path) + { + return utils::io::file_exists(path + "/mod.ff") || utils::io::file_exists(path + "/zone/mod.ff"); } class component final : public component_interface @@ -51,11 +70,6 @@ namespace mods public: void post_unpack() override { - if (!game::environment::is_sp()) - { - return; - } - if (!utils::io::directory_exists("mods")) { utils::io::create_directory("mods"); @@ -86,10 +100,21 @@ namespace mods } console::info("Loading mod %s\n", path); - filesystem::get_search_paths().erase(mod_path); - filesystem::get_search_paths().insert(path); - mod_path = path; - restart(); + + if (mod_requires_restart(mod_path) || mod_requires_restart(path)) + { + // vid_restart is still broken :( + // TODO: above was fed's comment for H2, can we actually use it just fine? + console::info("Restarting...\n"); + full_restart("+set fs_game \""s + path + "\""); + } + else + { + filesystem::unregister_path(mod_path); + filesystem::register_path(path); + mod_path = path; + restart(); + } }); command::add("unloadmod", [](const command::params& params) @@ -108,8 +133,27 @@ namespace mods } console::info("Unloading mod %s\n", mod_path.data()); - filesystem::get_search_paths().erase(mod_path); - mod_path.clear(); + + if (mod_requires_restart(mod_path)) + { + console::info("Restarting...\n"); + full_restart(""); + } + else + { + filesystem::unregister_path(mod_path); + mod_path.clear(); + restart(); + } + }); + + command::add("com_restart", []() + { + if (!game::Com_InFrontend()) + { + return; + } + restart(); }); } diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index e5b0b822..a2297004 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -84,10 +84,10 @@ namespace patches file_name.append(".cfg"); } - const auto file = filesystem::file(file_name); - if (file.exists()) + std::string buffer{}; + if (filesystem::read_file(file_name, &buffer)) { - snprintf(buf, size, "%s\n", file.get_buffer().data()); + snprintf(buf, size, "%s\n", buffer.data()); return buf; } diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 8b2d7ac6..5a5bf5db 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1421,6 +1421,12 @@ namespace game const char* name; }; + struct LocalizeEntry + { + const char* value; + const char* name; + }; + union XAssetHeader { void* data; @@ -1434,6 +1440,7 @@ namespace game TTF* ttf; XModel* model; WeaponDef* weapon; + LocalizeEntry* localize; }; struct XAsset diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index d3cea690..a1da4c04 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -212,6 +212,9 @@ namespace game WEAK symbol Sys_FileExists{0x0, 0x0}; WEAK symbol Sys_CreateFile{0x42C430, 0x5B2860}; + WEAK symbol SEH_GetCurrentLanguageCode{0x3E5FB0, 0x585090}; + WEAK symbol SEH_GetCurrentLanguageName{0x3E6030, 0x5850F0}; + WEAK symbol UI_GetMapDisplayName{0x0, 0x4DDEE0}; WEAK symbol UI_GetGameTypeDisplayName{0x0, 0x4DD8C0}; WEAK symbol UI_RunMenuScript{0x3F3AA0, 0x1E35B0}; diff --git a/src/client/main.cpp b/src/client/main.cpp index f769f457..5c3995be 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -202,6 +202,7 @@ void limit_parallel_dll_loading() int main() { ShowWindow(GetConsoleWindow(), SW_HIDE); + ShowWindow(GetConsoleWindow(), SW_SHOW); FARPROC entry_point; diff --git a/src/client/std_include.hpp b/src/client/std_include.hpp index 2b52c25c..3801db74 100644 --- a/src/client/std_include.hpp +++ b/src/client/std_include.hpp @@ -85,6 +85,9 @@ #include #include +#define RAPIDJSON_NOEXCEPT +#define RAPIDJSON_ASSERT(cond) if(cond); else throw std::runtime_error("rapidjson assert fail"); + #include #include #include diff --git a/src/common/utils/nt.cpp b/src/common/utils/nt.cpp index 5fc5273c..28f027f8 100644 --- a/src/common/utils/nt.cpp +++ b/src/common/utils/nt.cpp @@ -225,7 +225,7 @@ namespace utils::nt return std::string(LPSTR(LockResource(handle)), SizeofResource(nullptr, res)); } - void relaunch_self() + void relaunch_self(const std::string& extra_command_line, bool override_command_line) { const utils::nt::library self; @@ -238,9 +238,21 @@ namespace utils::nt char current_dir[MAX_PATH]; GetCurrentDirectoryA(sizeof(current_dir), current_dir); - auto* const command_line = GetCommandLineA(); - CreateProcessA(self.get_path().data(), command_line, nullptr, nullptr, false, NULL, nullptr, current_dir, + std::string command_line = GetCommandLineA(); + if (!extra_command_line.empty()) + { + if (override_command_line) + { + command_line = extra_command_line; + } + else + { + command_line += " " + extra_command_line; + } + } + + CreateProcessA(self.get_path().data(), command_line.data(), nullptr, nullptr, false, NULL, nullptr, current_dir, &startup_info, &process_info); if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE) CloseHandle(process_info.hThread); diff --git a/src/common/utils/nt.hpp b/src/common/utils/nt.hpp index 86001bea..ec479e88 100644 --- a/src/common/utils/nt.hpp +++ b/src/common/utils/nt.hpp @@ -105,6 +105,6 @@ namespace utils::nt __declspec(noreturn) void raise_hard_exception(); std::string load_resource(int id); - void relaunch_self(); + void relaunch_self(const std::string& extra_command_line = "", bool override_command_line = false); __declspec(noreturn) void terminate(uint32_t code = 0); }