From 418c4bd34ee8c5380a4144fd53df0444c41120fc Mon Sep 17 00:00:00 2001 From: m Date: Sat, 3 Sep 2022 00:10:49 -0500 Subject: [PATCH 01/12] fix system check [skip ci] --- src/client/component/system_check.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/component/system_check.cpp b/src/client/component/system_check.cpp index 64c64ed1..f29216ab 100644 --- a/src/client/component/system_check.cpp +++ b/src/client/component/system_check.cpp @@ -71,14 +71,14 @@ namespace system_check if (arxan::is_wine()) { - if (value != 0xFFB81262 && value != 0xFFB81143) + if (value == 0xFFB81262 || value == 0xFFB81143) { return; } } else { - if (value != 0x60202B6A && value != 0xBC0E9FE) + if (value == 0x60202B6A || value == 0xBC0E9FE) { return; } From d7ffafd4e9b46c9b355d39b2e5778c1af35f81d0 Mon Sep 17 00:00:00 2001 From: m Date: Sat, 3 Sep 2022 00:23:04 -0500 Subject: [PATCH 02/12] 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); } From eba62a20cabf9498285a04282fa2038921494448 Mon Sep 17 00:00:00 2001 From: m Date: Sat, 3 Sep 2022 04:50:08 -0500 Subject: [PATCH 03/12] fix fs_startup + fs_game file path --- src/client/component/command.cpp | 47 +++++++++++++++++++++--- src/client/component/dvars.cpp | 43 +++++++++++++++++----- src/client/component/dvars.hpp | 7 +++- src/client/component/filesystem.cpp | 15 +++++--- src/client/component/ui_scripting.cpp | 2 +- src/client/game/scripting/lua/engine.cpp | 2 +- src/client/main.cpp | 1 - 7 files changed, 93 insertions(+), 24 deletions(-) diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index 90ee1596..f003187d 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -28,6 +28,8 @@ namespace command std::unordered_map> handlers; std::unordered_map> handlers_sv; + std::string saved_fs_game; + void main_handler() { params params = {}; @@ -106,6 +108,33 @@ namespace command parsed = true; } + void register_fs_game_path() + { + console::debug("[FS] " __FUNCTION__ " called\n"); + + static const auto* fs_game = game::Dvar_FindVar("fs_game"); + const auto* new_mod_path = fs_game->current.string; + + // check if the last saved fs_game value isn't empty and if it doesn't equal the new fs_game + if (!saved_fs_game.empty() && saved_fs_game != new_mod_path) + { + // unregister path to be used as a fs directory + filesystem::unregister_path(saved_fs_game); + } + + if (new_mod_path && !new_mod_path[0]) + { + console::debug("[FS] " __FUNCTION__ " returning because new mod path is blank\n"); + return; + } + + console::debug("[FS] " __FUNCTION__ " registering new mod path\n"); + + // register fs_game value as a fs directory used for many things + filesystem::register_path(new_mod_path); + saved_fs_game = new_mod_path; + } + void parse_startup_variables() { auto& com_num_console_lines = *reinterpret_cast(0x35634B8_b); @@ -128,14 +157,9 @@ namespace command } else { - dvars::on_register(dvar_name, [dvar_name, value]() + dvars::callback::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); - } }); } } @@ -565,6 +589,17 @@ namespace command public: void post_unpack() override { + // monitor fs_game register and new value changes to adjust our paths for searching + dvars::callback::on_register("fs_game", []() + { + register_fs_game_path(); + }); + + dvars::callback::on_new_value("fs_game", []() + { + register_fs_game_path(); + }); + if (game::environment::is_sp()) { add_commands_sp(); diff --git a/src/client/component/dvars.cpp b/src/client/component/dvars.cpp index 416aaace..a1e4a4ee 100644 --- a/src/client/component/dvars.cpp +++ b/src/client/component/dvars.cpp @@ -252,6 +252,23 @@ namespace dvars } } + namespace callback + { + static std::unordered_map> new_value_callbacks; + + static std::unordered_map> dvar_on_register_function_map; + + void on_new_value(const std::string& name, const std::function callback) + { + new_value_callbacks[game::generateHashValue(name.data())] = callback; + } + + void on_register(const std::string& name, const std::function& callback) + { + dvar_on_register_function_map[game::generateHashValue(name.data())] = callback; + } + } + utils::hook::detour dvar_register_bool_hook; utils::hook::detour dvar_register_bool_hashed_hook; utils::hook::detour dvar_register_float_hook; @@ -271,6 +288,8 @@ namespace dvars utils::hook::detour dvar_set_string_hook; utils::hook::detour dvar_set_from_string_hook; + utils::hook::detour dvar_set_variant_hook; + game::dvar_t* dvar_register_bool(const int hash, const char* name, bool value, unsigned int flags) { auto* var = find_dvar(override::register_bool_overrides, hash); @@ -409,21 +428,15 @@ namespace dvars return dvar_register_enum_hook.invoke(hash, name, value_list, default_index, flags); } - std::unordered_map> dvar_on_register_function_map; - void on_register(const std::string& name, const std::function& callback) - { - dvar_on_register_function_map[game::generateHashValue(name.data())] = callback; - } - game::dvar_t* dvar_register_new(const int hash, const char* name, game::dvar_type type, unsigned int flags, game::dvar_value* value, game::dvar_limits* domain, const char* description) { auto* dvar = dvar_register_new_hook.invoke(hash, name, type, flags, value, domain, description); - if (dvar && dvar_on_register_function_map.find(hash) != dvar_on_register_function_map.end()) + if (dvar && callback::dvar_on_register_function_map.find(hash) != callback::dvar_on_register_function_map.end()) { - dvar_on_register_function_map[hash](); - dvar_on_register_function_map.erase(hash); + callback::dvar_on_register_function_map[hash](); + callback::dvar_on_register_function_map.erase(hash); } return dvar; @@ -514,6 +527,16 @@ namespace dvars return dvar_set_from_string_hook.invoke(dvar, string, source); } + void dvar_set_variant(game::dvar_t* dvar, game::dvar_value* value, game::DvarSetSource source) + { + dvar_set_variant_hook.invoke(dvar, value, source); + + if (callback::new_value_callbacks.find(dvar->hash) != callback::new_value_callbacks.end()) + { + callback::new_value_callbacks[dvar->hash](); + } + } + class component final : public component_interface { public: @@ -541,6 +564,8 @@ namespace dvars dvar_set_int_hook.create(SELECT_VALUE(0x41BEE0_b, 0x185D10_b), &dvar_set_int); dvar_set_string_hook.create(SELECT_VALUE(0x41C0F0_b, 0x186080_b), &dvar_set_string); dvar_set_from_string_hook.create(SELECT_VALUE(0x41BE20_b, 0x185C60_b), &dvar_set_from_string); + + dvar_set_variant_hook.create(SELECT_VALUE(0x41C190_b, 0x186120_b), &dvar_set_variant); } }; } diff --git a/src/client/component/dvars.hpp b/src/client/component/dvars.hpp index c00eafa6..3c9831c8 100644 --- a/src/client/component/dvars.hpp +++ b/src/client/component/dvars.hpp @@ -27,5 +27,10 @@ namespace dvars 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_new_value(const std::string& name, const std::function callback); + + void on_register(const std::string& name, const std::function& callback); + } } diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp index 233bf416..ccd0807e 100644 --- a/src/client/component/filesystem.cpp +++ b/src/client/component/filesystem.cpp @@ -15,6 +15,8 @@ namespace filesystem { namespace { + utils::hook::detour fs_startup_hook; + bool initialized = false; std::deque& get_search_paths_internal() @@ -32,17 +34,19 @@ namespace filesystem void fs_startup_stub(const char* name) { - console::info("[FS] Startup\n"); + console::debug("[FS] Startup\n"); initialized = true; + // hardcoded paths filesystem::register_path(L"."); filesystem::register_path(L"h1-mod"); filesystem::register_path(L"data"); + // while this clears localizations, it also calls a function to load them again localized_strings::clear(); - utils::hook::invoke(SELECT_VALUE(0x40D890_b, 0x189A40_b), name); + fs_startup_hook.invoke(name); } std::vector get_paths(const std::filesystem::path& path) @@ -157,7 +161,7 @@ namespace filesystem { if (can_insert_path(path_)) { - console::info("[FS] Registering path '%s'\n", path_.generic_string().data()); + console::debug("[FS] Registering path '%s'\n", path_.generic_string().data()); get_search_paths_internal().push_front(path_); } } @@ -178,7 +182,7 @@ namespace filesystem { if (*i == path_) { - console::info("[FS] Unregistering path '%s'\n", path_.generic_string().data()); + console::debug("[FS] Unregistering path '%s'\n", path_.generic_string().data()); i = search_paths.erase(i); } else @@ -219,7 +223,8 @@ namespace filesystem public: void post_unpack() override { - utils::hook::call(SELECT_VALUE(0x40C992_b, 0x18939F_b), fs_startup_stub); + fs_startup_hook.create(SELECT_VALUE(0x40D890_b, 0x189A40_b), fs_startup_stub); + utils::hook::jump(SELECT_VALUE(0x42CE00_b, 0x5B3440_b), sys_default_install_path_stub); // fs_game flags diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index 7c2e8ea8..ac566a2e 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -390,7 +390,7 @@ namespace ui_scripting load_script("lui_updater", lui_updater); load_script("lua_json", lua_json); - for (const auto& path : filesystem::get_search_paths()) + for (const auto& path : filesystem::get_search_paths_rev()) { load_scripts(path + "/ui_scripts/"); if (game::environment::is_sp()) diff --git a/src/client/game/scripting/lua/engine.cpp b/src/client/game/scripting/lua/engine.cpp index 6d025409..07fae1c4 100644 --- a/src/client/game/scripting/lua/engine.cpp +++ b/src/client/game/scripting/lua/engine.cpp @@ -50,7 +50,7 @@ namespace scripting::lua::engine { stop(); running = true; - for (const auto& path : filesystem::get_search_paths()) + for (const auto& path : filesystem::get_search_paths_rev()) { load_scripts(path + "/scripts/"); if (game::environment::is_sp()) diff --git a/src/client/main.cpp b/src/client/main.cpp index 5c3995be..f769f457 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -202,7 +202,6 @@ void limit_parallel_dll_loading() int main() { ShowWindow(GetConsoleWindow(), SW_HIDE); - ShowWindow(GetConsoleWindow(), SW_SHOW); FARPROC entry_point; From 9fce064632cbea67454e0bb4aa70667e935e9f36 Mon Sep 17 00:00:00 2001 From: m Date: Sat, 3 Sep 2022 05:06:45 -0500 Subject: [PATCH 04/12] add mods menu in lobby --- data/ui_scripts/mods/__init__.lua | 6 ++++-- data/ui_scripts/mods/loading.lua | 18 ++++++++++-------- data/ui_scripts/server_list/lobby.lua | 6 ++++++ src/client/component/mods.cpp | 4 ++-- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/data/ui_scripts/mods/__init__.lua b/data/ui_scripts/mods/__init__.lua index 1ca1f8d2..5c75897c 100644 --- a/data/ui_scripts/mods/__init__.lua +++ b/data/ui_scripts/mods/__init__.lua @@ -1,3 +1,5 @@ -if (game:issingleplayer()) then - require("loading") +require("loading") + +if (Engine.InFrontend()) then + require("download") end diff --git a/data/ui_scripts/mods/loading.lua b/data/ui_scripts/mods/loading.lua index 9600d8a5..85165f24 100644 --- a/data/ui_scripts/mods/loading.lua +++ b/data/ui_scripts/mods/loading.lua @@ -38,14 +38,16 @@ function string:truncate(length) return self:sub(1, length - 3) .. "..." end -LUI.addmenubutton("main_campaign", { - index = 6, - text = "@MENU_MODS", - description = Engine.Localize("@MENU_MODS_DESC"), - callback = function() - LUI.FlowManager.RequestAddMenu(nil, "mods_menu") - end -}) +if (game:issingleplayer()) then + LUI.addmenubutton("main_campaign", { + index = 6, + text = "@MENU_MODS", + description = Engine.Localize("@MENU_MODS_DESC"), + callback = function() + LUI.FlowManager.RequestAddMenu(nil, "mods_menu") + end + }) +end function getmodname(path) local name = path diff --git a/data/ui_scripts/server_list/lobby.lua b/data/ui_scripts/server_list/lobby.lua index 20027026..dc1be437 100644 --- a/data/ui_scripts/server_list/lobby.lua +++ b/data/ui_scripts/server_list/lobby.lua @@ -28,6 +28,12 @@ function menu_xboxlive(f16_arg0, f16_arg1) menu:AddBarracksButton() menu:AddPersonalizationButton() menu:AddDepotButton() + + -- kinda a weird place to do this, but it's whatever + -- add "MODS" button below depot button + local modsButton = menu:AddButton("@MENU_MODS", function(a1, a2) + LUI.FlowManager.RequestAddMenu(a1, "mods_menu", true, nil) + end) end local privateMatchButton = menu:AddButton("@MENU_PRIVATE_MATCH", MPLobbyOnline.OnPrivateMatch, diff --git a/src/client/component/mods.cpp b/src/client/component/mods.cpp index 7a161e28..e4b1b723 100644 --- a/src/client/component/mods.cpp +++ b/src/client/component/mods.cpp @@ -85,7 +85,7 @@ namespace mods return; } - if (!game::Com_InFrontend()) + if (!game::Com_InFrontend() && (game::environment::is_mp() && !game::VirtualLobby_Loaded())) { console::info("Cannot load mod while in-game!\n"); game::CG_GameMessage(0, "^1Cannot unload mod while in-game!"); @@ -125,7 +125,7 @@ namespace mods return; } - if (!game::Com_InFrontend()) + if (!game::Com_InFrontend() && (game::environment::is_mp() && !game::VirtualLobby_Loaded())) { console::info("Cannot unload mod while in-game!\n"); game::CG_GameMessage(0, "^1Cannot unload mod while in-game!"); From b913ea71294378d4f1ef8aa1c6f31dcf26726c53 Mon Sep 17 00:00:00 2001 From: m Date: Sat, 3 Sep 2022 05:25:13 -0500 Subject: [PATCH 05/12] tiny changes --- src/client/component/command.cpp | 8 ++------ src/client/component/fastfiles.cpp | 10 +++++++--- src/client/component/mods.cpp | 2 +- src/client/component/ui_scripting.cpp | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index f003187d..e01ce5fd 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -110,10 +110,8 @@ namespace command void register_fs_game_path() { - console::debug("[FS] " __FUNCTION__ " called\n"); - static const auto* fs_game = game::Dvar_FindVar("fs_game"); - const auto* new_mod_path = fs_game->current.string; + const auto new_mod_path = fs_game->current.string; // check if the last saved fs_game value isn't empty and if it doesn't equal the new fs_game if (!saved_fs_game.empty() && saved_fs_game != new_mod_path) @@ -124,12 +122,9 @@ namespace command if (new_mod_path && !new_mod_path[0]) { - console::debug("[FS] " __FUNCTION__ " returning because new mod path is blank\n"); return; } - console::debug("[FS] " __FUNCTION__ " registering new mod path\n"); - // register fs_game value as a fs directory used for many things filesystem::register_path(new_mod_path); saved_fs_game = new_mod_path; @@ -597,6 +592,7 @@ namespace command dvars::callback::on_new_value("fs_game", []() { + console::warn("fs_game value changed, filesystem paths will be adjusted to new dvar value."); register_fs_game_path(); }); diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp index 58216ee2..96f4a06c 100644 --- a/src/client/component/fastfiles.cpp +++ b/src/client/component/fastfiles.cpp @@ -172,8 +172,8 @@ namespace fastfiles utils::hook::detour sys_createfile_hook; HANDLE sys_create_file_stub(game::Sys_Folder folder, const char* base_filename) { - auto* fs_basepath = game::Dvar_FindVar("fs_basepath"); - auto* fs_game = game::Dvar_FindVar("fs_game"); + static const auto* fs_basepath = game::Dvar_FindVar("fs_basepath"); + static const auto* fs_game = game::Dvar_FindVar("fs_game"); std::string dir = fs_basepath ? fs_basepath->current.string : ""; std::string mod_dir = fs_game ? fs_game->current.string : ""; @@ -232,8 +232,12 @@ namespace fastfiles // ui // common + //try_load_zone("h1_mod_common", true); + game::DB_LoadXAssets(data.data(), static_cast(data.size()), syncMode); + // H2-mod has the try_load_zone for mod right here but before, we had it before the load assets call. guess quaK can let me know + // what to do about this try_load_zone("mod", true); } @@ -335,7 +339,7 @@ namespace fastfiles } const auto name = params.get(1); - if (!try_load_zone(name, false)) + if (!try_load_zone(name, false, true)) { console::warn("loadzone: zone \"%s\" could not be found!\n", name); } diff --git a/src/client/component/mods.cpp b/src/client/component/mods.cpp index e4b1b723..a8dcb953 100644 --- a/src/client/component/mods.cpp +++ b/src/client/component/mods.cpp @@ -149,7 +149,7 @@ namespace mods command::add("com_restart", []() { - if (!game::Com_InFrontend()) + if (!game::Com_InFrontend() && (game::environment::is_mp() && !game::VirtualLobby_Loaded())) { return; } diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index ac566a2e..31172293 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -219,7 +219,7 @@ namespace ui_scripting game_type["addlocalizedstring"] = [](const game&, const std::string& string, const std::string& value) { - localized_strings::override(string, value); + localized_strings::override(string, value, true); }; game_type["sharedset"] = [](const game&, const std::string& key, const std::string& value) From 748ab6899e5468f421eb56c0d24cdc7de4bd0a30 Mon Sep 17 00:00:00 2001 From: m Date: Sat, 3 Sep 2022 06:00:31 -0500 Subject: [PATCH 06/12] add new data directory, and update updater --- .../ui_scripts/custom_depot/__init__.lua | 0 .../custom_depot/depot_override.lua | 0 .../ui_scripts/custom_depot/mod_eula.lua | 13 + .../custom_depot/scoreboard_override.lua | 0 .../ui_scripts/discord/__init__.lua | 0 .../ui_scripts/hud_info/__init__.lua | 0 data/{ => cdata}/ui_scripts/hud_info/hud.lua | 0 .../ui_scripts/hud_info/settings.lua | 11 - data/{ => cdata}/ui_scripts/mods/__init__.lua | 0 data/{ => cdata}/ui_scripts/mods/loading.lua | 10 - .../ui_scripts/patches/__init__.lua | 0 .../patches/disable_useless_things.lua | 0 .../ui_scripts/patches/gamemodes.lua | 0 .../ui_scripts/patches/no_mode_switch.lua | 0 .../ui_scripts/patches/shader_dialog.lua} | 3 - .../ui_scripts/server_list/__init__.lua | 0 .../ui_scripts/server_list/lobby.lua | 2 - .../ui_scripts/server_list/serverlist.lua | 5 - .../{ => cdata}/ui_scripts/stats/__init__.lua | 26 -- data/scripts/logging/__init__.lua | 117 --------- data/ui_scripts/custom_depot/mod_eula.lua | 23 -- data/ui_scripts/extra_gamemodes/__init__.lua | 1 - data/ui_scripts/no_mode_switch/__init__.lua | 1 - data/zone_source/eng_h1_mod_common.csv | 1 + data/zone_source/h1_mod_common.csv | 1 + data/zonetool/localizedstrings/english.json | 53 ++++ src/client/component/filesystem.cpp | 6 +- src/client/component/updater.cpp | 227 +++++++++++++----- src/client/component/updater.hpp | 2 + src/client/main.cpp | 6 +- src/common/utils/io.cpp | 12 + src/common/utils/io.hpp | 1 + src/common/utils/properties.cpp | 24 ++ src/common/utils/properties.hpp | 6 + 34 files changed, 286 insertions(+), 265 deletions(-) rename data/{ => cdata}/ui_scripts/custom_depot/__init__.lua (100%) rename data/{ => cdata}/ui_scripts/custom_depot/depot_override.lua (100%) create mode 100644 data/cdata/ui_scripts/custom_depot/mod_eula.lua rename data/{ => cdata}/ui_scripts/custom_depot/scoreboard_override.lua (100%) rename data/{ => cdata}/ui_scripts/discord/__init__.lua (100%) rename data/{ => cdata}/ui_scripts/hud_info/__init__.lua (100%) rename data/{ => cdata}/ui_scripts/hud_info/hud.lua (100%) rename data/{ => cdata}/ui_scripts/hud_info/settings.lua (90%) rename data/{ => cdata}/ui_scripts/mods/__init__.lua (100%) rename data/{ => cdata}/ui_scripts/mods/loading.lua (83%) rename data/{ => cdata}/ui_scripts/patches/__init__.lua (100%) rename data/{ => cdata}/ui_scripts/patches/disable_useless_things.lua (100%) rename data/{ => cdata}/ui_scripts/patches/gamemodes.lua (100%) rename data/{ => cdata}/ui_scripts/patches/no_mode_switch.lua (100%) rename data/{ui_scripts/patches/shaderdialog.lua => cdata/ui_scripts/patches/shader_dialog.lua} (76%) rename data/{ => cdata}/ui_scripts/server_list/__init__.lua (100%) rename data/{ => cdata}/ui_scripts/server_list/lobby.lua (98%) rename data/{ => cdata}/ui_scripts/server_list/serverlist.lua (96%) rename data/{ => cdata}/ui_scripts/stats/__init__.lua (75%) delete mode 100644 data/scripts/logging/__init__.lua delete mode 100644 data/ui_scripts/custom_depot/mod_eula.lua delete mode 100644 data/ui_scripts/extra_gamemodes/__init__.lua delete mode 100644 data/ui_scripts/no_mode_switch/__init__.lua create mode 100644 data/zone_source/eng_h1_mod_common.csv create mode 100644 data/zone_source/h1_mod_common.csv create mode 100644 data/zonetool/localizedstrings/english.json create mode 100644 src/common/utils/properties.cpp create mode 100644 src/common/utils/properties.hpp diff --git a/data/ui_scripts/custom_depot/__init__.lua b/data/cdata/ui_scripts/custom_depot/__init__.lua similarity index 100% rename from data/ui_scripts/custom_depot/__init__.lua rename to data/cdata/ui_scripts/custom_depot/__init__.lua diff --git a/data/ui_scripts/custom_depot/depot_override.lua b/data/cdata/ui_scripts/custom_depot/depot_override.lua similarity index 100% rename from data/ui_scripts/custom_depot/depot_override.lua rename to data/cdata/ui_scripts/custom_depot/depot_override.lua diff --git a/data/cdata/ui_scripts/custom_depot/mod_eula.lua b/data/cdata/ui_scripts/custom_depot/mod_eula.lua new file mode 100644 index 00000000..6d5b597c --- /dev/null +++ b/data/cdata/ui_scripts/custom_depot/mod_eula.lua @@ -0,0 +1,13 @@ +local mod_eula = function(unk1, unk2) + return LUI.EULABase.new(CoD.CreateState(0, 0, 0, 0, CoD.AnchorTypes.All), { + textStrings = LUI.EULABase.CreateTextStrings("@CUSTOM_DEPOT_EULA_", 6), + declineCallback = function(unk3) + unk2.declineCallback(unk3) + end, + acceptCallback = function(unk4) + unk2.acceptCallback(unk4) + end + }) +end + +LUI.MenuBuilder.registerPopupType("mod_eula", mod_eula) diff --git a/data/ui_scripts/custom_depot/scoreboard_override.lua b/data/cdata/ui_scripts/custom_depot/scoreboard_override.lua similarity index 100% rename from data/ui_scripts/custom_depot/scoreboard_override.lua rename to data/cdata/ui_scripts/custom_depot/scoreboard_override.lua diff --git a/data/ui_scripts/discord/__init__.lua b/data/cdata/ui_scripts/discord/__init__.lua similarity index 100% rename from data/ui_scripts/discord/__init__.lua rename to data/cdata/ui_scripts/discord/__init__.lua diff --git a/data/ui_scripts/hud_info/__init__.lua b/data/cdata/ui_scripts/hud_info/__init__.lua similarity index 100% rename from data/ui_scripts/hud_info/__init__.lua rename to data/cdata/ui_scripts/hud_info/__init__.lua diff --git a/data/ui_scripts/hud_info/hud.lua b/data/cdata/ui_scripts/hud_info/hud.lua similarity index 100% rename from data/ui_scripts/hud_info/hud.lua rename to data/cdata/ui_scripts/hud_info/hud.lua diff --git a/data/ui_scripts/hud_info/settings.lua b/data/cdata/ui_scripts/hud_info/settings.lua similarity index 90% rename from data/ui_scripts/hud_info/settings.lua rename to data/cdata/ui_scripts/hud_info/settings.lua index 603bad07..435a56d6 100644 --- a/data/ui_scripts/hud_info/settings.lua +++ b/data/cdata/ui_scripts/hud_info/settings.lua @@ -1,16 +1,5 @@ local pcdisplay = luiglobals.require("LUI.PCDisplay") -game:addlocalizedstring("LUA_MENU_FPS", "FPS Counter") -game:addlocalizedstring("LUA_MENU_FPS_DESC", "Show FPS Counter.") - -game:addlocalizedstring("LUA_MENU_LATENCY", "Server Latency") -game:addlocalizedstring("LUA_MENU_LATENCY_DESC", "Show server latency.") - -game:addlocalizedstring("LUA_MENU_RED_DOT_BRIGHTNESS", "Red dot Brightness") -game:addlocalizedstring("LUA_MENU_RED_DOT_BRIGHTNESS_DESC", "Adjust the brightness of red dot reticles.") - -game:addlocalizedstring("MENU_SYSINFO_CUSTOMER_SUPPORT_URL", "https://h1.gg/") - function createdivider(menu, text) local element = LUI.UIElement.new({ leftAnchor = true, diff --git a/data/ui_scripts/mods/__init__.lua b/data/cdata/ui_scripts/mods/__init__.lua similarity index 100% rename from data/ui_scripts/mods/__init__.lua rename to data/cdata/ui_scripts/mods/__init__.lua diff --git a/data/ui_scripts/mods/loading.lua b/data/cdata/ui_scripts/mods/loading.lua similarity index 83% rename from data/ui_scripts/mods/loading.lua rename to data/cdata/ui_scripts/mods/loading.lua index 85165f24..66efde1a 100644 --- a/data/ui_scripts/mods/loading.lua +++ b/data/cdata/ui_scripts/mods/loading.lua @@ -1,12 +1,3 @@ -game:addlocalizedstring("MENU_MODS", "MODS") -game:addlocalizedstring("MENU_MODS_DESC", "Load installed mods.") -game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.") -game:addlocalizedstring("LUA_MENU_MOD_DESC", "&&1\nAuthor: &&2\nVersion: &&3") -game:addlocalizedstring("LUA_MENU_LOADED_MOD", "Loaded mod: ^2&&1") -game:addlocalizedstring("LUA_MENU_AVAILABLE_MODS", "Available mods") -game:addlocalizedstring("LUA_MENU_UNLOAD", "Unload") -game:addlocalizedstring("LUA_MENU_UNLOAD_DESC", "Unload the currently loaded mod.") - function createdivider(menu, text) local element = LUI.UIElement.new({ leftAnchor = true, @@ -52,7 +43,6 @@ end function getmodname(path) local name = path game:addlocalizedstring(name, name) - game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.") local desc = Engine.Localize("LUA_MENU_MOD_DESC_DEFAULT", name) local infofile = path .. "/info.json" diff --git a/data/ui_scripts/patches/__init__.lua b/data/cdata/ui_scripts/patches/__init__.lua similarity index 100% rename from data/ui_scripts/patches/__init__.lua rename to data/cdata/ui_scripts/patches/__init__.lua diff --git a/data/ui_scripts/patches/disable_useless_things.lua b/data/cdata/ui_scripts/patches/disable_useless_things.lua similarity index 100% rename from data/ui_scripts/patches/disable_useless_things.lua rename to data/cdata/ui_scripts/patches/disable_useless_things.lua diff --git a/data/ui_scripts/patches/gamemodes.lua b/data/cdata/ui_scripts/patches/gamemodes.lua similarity index 100% rename from data/ui_scripts/patches/gamemodes.lua rename to data/cdata/ui_scripts/patches/gamemodes.lua diff --git a/data/ui_scripts/patches/no_mode_switch.lua b/data/cdata/ui_scripts/patches/no_mode_switch.lua similarity index 100% rename from data/ui_scripts/patches/no_mode_switch.lua rename to data/cdata/ui_scripts/patches/no_mode_switch.lua diff --git a/data/ui_scripts/patches/shaderdialog.lua b/data/cdata/ui_scripts/patches/shader_dialog.lua similarity index 76% rename from data/ui_scripts/patches/shaderdialog.lua rename to data/cdata/ui_scripts/patches/shader_dialog.lua index 9d658d27..6c9fcbc1 100644 --- a/data/ui_scripts/patches/shaderdialog.lua +++ b/data/cdata/ui_scripts/patches/shader_dialog.lua @@ -1,8 +1,5 @@ LUI.MenuBuilder.registerPopupType("ShaderCacheDialog_original", LUI.ShaderCacheDialog.new) -game:addlocalizedstring("PLATFORM_SHADER_PRECACHE_ASK", "Would you like to populate the shader cache? It may cause crashes with certain GPUs (e.g. RTX cards) but will improve performance if successful.") -game:addlocalizedstring("MENU_NO_DONT_ASK", "No, don't ask me again") - local function dialog(...) if (game:sharedget("has_accepted_shader_caching") == "1") then return LUI.ShaderCacheDialog.new(...) diff --git a/data/ui_scripts/server_list/__init__.lua b/data/cdata/ui_scripts/server_list/__init__.lua similarity index 100% rename from data/ui_scripts/server_list/__init__.lua rename to data/cdata/ui_scripts/server_list/__init__.lua diff --git a/data/ui_scripts/server_list/lobby.lua b/data/cdata/ui_scripts/server_list/lobby.lua similarity index 98% rename from data/ui_scripts/server_list/lobby.lua rename to data/cdata/ui_scripts/server_list/lobby.lua index dc1be437..8d71585c 100644 --- a/data/ui_scripts/server_list/lobby.lua +++ b/data/cdata/ui_scripts/server_list/lobby.lua @@ -1,8 +1,6 @@ local Lobby = luiglobals.Lobby local MPLobbyOnline = LUI.mp_menus.MPLobbyOnline -game:addlocalizedstring("LUA_MENU_SERVERLIST", "SERVER LIST") - function LeaveLobby(f5_arg0) LeaveXboxLive() if Lobby.IsInPrivateParty() == false or Lobby.IsPrivatePartyHost() then diff --git a/data/ui_scripts/server_list/serverlist.lua b/data/cdata/ui_scripts/server_list/serverlist.lua similarity index 96% rename from data/ui_scripts/server_list/serverlist.lua rename to data/cdata/ui_scripts/server_list/serverlist.lua index 10e48598..0de3cdd4 100644 --- a/data/ui_scripts/server_list/serverlist.lua +++ b/data/cdata/ui_scripts/server_list/serverlist.lua @@ -5,11 +5,6 @@ if (not SystemLinkJoinMenu) then return end -game:addlocalizedstring("MENU_NUMPLAYERS", "Players") -game:addlocalizedstring("MENU_PING", "Ping") -game:addlocalizedstring("SERVERLIST_PLAYER_COUNT", "&&1 Players") -game:addlocalizedstring("SERVERLIST_SERVER_COUNT", "&&1 Servers") - local columns = { { offset = 40, diff --git a/data/ui_scripts/stats/__init__.lua b/data/cdata/ui_scripts/stats/__init__.lua similarity index 75% rename from data/ui_scripts/stats/__init__.lua rename to data/cdata/ui_scripts/stats/__init__.lua index 306c7ae0..128e4448 100644 --- a/data/ui_scripts/stats/__init__.lua +++ b/data/cdata/ui_scripts/stats/__init__.lua @@ -2,32 +2,6 @@ if (game:issingleplayer() or not Engine.InFrontend()) then return end -game:addlocalizedstring("LUA_MENU_STATS", "Stats") -game:addlocalizedstring("LUA_MENU_STATS_DESC", "Edit player stats settings.") - -game:addlocalizedstring("LUA_MENU_UNLOCKALL_ITEMS", "Unlock all items") -game:addlocalizedstring("LUA_MENU_UNLOCKALL_ITEMS_DESC", - "Whether items should be locked based on the player's stats or always unlocked.") - -game:addlocalizedstring("LUA_MENU_UNLOCKALL_LOOT", "Unlock all loot") -game:addlocalizedstring("LUA_MENU_UNLOCKALL_LOOT_DESC", - "Whether loot should be locked based on the player's stats or always unlocked.") - -game:addlocalizedstring("LUA_MENU_UNLOCKALL_CLASSES", "Unlock all classes") -game:addlocalizedstring("LUA_MENU_UNLOCKALL_CLASSES_DESC", - "Whether classes should be locked based on the player's stats or always unlocked.") - -game:addlocalizedstring("LUA_MENU_PRESTIGE", "Prestige") -game:addlocalizedstring("LUA_MENU_PRESTIGE_DESC", "Edit prestige level.") -game:addlocalizedstring("LUA_MENU_RANK", "Rank") -game:addlocalizedstring("LUA_MENU_RANK_DESC", "Edit rank.") - -game:addlocalizedstring("LUA_MENU_UNSAVED_CHANGES", "You have unsaved changes, are you sure you want to exit?") -game:addlocalizedstring("LUA_MENU_SAVE", "Save changes") -game:addlocalizedstring("LUA_MENU_SAVE_DESC", "Save changes.") -game:addlocalizedstring("LUA_MENU_SETTINGS", "Settings") -game:addlocalizedstring("LUA_MENU_EDIT_STATS", "Edit Stats") - function createdivider(menu, text) local element = LUI.UIElement.new({ leftAnchor = true, diff --git a/data/scripts/logging/__init__.lua b/data/scripts/logging/__init__.lua deleted file mode 100644 index 33db4cfc..00000000 --- a/data/scripts/logging/__init__.lua +++ /dev/null @@ -1,117 +0,0 @@ --- modified version of https://github.com/Joelrau/S1x-IW6x-g_log-script (permission to use by author) - -if (game:getdvar("gamemode") ~= "mp") then - return -end - --- setup dvars -game:setdvarifuninitialized("logfile", 1) -if (tonumber(game:getdvar("logfile")) < 1) then - return -end -game:setdvarifuninitialized("g_log", "logs/games_mp.log") - -start_time = 0 - -function get_time() - local seconds = math.floor((game:gettime() - start_time) / 1000) - local minutes = math.floor(seconds / 60) - time = string.format("%d:%02d", minutes, seconds - minutes * 60) - while (string.len(time) < 6) do - time = " " .. time - end - time = time .. " " - return time -end - -function create_path(path) - local dir = path:gsub("%/", "\\"):match("(.*[\\])") - os.execute("if not exist " .. dir .. " mkdir " .. dir) -end - -function log_print(message) - local path = game:getdvar("g_log") - local file = io.open(path, "a") - if (file == nil) then - create_path(path) - file = assert(io.open(path, "a")) - end - file:write(get_time() .. message .. "\n") - file:close() -end - -function init() - start_time = game:gettime() - - log_print("------------------------------------------------------------") - log_print("InitGame") - - -- player callbacks - level:onnotify("connected", function(player) - player:player_connected() - end) - level:onnotify("say", function(player, message, hidden) - player:say(message) - end) - level:onnotify("say_team", function(player, message, hidden) - player:say(message, "say_team") - end) - - -- damage/killed hooks - game:onplayerdamage(player_damage) - game:onplayerkilled(player_killed) - - -- other level notifies for log - level:onnotify("exitLevel_called", function() - log_print("ExitLevel: executed") - end) - level:onnotify("shutdownGame_called", function() - log_print("ShutdownGame:") - log_print("------------------------------------------------------------") - end) -end - -function entity:player_connected() - log_print(string.format("J;%s;%i;%s", self:getguid(), self:getentitynumber(), self.name)) - - self:onnotifyonce("disconnect", function() - self:disconnect() - end) -end - -function entity:disconnect() - log_print(string.format("Q;%s;%i;%s", self:getguid(), self:getentitynumber(), self.name)) -end - -function player_damage(self_, inflictor, attacker, damage, dflags, mod, weapon, vPoint, vDir, hitLoc) - if (game:isplayer(attacker) == 1) then - log_print(string.format("D;%s;%i;%s;%s;%s;%i;%s;%s;%s;%i;%s;%s", self_:getguid(), self_:getentitynumber(), - self_.team, self_.name, attacker:getguid(), attacker:getentitynumber(), attacker.team, attacker.name, - weapon, damage, mod, hitLoc)) - else - log_print(string.format("D;%s;%i;%s;%s;%s;%i;%s;%s;%s;%i;%s;%s", self_:getguid(), self_:getentitynumber(), - self_.team, self_.name, "", "-1", "world", "", weapon, damage, mod, hitLoc)) - end -end - -function player_killed(self_, inflictor, attacker, damage, mod, weapon, vDir, hitLoc, psTimeOffset, deathAnimDuration) - if (game:isplayer(attacker) == 1) then - log_print(string.format("K;%s;%i;%s;%s;%s;%i;%s;%s;%s;%i;%s;%s", self_:getguid(), self_:getentitynumber(), - self_.team, self_.name, attacker:getguid(), attacker:getentitynumber(), attacker.team, attacker.name, - weapon, damage, mod, hitLoc)) - else - log_print(string.format("K;%s;%i;%s;%s;%s;%i;%s;%s;%s;%i;%s;%s", self_:getguid(), self_:getentitynumber(), - self_.team, self_.name, "", "-1", "world", "", weapon, damage, mod, hitLoc)) - end -end - --- this function handles 'say' and 'say_team' -function entity:say(message, mode) - if (not mode) then - mode = "say" - end - - log_print(string.format("%s;%s;%i;%s;%s", mode, self:getguid(), self:getentitynumber(), self.name, message)) -end - -init() diff --git a/data/ui_scripts/custom_depot/mod_eula.lua b/data/ui_scripts/custom_depot/mod_eula.lua deleted file mode 100644 index 31bae99b..00000000 --- a/data/ui_scripts/custom_depot/mod_eula.lua +++ /dev/null @@ -1,23 +0,0 @@ -game:addlocalizedstring("CUSTOM_DEPOT_EULA_1", "Dear User,") -game:addlocalizedstring("CUSTOM_DEPOT_EULA_2", - "By using this feature, you acknowledge that you are over 18 years old, and that any sort of chance games / gambling are allowed in your country (even if they do not involve real money).") -game:addlocalizedstring("CUSTOM_DEPOT_EULA_3", - "The H1-Mod team is not responsible if you break the law within your country, and the sole responsibility will be upon you to respect the same.") -game:addlocalizedstring("CUSTOM_DEPOT_EULA_4", - "The H1-Mod team will never include real money transactions within the modified systems. The only way to get currency, should you wish to, is by playing the game.") -game:addlocalizedstring("CUSTOM_DEPOT_EULA_5", "Best Regards,") -game:addlocalizedstring("CUSTOM_DEPOT_EULA_6", "The H1-Mod Team.") - -local mod_eula = function(unk1, unk2) - return LUI.EULABase.new(CoD.CreateState(0, 0, 0, 0, CoD.AnchorTypes.All), { - textStrings = LUI.EULABase.CreateTextStrings("@CUSTOM_DEPOT_EULA_", 6), - declineCallback = function(unk3) - unk2.declineCallback(unk3) - end, - acceptCallback = function(unk4) - unk2.acceptCallback(unk4) - end - }) -end - -LUI.MenuBuilder.registerPopupType("mod_eula", mod_eula) diff --git a/data/ui_scripts/extra_gamemodes/__init__.lua b/data/ui_scripts/extra_gamemodes/__init__.lua deleted file mode 100644 index 6880b983..00000000 --- a/data/ui_scripts/extra_gamemodes/__init__.lua +++ /dev/null @@ -1 +0,0 @@ --- this patch has been moved to ui_scripts/patches/gamemodes.lua diff --git a/data/ui_scripts/no_mode_switch/__init__.lua b/data/ui_scripts/no_mode_switch/__init__.lua deleted file mode 100644 index 8761ea04..00000000 --- a/data/ui_scripts/no_mode_switch/__init__.lua +++ /dev/null @@ -1 +0,0 @@ --- this patch has been moved to ui_scripts/patches/no_mode_switch.lua diff --git a/data/zone_source/eng_h1_mod_common.csv b/data/zone_source/eng_h1_mod_common.csv new file mode 100644 index 00000000..7bf70ec8 --- /dev/null +++ b/data/zone_source/eng_h1_mod_common.csv @@ -0,0 +1 @@ +localize,english \ No newline at end of file diff --git a/data/zone_source/h1_mod_common.csv b/data/zone_source/h1_mod_common.csv new file mode 100644 index 00000000..1a7b02f3 --- /dev/null +++ b/data/zone_source/h1_mod_common.csv @@ -0,0 +1 @@ +build,eng_h1_mod_common \ No newline at end of file diff --git a/data/zonetool/localizedstrings/english.json b/data/zonetool/localizedstrings/english.json new file mode 100644 index 00000000..5b16ba14 --- /dev/null +++ b/data/zonetool/localizedstrings/english.json @@ -0,0 +1,53 @@ +{ + "CUSTOM_DEPOT_EULA_1": "Dear User,", + "CUSTOM_DEPOT_EULA_2": "By using this feature, you acknowledge that you are over the age of 18 years old, and that any sort of gambling is allowed in your country. (even if they do not involve real money)", + "CUSTOM_DEPOT_EULA_3": "The H1-mod team is not responsible if you break any law within your country, and the sole responsibility will be upon you to respect the same.", + "CUSTOM_DEPOT_EULA_4": "The H1-mod team will never include real money transactions within the modified systems. The only way to get currency, should you wish to, is by playing the game.", + "CUSTOM_DEPOT_EULA_5": "Best regards,", + "CUSTOM_DEPOT_EULA_6": "The H1-mod team.", + + "LUA_MENU_FPS": "FPS Counter", + "LUA_MENU_FPS_DESC": "Show FPS counter.", + "LUA_MENU_LATENCY": "Server Latency", + "LUA_MENU_LATENCY_DESC": "Show server latency.", + "LUA_MENU_RED_DOT_BRIGHTNESS": "Red Dot Brightness", + "LUA_MENU_RED_DOT_BRIGHTNESS_DESC": "Adjust the brightness of red dot reticles.", + + "MENU_SYSINFO_CUSTOMER_SUPPORT_URL": "https://h1.gg/", + + "MENU_MODS": "MODS", + "MENU_MODS_DESC": "Load installed mods.", + "LUA_MENU_MOD_DESC_DEFAULT": "Load &&1.", + "LUA_MENU_MOD_DESC": "&&1\nAuthor: &&2\nVersion: &&3", + "LUA_MENU_LOADED_MOD": "Loaded mod: ^2&&1", + "LUA_MENU_AVAILABLE_MODS": "Available mods", + "LUA_MENU_UNLOAD": "Unload", + "LUA_MENU_UNLOAD_DESC": "Unload the currently loaded mod.", + + "PLATFORM_SHADER_PRECACHE_ASK": "Would you like to populate the shader cache? It may cause crashes with certain GPUs (e.g. RTX cards) but will improve performance if successful.", + "MENU_NO_DONT_ASK": "No, don't ask me again", + + "LUA_MENU_SERVERLIST": "SERVER LIST", + "MENU_NUMPLAYERS": "Players", + "MENU_PING": "Ping", + "SERVERLIST_PLAYER_COUNT": "&&1 Players", + "SERVERLIST_SERVER_COUNT": "&&1 Servers", + + "LUA_MENU_STATS": "Stats", + "LUA_MENU_STATS_DESC": "Edit player stats settings.", + "LUA_MENU_UNLOCKALL_ITEMS": "Unlock all items", + "LUA_MENU_UNLOCKALL_ITEMS_DESC": "Whether items should be locked based on the player's stats or always unlocked.", + "LUA_MENU_UNLOCKALL_LOOT": "Unlock all loot", + "LUA_MENU_UNLOCKALL_LOOT_DESC": "Whether loot should be locked based on the player's stats or always unlocked.", + "LUA_MENU_UNLOCKALL_CLASSES": "Unlock all classes", + "LUA_MENU_UNLOCKALL_CLASSES_DESC": "Whether classes should be locked based on the player's stats or always unlocked.", + "LUA_MENU_PRESTIGE": "Prestige", + "LUA_MENU_PRESTIGE_DESC": "Edit prestige level.", + "LUA_MENU_RANK": "Rank", + "LUA_MENU_RANK_DESC": "Edit rank.", + "LUA_MENU_UNSAVED_CHANGES": "You have unsaved changes: are you sure you want to exit?", + "LUA_MENU_SAVE": "Save changes", + "LUA_MENU_SAVE_DESC": "Save changes.", + "LUA_MENU_SETTINGS": "Settings", + "LUA_MENU_EDIT_STATS": "Edit Stats" +} \ No newline at end of file diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp index ccd0807e..ad8fd4b4 100644 --- a/src/client/component/filesystem.cpp +++ b/src/client/component/filesystem.cpp @@ -4,12 +4,14 @@ #include "console.hpp" #include "filesystem.hpp" #include "localized_strings.hpp" +#include "updater.hpp" #include "game/game.hpp" #include -#include #include +#include +#include namespace filesystem { @@ -39,9 +41,9 @@ namespace filesystem initialized = true; // hardcoded paths + filesystem::register_path(utils::properties::get_appdata_path() / CLIENT_DATA_FOLDER); filesystem::register_path(L"."); filesystem::register_path(L"h1-mod"); - filesystem::register_path(L"data"); // while this clears localizations, it also calls a function to load them again localized_strings::clear(); diff --git a/src/client/component/updater.cpp b/src/client/component/updater.cpp index 044c81b4..460008a7 100644 --- a/src/client/component/updater.cpp +++ b/src/client/component/updater.cpp @@ -1,6 +1,7 @@ #include #include "loader/component_loader.hpp" +#include "console.hpp" #include "scheduler.hpp" #include "dvars.hpp" #include "updater.hpp" @@ -11,11 +12,12 @@ #include "game/game.hpp" #include "game/dvars.hpp" -#include #include -#include #include +#include #include +#include +#include #include #define MASTER "https://master.fed0001.xyz/h1-mod/" @@ -61,6 +63,14 @@ namespace updater std::string error{}; std::string current_file{}; std::vector required_files{}; + std::vector garbage_files{}; + }; + + // remove this at some point + std::vector old_data_files = + { + {"./data"}, + {"./cdata"}, }; utils::concurrency::container update_data; @@ -75,6 +85,18 @@ namespace updater return main; } + std::string load_binary_name() + { + utils::nt::library self; + return self.get_name(); + } + + std::string get_binary_name() + { + static const auto name = load_binary_name(); + return name; + } + void notify(const std::string& name) { scheduler::once([=]() @@ -109,9 +131,22 @@ namespace updater bool check_file(const std::string& name, const std::string& sha) { std::string data; - if (!utils::io::read_file(name, &data)) + + if (get_binary_name() == name) { - return false; + if (!utils::io::read_file(name, &data)) + { + return false; + } + } + else + { + const auto appdata_folder = utils::properties::get_appdata_path(); + const auto path = (appdata_folder / name).generic_string(); + if (!utils::io::read_file(path, &data)) + { + return false; + } } if (utils::cryptography::sha1::compute(data, true) != sha) @@ -122,18 +157,6 @@ namespace updater return true; } - std::string load_binary_name() - { - utils::nt::library self; - return self.get_name(); - } - - std::string get_binary_name() - { - static const auto name = load_binary_name(); - return name; - } - std::string get_time_str() { return utils::string::va("%i", uint32_t(time(nullptr))); @@ -144,6 +167,28 @@ namespace updater return utils::http::get_data(MASTER + select(DATA_PATH, DATA_PATH_DEV) + name + "?" + get_time_str()); } + bool has_old_data_files() + { + bool has = false; + for (const auto& file : old_data_files) + { + if (utils::io::directory_exists(file)) + { + has = true; + } + } + + return has; + } + + void delete_old_data_files() + { + for (const auto& file : old_data_files) + { + std::filesystem::remove_all(file); + } + } + bool is_update_cancelled() { return update_data.access([](update_data_t& data_) @@ -161,7 +206,16 @@ namespace updater return false; } - return utils::io::write_file(name, data); + if (get_binary_name() == name) + { + return utils::io::write_file(name, data); + } + else + { + const auto appdata_folder = utils::properties::get_appdata_path(); + const auto path = (appdata_folder / name).generic_string(); + return utils::io::write_file(path, data); + } } void delete_old_file() @@ -177,6 +231,56 @@ namespace updater }); } + std::vector find_garbage_files(const std::vector& update_files) + { + std::vector garbage_files{}; + + const auto appdata_folder = utils::properties::get_appdata_path(); + const auto path = (appdata_folder / CLIENT_DATA_FOLDER).generic_string(); + if (!utils::io::directory_exists(path)) + { + return {}; + } + + const auto current_files = utils::io::list_files_recursively(path); + for (const auto& file : current_files) + { + bool found = false; + for (const auto& update_file : update_files) + { + const auto update_file_ = (appdata_folder / update_file).generic_string(); + const auto path_a = std::filesystem::path(file); + const auto path_b = std::filesystem::path(update_file_); + const auto is_directory = utils::io::directory_exists(file); + const auto compare = path_a.compare(path_b); + + if ((is_directory && compare == -1) || compare == 0) + { + found = true; + break; + } + } + + if (!found) + { +#ifdef DEBUG + console::info("[Updater] Found extra file %s\n", file.data()); +#endif + if (file.ends_with(".ff")) + { + update_data.access([](update_data_t& data_) + { + data_.restart_required = true; + }); + } + + garbage_files.push_back(file); + } + } + + return garbage_files; + } + std::string get_mode_flag() { if (game::environment::is_mp()) @@ -199,36 +303,11 @@ namespace updater } } - // workaround void relaunch() { - if (!utils::io::file_exists(BINARY_NAME)) - { - utils::nt::terminate(0); - return; - } - - STARTUPINFOA startup_info; - PROCESS_INFORMATION process_info; - - ZeroMemory(&startup_info, sizeof(startup_info)); - ZeroMemory(&process_info, sizeof(process_info)); - startup_info.cb = sizeof(startup_info); - - char current_dir[MAX_PATH]; - GetCurrentDirectoryA(sizeof(current_dir), current_dir); - - char buf[1024] = {0}; - const auto command_line = utils::string::va("%s %s", GetCommandLineA(), get_mode_flag().data()); - strcpy_s(buf, 1024, command_line); - - CreateProcess(BINARY_NAME, buf, 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); - if (process_info.hProcess && process_info.hProcess != INVALID_HANDLE_VALUE) CloseHandle(process_info.hProcess); - - utils::nt::terminate(0); + const auto mode = game::environment::is_mp() ? "-multiplayer" : "-singleplayer"; + utils::nt::relaunch_self(mode); + utils::nt::terminate(); } void set_has_tried_update(bool tried) @@ -282,7 +361,7 @@ namespace updater { return update_data.access([](update_data_t& data_) { - return data_.required_files.size() > 0; + return data_.required_files.size() > 0 || data_.garbage_files.size() > 0 || has_old_data_files(); }); } @@ -312,9 +391,7 @@ namespace updater void cancel_update() { -#ifdef DEBUG - printf("[Updater] Cancelling update\n"); -#endif + console::debug("[Updater] Cancelling update\n"); return update_data.access([](update_data_t& data_) { @@ -327,9 +404,7 @@ namespace updater cancel_update(); reset_data(); -#ifdef DEBUG - printf("[Updater] starting update check\n"); -#endif + console::debug("[Updater] starting update check\n"); scheduler::once([]() { @@ -365,6 +440,7 @@ namespace updater } std::vector required_files; + std::vector update_files; const auto files = j.GetArray(); for (const auto& file : files) @@ -377,6 +453,8 @@ namespace updater const auto name = file[0].GetString(); const auto sha = file[2].GetString(); + update_files.push_back(name); + if (!check_file(name, sha)) { if (get_binary_name() == name) @@ -387,19 +465,29 @@ namespace updater }); } -#ifdef DEBUG - printf("[Updater] need file %s\n", name); -#endif + std::string name_ = name; + if (name_.ends_with(".ff")) + { + update_data.access([](update_data_t& data_) + { + data_.restart_required = true; + }); + } + + console::debug("[Updater] need file %s\n", name); required_files.push_back(name); } } - update_data.access([&required_files](update_data_t& data_) + const auto garbage_files = find_garbage_files(update_files); + + update_data.access([&](update_data_t& data_) { data_.check.done = true; data_.check.success = true; data_.required_files = required_files; + data_.garbage_files = garbage_files; }); notify("update_check_done"); @@ -408,15 +496,32 @@ namespace updater void start_update_download() { -#ifdef DEBUG - printf("[Updater] starting update download\n"); -#endif + console::debug("[Updater] starting update download\n"); if (!is_update_check_done() || !get_update_check_status() || is_update_cancelled()) { return; } + delete_old_data_files(); + + const auto garbage_files = update_data.access>([](update_data_t& data_) + { + return data_.garbage_files; + }); + + for (const auto& file : garbage_files) + { + try + { + std::filesystem::remove_all(file); + } + catch (...) + { + console::error("Failed to delete %s\n", file.data()); + } + } + scheduler::once([]() { const auto required_files = update_data.access>([](update_data_t& data_) @@ -433,9 +538,7 @@ namespace updater data_.current_file = file; }); -#ifdef DEBUG - printf("[Updater] downloading file %s\n", file.data()); -#endif + console::debug("[Updater] downloading file %s\n", file.data()); const auto data = download_file(file); diff --git a/src/client/component/updater.hpp b/src/client/component/updater.hpp index 9a3dd45e..0301a6c3 100644 --- a/src/client/component/updater.hpp +++ b/src/client/component/updater.hpp @@ -1,5 +1,7 @@ #pragma once +#define CLIENT_DATA_FOLDER "cdata" + namespace updater { void relaunch(); diff --git a/src/client/main.cpp b/src/client/main.cpp index f769f457..594f11bf 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -6,9 +6,10 @@ #include "component/arxan.hpp" -#include #include #include +#include +#include DECLSPEC_NORETURN void WINAPI exit_hook(const int code) { @@ -62,7 +63,7 @@ void apply_aslr_patch(std::string* data) void get_aslr_patched_binary(std::string* binary, std::string* data) { - const auto patched_binary = "h1-mod\\"s + *binary; + const auto patched_binary = (utils::properties::get_appdata_path() / "bin/h1_mp64_ship.exe"s).generic_string(); try { @@ -159,6 +160,7 @@ FARPROC load_binary(const launcher::mode mode, uint64_t* base_address) void remove_crash_file() { utils::io::remove_file("__h1Exe"); + utils::io::remove_file("h1-mod\\h1_mp64_ship.exe"); // remove this at some point } void enable_dpi_awareness() diff --git a/src/common/utils/io.cpp b/src/common/utils/io.cpp index 52083ac9..5d898cc9 100644 --- a/src/common/utils/io.cpp +++ b/src/common/utils/io.cpp @@ -121,6 +121,18 @@ namespace utils::io return files; } + std::vector list_files_recursively(const std::string& directory) + { + std::vector files; + + for (auto& file : std::filesystem::recursive_directory_iterator(directory)) + { + files.push_back(file.path().generic_string()); + } + + return files; + } + void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target) { std::filesystem::copy(src, target, diff --git a/src/common/utils/io.hpp b/src/common/utils/io.hpp index 38344987..19e8c143 100644 --- a/src/common/utils/io.hpp +++ b/src/common/utils/io.hpp @@ -18,5 +18,6 @@ namespace utils::io bool directory_is_empty(const std::string& directory); bool remove_directory(const std::string& directory); std::vector list_files(const std::string& directory); + std::vector list_files_recursively(const std::string& directory); void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target); } diff --git a/src/common/utils/properties.cpp b/src/common/utils/properties.cpp new file mode 100644 index 00000000..21926d0c --- /dev/null +++ b/src/common/utils/properties.cpp @@ -0,0 +1,24 @@ +#include "io.hpp" +#include "properties.hpp" +#include +#include + +namespace utils::properties +{ + std::filesystem::path get_appdata_path() + { + PWSTR path; + if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path))) + { + throw std::runtime_error("Failed to read APPDATA path!"); + } + + auto _ = gsl::finally([&path] + { + CoTaskMemFree(path); + }); + + static auto appdata = std::filesystem::path(path) / "h2-mod"; + return appdata; + } +} diff --git a/src/common/utils/properties.hpp b/src/common/utils/properties.hpp new file mode 100644 index 00000000..103e6ae6 --- /dev/null +++ b/src/common/utils/properties.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace utils::properties +{ + std::filesystem::path get_appdata_path(); +} From 22ca646021788f12d4df3da2faf0b3931881b702 Mon Sep 17 00:00:00 2001 From: m Date: Sat, 3 Sep 2022 06:03:27 -0500 Subject: [PATCH 07/12] appdata folder to h1 --- src/client/component/updater.cpp | 4 +--- src/common/utils/properties.cpp | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/client/component/updater.cpp b/src/client/component/updater.cpp index 460008a7..6d765dfd 100644 --- a/src/client/component/updater.cpp +++ b/src/client/component/updater.cpp @@ -263,9 +263,7 @@ namespace updater if (!found) { -#ifdef DEBUG - console::info("[Updater] Found extra file %s\n", file.data()); -#endif + console::debug("[Updater] Found extra file %s\n", file.data()); if (file.ends_with(".ff")) { update_data.access([](update_data_t& data_) diff --git a/src/common/utils/properties.cpp b/src/common/utils/properties.cpp index 21926d0c..23ea77c3 100644 --- a/src/common/utils/properties.cpp +++ b/src/common/utils/properties.cpp @@ -18,7 +18,7 @@ namespace utils::properties CoTaskMemFree(path); }); - static auto appdata = std::filesystem::path(path) / "h2-mod"; + static auto appdata = std::filesystem::path(path) / "h1-mod"; return appdata; } } From fbc5c9763fb3b1785a2ffff1992d7c8581f832ca Mon Sep 17 00:00:00 2001 From: Vlad Loktionov Date: Sat, 3 Sep 2022 18:21:02 +0300 Subject: [PATCH 08/12] use the binary variable --- src/client/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/main.cpp b/src/client/main.cpp index 594f11bf..d1f58eea 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -63,7 +63,7 @@ void apply_aslr_patch(std::string* data) void get_aslr_patched_binary(std::string* binary, std::string* data) { - const auto patched_binary = (utils::properties::get_appdata_path() / "bin/h1_mp64_ship.exe"s).generic_string(); + const auto patched_binary = (utils::properties::get_appdata_path() / "bin" / *binary).generic_string(); try { From 4c081b1fee3d213cddaf7e5ef10daa24b11fbf60 Mon Sep 17 00:00:00 2001 From: m Date: Sat, 3 Sep 2022 17:57:24 -0500 Subject: [PATCH 09/12] cleanup + fixes i think our sys_createfile hook needs fixed --- src/client/component/command.cpp | 29 ++-- src/client/component/fastfiles.cpp | 12 +- src/client/component/filesystem.cpp | 3 - src/client/component/localized_strings.cpp | 148 +-------------------- src/client/component/localized_strings.hpp | 3 +- src/client/component/mods.cpp | 6 +- src/client/component/ui_scripting.cpp | 2 +- 7 files changed, 31 insertions(+), 172 deletions(-) diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index e01ce5fd..32ac5e9c 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -417,6 +417,24 @@ namespace command ? "^2on" : "^1off")); } + + void monitor_fs_game_values() + { + dvars::callback::on_register("fs_game", []() + { + register_fs_game_path(); + }); + + // it might be overdone to change the filesystem path on every new value change, but to be fair, + // for the mods that don't need full restarts, this is good because it'll adjust and work like so + // in my opinion, this is fine. if a user tries to modify the dvar themselves, they'll have problems + // but i seriously doubt it'll be bad. + dvars::callback::on_new_value("fs_game", []() + { + console::warn("fs_game value changed, filesystem paths will be adjusted to new dvar value."); + register_fs_game_path(); + }); + } } void read_startup_variable(const std::string& dvar) @@ -585,16 +603,7 @@ namespace command void post_unpack() override { // monitor fs_game register and new value changes to adjust our paths for searching - dvars::callback::on_register("fs_game", []() - { - register_fs_game_path(); - }); - - dvars::callback::on_new_value("fs_game", []() - { - console::warn("fs_game value changed, filesystem paths will be adjusted to new dvar value."); - register_fs_game_path(); - }); + monitor_fs_game_values(); if (game::environment::is_sp()) { diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp index 96f4a06c..4a7aba99 100644 --- a/src/client/component/fastfiles.cpp +++ b/src/client/component/fastfiles.cpp @@ -148,9 +148,7 @@ namespace fastfiles if (localized) { const auto language = game::SEH_GetCurrentLanguageCode(); - try_load_zone(language + "_"s + name, false); - - if (language != "eng"s) + if (!try_load_zone(language + "_"s + name, false) && language != "eng"s) { try_load_zone("eng_" + name, false); } @@ -158,6 +156,7 @@ namespace fastfiles if (!fastfiles::exists(name)) { + console::debug("fastfile %s doesn't exist\n", name.data()); return false; } @@ -232,12 +231,10 @@ namespace fastfiles // ui // common - //try_load_zone("h1_mod_common", true); + try_load_zone("h1_mod_common", true); game::DB_LoadXAssets(data.data(), static_cast(data.size()), syncMode); - // H2-mod has the try_load_zone for mod right here but before, we had it before the load assets call. guess quaK can let me know - // what to do about this try_load_zone("mod", true); } @@ -261,6 +258,7 @@ namespace fastfiles CloseHandle(handle); return true; } + return false; } @@ -339,7 +337,7 @@ namespace fastfiles } const auto name = params.get(1); - if (!try_load_zone(name, false, true)) + if (!try_load_zone(name, false)) { console::warn("loadzone: zone \"%s\" could not be found!\n", name); } diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp index ad8fd4b4..954d511c 100644 --- a/src/client/component/filesystem.cpp +++ b/src/client/component/filesystem.cpp @@ -45,9 +45,6 @@ namespace filesystem filesystem::register_path(L"."); filesystem::register_path(L"h1-mod"); - // while this clears localizations, it also calls a function to load them again - localized_strings::clear(); - fs_startup_hook.invoke(name); } diff --git a/src/client/component/localized_strings.cpp b/src/client/component/localized_strings.cpp index 18c23aeb..231a765c 100644 --- a/src/client/component/localized_strings.cpp +++ b/src/client/component/localized_strings.cpp @@ -18,13 +18,7 @@ namespace localized_strings { utils::hook::detour seh_string_ed_get_string_hook; - struct localize_entry - { - std::string value{}; - bool volatile_{}; - }; - - using localized_map = std::unordered_map; + using localized_map = std::unordered_map; utils::concurrency::container localized_overrides; const char* seh_string_ed_get_string(const char* reference) @@ -34,155 +28,22 @@ namespace localized_strings const auto entry = map.find(reference); if (entry != map.end()) { - return utils::string::va("%s", entry->second.value.data()); + return utils::string::va("%s", entry->second.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, bool volatile_) + void override(const std::string& key, const std::string& value) { localized_overrides.access([&](localized_map& map) { - map[key] = {value, volatile_}; + map[key] = value; }); } - 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: @@ -190,7 +51,6 @@ 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 7a0d8f07..01d15907 100644 --- a/src/client/component/localized_strings.hpp +++ b/src/client/component/localized_strings.hpp @@ -2,6 +2,5 @@ namespace localized_strings { - void override(const std::string& key, const std::string& value, bool volatile_ = false); - void clear(); + void override(const std::string& key, const std::string& value); } \ No newline at end of file diff --git a/src/client/component/mods.cpp b/src/client/component/mods.cpp index a8dcb953..2d8d898b 100644 --- a/src/client/component/mods.cpp +++ b/src/client/component/mods.cpp @@ -32,8 +32,6 @@ namespace mods fonts::clear(); } - localized_strings::clear(); - db_release_xassets_hook.invoke(); } @@ -88,7 +86,7 @@ namespace mods if (!game::Com_InFrontend() && (game::environment::is_mp() && !game::VirtualLobby_Loaded())) { console::info("Cannot load mod while in-game!\n"); - game::CG_GameMessage(0, "^1Cannot unload mod while in-game!"); + game::CG_GameMessage(0, "^1Cannot load mod while in-game!"); return; } @@ -103,8 +101,6 @@ namespace mods 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 + "\""); } diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index 31172293..ac566a2e 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -219,7 +219,7 @@ namespace ui_scripting game_type["addlocalizedstring"] = [](const game&, const std::string& string, const std::string& value) { - localized_strings::override(string, value, true); + localized_strings::override(string, value); }; game_type["sharedset"] = [](const game&, const std::string& key, const std::string& value) From df2900e1b285e35ccdcc6e832952a8e9ed20add7 Mon Sep 17 00:00:00 2001 From: m Date: Tue, 6 Sep 2022 21:14:30 -0500 Subject: [PATCH 10/12] ye [skip ci] --- data/zone_source/build.txt | 2 ++ data/zone_source/h1_mod_common.csv | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 data/zone_source/build.txt diff --git a/data/zone_source/build.txt b/data/zone_source/build.txt new file mode 100644 index 00000000..4d33c56a --- /dev/null +++ b/data/zone_source/build.txt @@ -0,0 +1,2 @@ +eng_h1_mod_common +h1_mod_common \ No newline at end of file diff --git a/data/zone_source/h1_mod_common.csv b/data/zone_source/h1_mod_common.csv index 1a7b02f3..7bf70ec8 100644 --- a/data/zone_source/h1_mod_common.csv +++ b/data/zone_source/h1_mod_common.csv @@ -1 +1 @@ -build,eng_h1_mod_common \ No newline at end of file +localize,english \ No newline at end of file From e7972a270db803bdec1d2d6fd29d5afbd20de290 Mon Sep 17 00:00:00 2001 From: m Date: Fri, 9 Sep 2022 20:38:04 -0500 Subject: [PATCH 11/12] register fs game path on fs startup [skip ci] --- src/client/component/command.cpp | 73 ++++++++++++----------------- src/client/component/command.hpp | 2 + src/client/component/fastfiles.cpp | 4 +- src/client/component/filesystem.cpp | 3 ++ 4 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index 32ac5e9c..5b7c6d84 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -108,28 +108,6 @@ namespace command parsed = true; } - void register_fs_game_path() - { - static const auto* fs_game = game::Dvar_FindVar("fs_game"); - const auto new_mod_path = fs_game->current.string; - - // check if the last saved fs_game value isn't empty and if it doesn't equal the new fs_game - if (!saved_fs_game.empty() && saved_fs_game != new_mod_path) - { - // unregister path to be used as a fs directory - filesystem::unregister_path(saved_fs_game); - } - - if (new_mod_path && !new_mod_path[0]) - { - return; - } - - // register fs_game value as a fs directory used for many things - filesystem::register_path(new_mod_path); - saved_fs_game = new_mod_path; - } - void parse_startup_variables() { auto& com_num_console_lines = *reinterpret_cast(0x35634B8_b); @@ -417,24 +395,6 @@ namespace command ? "^2on" : "^1off")); } - - void monitor_fs_game_values() - { - dvars::callback::on_register("fs_game", []() - { - register_fs_game_path(); - }); - - // it might be overdone to change the filesystem path on every new value change, but to be fair, - // for the mods that don't need full restarts, this is good because it'll adjust and work like so - // in my opinion, this is fine. if a user tries to modify the dvar themselves, they'll have problems - // but i seriously doubt it'll be bad. - dvars::callback::on_new_value("fs_game", []() - { - console::warn("fs_game value changed, filesystem paths will be adjusted to new dvar value."); - register_fs_game_path(); - }); - } } void read_startup_variable(const std::string& dvar) @@ -597,13 +557,42 @@ namespace command } } + void register_fs_game_path() + { + const auto* fs_game = game::Dvar_FindVar("fs_game"); + const auto new_mod_path = fs_game->current.string; + + // check if the last saved fs_game value isn't empty and if it doesn't equal the new fs_game + if (!saved_fs_game.empty() && saved_fs_game != new_mod_path) + { + // unregister path to be used as a fs directory + filesystem::unregister_path(saved_fs_game); + } + + if (new_mod_path && !new_mod_path[0]) + { + return; + } + + // register fs_game value as a fs directory used for many things + filesystem::register_path(new_mod_path); + saved_fs_game = new_mod_path; + } + class component final : public component_interface { public: void post_unpack() override { - // monitor fs_game register and new value changes to adjust our paths for searching - monitor_fs_game_values(); + // it might be overdone to change the filesystem path on every new value change, but to be fair, + // for the mods that don't need full restarts, this is good because it'll adjust and work like so + // in my opinion, this is fine. if a user tries to modify the dvar themselves, they'll have problems + // but i seriously doubt it'll be bad. + dvars::callback::on_new_value("fs_game", []() + { + console::warn("fs_game value changed, filesystem paths will be adjusted to new dvar value."); + register_fs_game_path(); + }); if (game::environment::is_sp()) { diff --git a/src/client/component/command.hpp b/src/client/component/command.hpp index 06228c9d..a891b802 100644 --- a/src/client/component/command.hpp +++ b/src/client/component/command.hpp @@ -49,4 +49,6 @@ namespace command void add_sv(const char* name, std::function callback); void execute(std::string command, bool sync = false); + + void register_fs_game_path(); } \ No newline at end of file diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp index 4a7aba99..8ad93908 100644 --- a/src/client/component/fastfiles.cpp +++ b/src/client/component/fastfiles.cpp @@ -171,8 +171,8 @@ namespace fastfiles utils::hook::detour sys_createfile_hook; HANDLE sys_create_file_stub(game::Sys_Folder folder, const char* base_filename) { - static const auto* fs_basepath = game::Dvar_FindVar("fs_basepath"); - static const auto* fs_game = game::Dvar_FindVar("fs_game"); + const auto* fs_basepath = game::Dvar_FindVar("fs_basepath"); + const auto* fs_game = game::Dvar_FindVar("fs_game"); std::string dir = fs_basepath ? fs_basepath->current.string : ""; std::string mod_dir = fs_game ? fs_game->current.string : ""; diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp index 954d511c..1d4868c6 100644 --- a/src/client/component/filesystem.cpp +++ b/src/client/component/filesystem.cpp @@ -1,6 +1,7 @@ #include #include "loader/component_loader.hpp" +#include "command.hpp" #include "console.hpp" #include "filesystem.hpp" #include "localized_strings.hpp" @@ -46,6 +47,8 @@ namespace filesystem filesystem::register_path(L"h1-mod"); fs_startup_hook.invoke(name); + + command::register_fs_game_path(); } std::vector get_paths(const std::filesystem::path& path) From b36a26b81bb5dbc29f12e621ed896e723f5c8cdb Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Sat, 10 Sep 2022 22:59:55 +0200 Subject: [PATCH 12/12] Support loading fastfiles from appdata --- src/client/component/fastfiles.cpp | 69 ++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp index 8ad93908..a5ae2c0b 100644 --- a/src/client/component/fastfiles.cpp +++ b/src/client/component/fastfiles.cpp @@ -6,6 +6,7 @@ #include "fastfiles.hpp" #include "command.hpp" #include "console.hpp" +#include "filesystem.hpp" #include #include @@ -148,15 +149,11 @@ namespace fastfiles if (localized) { const auto language = game::SEH_GetCurrentLanguageCode(); - if (!try_load_zone(language + "_"s + name, false) && language != "eng"s) - { - try_load_zone("eng_" + name, false); - } + try_load_zone(language + "_"s + name, false); } if (!fastfiles::exists(name)) { - console::debug("fastfile %s doesn't exist\n", name.data()); return false; } @@ -168,32 +165,73 @@ namespace fastfiles return true; } + HANDLE find_fastfile(const std::string& filename, bool check_loc_folder) + { + std::string path{}; + std::string loc_folder{}; + + if (check_loc_folder && game::DB_IsLocalized(filename.data())) + { + const auto handle = find_fastfile(filename, false); + if (handle != reinterpret_cast(-1)) + { + return handle; + } + + loc_folder = game::SEH_GetCurrentLanguageName() + "/"s; + } + + if (!filesystem::find_file(loc_folder + filename, &path)) + { + if (!filesystem::find_file("zone/"s + loc_folder + filename, &path)) + { + return reinterpret_cast(-1); + } + } + + return CreateFileA(path.data(), 0x80000000, 1u, 0, 3u, 0x60000000u, 0); + } + utils::hook::detour sys_createfile_hook; HANDLE sys_create_file_stub(game::Sys_Folder folder, const char* base_filename) { const auto* fs_basepath = game::Dvar_FindVar("fs_basepath"); const auto* fs_game = game::Dvar_FindVar("fs_game"); - std::string dir = fs_basepath ? fs_basepath->current.string : ""; - std::string mod_dir = fs_game ? fs_game->current.string : ""; + const std::string dir = fs_basepath ? fs_basepath->current.string : ""; + const std::string mod_dir = fs_game ? fs_game->current.string : ""; + const std::string name = base_filename; - if (base_filename == "mod.ff"s) + if (name == "mod.ff") { if (!mod_dir.empty()) { - auto path = utils::string::va("%s\\%s\\%s", dir.data(), mod_dir.data(), base_filename); + const auto path = utils::string::va("%s\\%s\\%s", + dir.data(), mod_dir.data(), base_filename); + if (utils::io::file_exists(path)) { return CreateFileA(path, 0x80000000, 1u, 0, 3u, 0x60000000u, 0); } } - return (HANDLE)-1; + + return reinterpret_cast(-1); + } + + if (name.ends_with(".ff")) + { + const auto handle = find_fastfile(name, true); + if (handle != reinterpret_cast(-1)) + { + return handle; + } } return sys_createfile_hook.invoke(folder, base_filename); } - template inline void merge(std::vector* target, T* source, size_t length) + template + inline void merge(std::vector* target, T* source, size_t length) { if (source) { @@ -204,7 +242,8 @@ namespace fastfiles } } - template inline void merge(std::vector* target, std::vector source) + template + inline void merge(std::vector* target, std::vector source) { for (auto& entry : source) { @@ -252,8 +291,10 @@ 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 != (HANDLE)-1) + const auto handle = game::Sys_CreateFile((is_localized ? game::SF_ZONE_LOC : game::SF_ZONE), + utils::string::va("%s.ff", zone.data())); + + if (handle != reinterpret_cast(-1)) { CloseHandle(handle); return true;