234 lines
5.1 KiB
C++
234 lines
5.1 KiB
C++
#include <std_include.hpp>
|
|
#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 <utils/hook.hpp>
|
|
#include <utils/io.hpp>
|
|
|
|
namespace mods
|
|
{
|
|
|
|
std::optional<std::string> 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<game::dvar_t**>(SELECT_VALUE(0x145856D38, 0x147876000));
|
|
|
|
// Remove DVAR_INIT from fs_game
|
|
utils::hook::set<std::uint32_t>(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/<modname>");
|
|
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)
|