#include #include "loader/component_loader.hpp" #include "game/game.hpp" #include "game/dvars.hpp" #include "command.hpp" #include "console.hpp" #include "filesystem.hpp" #include "scheduler.hpp" #include "mods.hpp" #include #include namespace mods { std::optional mod_path; namespace { utils::hook::detour sys_create_file_hook; bool release_assets = false; void db_build_os_path_from_source(const char* zone_name, game::FF_DIR source, unsigned int size, char* filename) { char user_map[MAX_PATH]{}; switch (source) { case game::FFD_DEFAULT: (void)sprintf_s(filename, size, "%s\\%s.ff", std::filesystem::current_path().string().c_str(), zone_name); break; case game::FFD_MOD_DIR: assert(mods::is_using_mods()); (void)sprintf_s(filename, size, "%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), (*dvars::fs_gameDirVar)->current.string, zone_name); break; case game::FFD_USER_MAP: strncpy_s(user_map, zone_name, _TRUNCATE); (void)sprintf_s(filename, size, "%s\\%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), "usermaps", user_map, zone_name); break; default: assert(false && "inconceivable"); break; } } void restart() { scheduler::once([]() { release_assets = true; const auto _0 = gsl::finally([]() { release_assets = false; }); game::Com_Shutdown(""); }, scheduler::pipeline::main); } void full_restart(const std::string& arg) { if (game::environment::is_mp()) { command::execute("vid_restart"); scheduler::once([] { //mods::read_stats(); }, scheduler::main); return; } auto mode = game::environment::is_mp() ? " -multiplayer "s : " -singleplayer "s; utils::nt::relaunch_self(); 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"); } void set_filesystem_data(const std::string& path, bool change_fs_game) { if (mod_path.has_value()) { filesystem::unregister_path(mod_path.value()); } if (change_fs_game) { game::Dvar_SetFromStringByNameFromSource("fs_game", path.data(), game::DVAR_SOURCE_INTERNAL); } if (path != "") { filesystem::register_path(path); } } } void set_mod(const std::string& path, bool change_fs_game) { set_filesystem_data(path, change_fs_game); if (path != "") { mod_path = path; } else { mod_path.reset(); } } bool is_using_mods() { return (*dvars::fs_gameDirVar) && *(*dvars::fs_gameDirVar)->current.string; } bool db_mod_file_exists() { if (!*(*dvars::fs_gameDirVar)->current.string) { return false; } char filename[MAX_PATH]{}; db_build_os_path_from_source("mod", game::FFD_MOD_DIR, sizeof(filename), filename); if (auto zone_file = game::Sys_OpenFileReliable(filename); zone_file != INVALID_HANDLE_VALUE) { ::CloseHandle(zone_file); return true; } return false; } class component final : public component_interface { public: static_assert(sizeof(game::Sys_File) == 8); void post_unpack() override { dvars::fs_gameDirVar = reinterpret_cast(SELECT_VALUE(0x145856D38, 0x147876000)); // Remove DVAR_INIT from fs_game utils::hook::set(SELECT_VALUE(0x14041C085 + 2, 0x1404DDA45 + 2), SELECT_VALUE(game::DVAR_FLAG_NONE, game::DVAR_FLAG_SERVERINFO)); if (game::environment::is_sp()) { return; } command::add("loadmod", [](const command::params& params) { if (params.size() < 2) { console::info("Usage: loadmod mods/"); return; } /*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 load mod while in-game!"); return; }*/ const auto path = params.get(1); if (!utils::io::directory_exists(path)) { console::info("Mod %s not found!\n", path); return; } console::info("Loading mod %s\n", path); set_mod(path, true); if ((mod_path.has_value() && mod_requires_restart(mod_path.value())) || mod_requires_restart(path)) { console::info("Restarting...\n"); full_restart("-mod \""s + path + "\""); } else { restart(); } }); command::add("unloadmod", [](const command::params& params) { if (!mod_path.has_value()) { console::info("No mod loaded\n"); return; } /*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!"); return; }*/ console::info("Unloading mod %s\n", mod_path.value().data()); if (mod_requires_restart(mod_path.value())) { console::info("Restarting...\n"); set_mod("", true); full_restart(""); } else { set_mod("", true); restart(); } }); } }; } REGISTER_COMPONENT(mods::component)