#include #include "loader/component_loader.hpp" #include "filesystem.hpp" #include "console.hpp" #include "mods.hpp" #include "language.hpp" #include "game/game.hpp" #include #include #include #include namespace filesystem { namespace { bool initialized = false; std::deque& get_search_paths_internal() { static std::deque search_paths{}; return search_paths; } void fs_startup_stub(const char* name) { console::info("[FS] Startup\n"); initialized = true; filesystem::register_path(utils::properties::get_appdata_path() / CLIENT_DATA_FOLDER); filesystem::register_path(L"."); filesystem::register_path(L"h2-mod"); const auto mod_path = utils::flags::get_flag("mod"); if (mod_path.has_value()) { mods::set_mod(mod_path.value()); } utils::hook::invoke(0x14060BF50, name); } std::vector get_paths(const std::filesystem::path& path) { std::vector paths{}; const auto code = game::SEH_GetCurrentLanguageName(); paths.push_back(path); paths.push_back(path / code); return paths; } bool can_insert_path(const std::filesystem::path& path) { const auto& paths = get_search_paths_internal(); return std::ranges::none_of(paths.cbegin(), paths.cend(), [path](const auto& elem) { return elem == path; }); } const char* sys_default_install_path_stub() { static auto current_path = std::filesystem::current_path().string(); return current_path.data(); } bool is_parent_path(const std::filesystem::path& parent, const std::filesystem::path& child) { std::filesystem::path iter = child; while (iter != iter.parent_path()) { if (iter == parent) { return true; } iter = iter.parent_path(); } return false; } } std::string read_file(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 utils::io::read_file(path_.generic_string()); } } return {}; } bool read_file(const std::string& path, std::string* data, std::string* real_path) { for (const auto& search_path : get_search_paths_internal()) { 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; } } 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; } void check_path(const std::filesystem::path& path) { if (path.generic_string().find("..") != std::string::npos) { throw std::runtime_error("directory traversal is not allowed"); } } std::string get_safe_path(const std::filesystem::path& path) { check_path(path); const auto absolute = std::filesystem::weakly_canonical(path); static std::vector allowed_directories = { {std::filesystem::weakly_canonical("mods")}, {std::filesystem::weakly_canonical("h2-mod")}, {std::filesystem::weakly_canonical("players2/default")}, }; auto is_allowed = false; for (const auto& dir : allowed_directories) { if (is_parent_path(dir, absolute)) { is_allowed = true; break; } } if (!is_allowed) { throw std::runtime_error(std::format("Disallowed access to directory \"{}\"", path.generic_string())); } return path.generic_string(); } bool safe_write_file(const std::string& file, const std::string& data, bool append) { const auto path = filesystem::get_safe_path(file); return utils::io::write_file(path, data, append); } class component final : public component_interface { public: void post_unpack() override { utils::hook::call(0x14060B052, fs_startup_stub); utils::hook::jump(0x140624050, sys_default_install_path_stub); } }; } REGISTER_COMPONENT(filesystem::component)