Added Loadmod/UnloadMod commands and Lua functions directoryexists, listfiles, directoryisempty, and fileexists for mod loading menu. Cleaned up mods.cpp and fastfile.cpp. Added Load_LevelZone to reload mods after maps have been loaded to allow overriding map gsc's and other assets. Added Com_Shutdown to Symbols.hpp.
This commit is contained in:
parent
199206ee5a
commit
e243afacb6
@ -10,6 +10,7 @@
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/memory.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace fastfiles
|
||||
@ -19,6 +20,25 @@ namespace fastfiles
|
||||
utils::hook::detour db_try_load_x_file_internal_hook;
|
||||
utils::hook::detour db_find_x_asset_header_hook;
|
||||
|
||||
template <typename T> inline void merge(std::vector<T>* target, T* source, size_t length)
|
||||
{
|
||||
if (source)
|
||||
{
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
{
|
||||
target->push_back(source[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> inline void merge(std::vector<T>* target, std::vector<T> source)
|
||||
{
|
||||
for (auto& entry : source)
|
||||
{
|
||||
target->push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
void db_try_load_x_file_internal(const char* zone_name, const int zone_flags, const int is_base_map)
|
||||
{
|
||||
console::info("Loading fastfile %s\n", zone_name);
|
||||
@ -90,6 +110,42 @@ namespace fastfiles
|
||||
console::info("Unloaded fastfile %s\n", name);
|
||||
game::PMem_Free(name, alloc_dir);
|
||||
}
|
||||
|
||||
const auto skip_extra_zones_stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
const auto skip = a.newLabel();
|
||||
const auto original = a.newLabel();
|
||||
|
||||
a.pushad64();
|
||||
a.test(ebp, game::DB_ZONE_CUSTOM); // allocFlags
|
||||
a.jnz(skip);
|
||||
|
||||
a.bind(original);
|
||||
a.popad64();
|
||||
a.mov(rdx, 0x140835F28);
|
||||
a.mov(rcx, rsi);
|
||||
a.call_aligned(strcmp);
|
||||
a.jmp(0x1403217C0);
|
||||
|
||||
a.bind(skip);
|
||||
a.popad64();
|
||||
a.mov(r15d, 0x80);
|
||||
a.not_(r15d);
|
||||
a.and_(ebp, r15d);
|
||||
a.jmp(0x1403217F6);
|
||||
});
|
||||
}
|
||||
|
||||
bool exists(const std::string& zone_name)
|
||||
{
|
||||
auto is_localized = game::DB_IsLocalized(zone_name.data());
|
||||
auto handle = game::Sys_CreateFile(game::Sys_Folder(is_localized), utils::string::va("%s.ff", zone_name.data()));
|
||||
if (handle != (HANDLE)-1)
|
||||
{
|
||||
CloseHandle(handle);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool include_override)
|
||||
@ -101,6 +157,57 @@ namespace fastfiles
|
||||
}), &callback, include_override);
|
||||
}
|
||||
|
||||
void Load_CommonZones(game::XZoneInfo* zoneInfo, unsigned int zoneCount, game::DBSyncMode syncMode)
|
||||
{
|
||||
std::vector<game::XZoneInfo> data;
|
||||
merge(&data, zoneInfo, zoneCount);
|
||||
|
||||
if (fastfiles::exists("mod"))
|
||||
{
|
||||
data.push_back({ "mod", game::DB_ZONE_COMMON | game::DB_ZONE_CUSTOM, 0 });
|
||||
}
|
||||
|
||||
game::DB_LoadXAssets(data.data(), static_cast<std::uint32_t>(data.size()), syncMode);
|
||||
}
|
||||
|
||||
void Load_LevelZones(game::XZoneInfo* zoneInfo, unsigned int zoneCount, game::DBSyncMode syncMode)
|
||||
{
|
||||
std::vector<game::XZoneInfo> data;
|
||||
merge(&data, zoneInfo, zoneCount);
|
||||
|
||||
if (fastfiles::exists("mod"))
|
||||
{
|
||||
data.push_back({ "mod", game::DB_ZONE_COMMON | game::DB_ZONE_CUSTOM, 0 });
|
||||
}
|
||||
|
||||
game::DB_LoadXAssets(data.data(), static_cast<std::uint32_t>(data.size()), syncMode);
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
std::string dir = fs_basepath ? fs_basepath->current.string : "";
|
||||
std::string mod_dir = fs_game ? fs_game->current.string : "";
|
||||
|
||||
if (base_filename == "mod.ff"s)
|
||||
{
|
||||
if (!mod_dir.empty())
|
||||
{
|
||||
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 sys_createfile_hook.invoke<HANDLE>(folder, base_filename);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
@ -118,6 +225,19 @@ namespace fastfiles
|
||||
utils::hook::set<uint8_t>(0x1402FBF23, 0xEB); // DB_LoadXFile
|
||||
utils::hook::nop(0x1402FC445, 2); // DB_SetFileLoadCompressor
|
||||
|
||||
// Don't load eng_ + patch_ with loadzone
|
||||
utils::hook::nop(0x1403217B1, 15);
|
||||
utils::hook::jump(0x1403217B1, skip_extra_zones_stub, true);
|
||||
|
||||
// Add custom zone paths
|
||||
sys_createfile_hook.create(game::Sys_CreateFile, sys_create_file_stub);
|
||||
|
||||
// Load our custom fastfiles (Mod)
|
||||
utils::hook::call(0x1405E7113, Load_CommonZones);
|
||||
|
||||
// Reload mod after level is loaded to allow overriding map stuff
|
||||
utils::hook::call(0x140320ED1, Load_LevelZones);
|
||||
|
||||
command::add("materiallist", [](const command::params& params)
|
||||
{
|
||||
game::DB_EnumXAssets_FastFile(game::ASSET_TYPE_MATERIAL, [](const game::XAssetHeader header, void*)
|
||||
|
@ -3,16 +3,27 @@
|
||||
#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]{};
|
||||
@ -38,66 +49,74 @@ namespace mods
|
||||
}
|
||||
}
|
||||
|
||||
game::Sys_File sys_create_file_stub(const char* dir, const char* filename)
|
||||
void restart()
|
||||
{
|
||||
auto result = sys_create_file_hook.invoke<game::Sys_File>(dir, filename);
|
||||
|
||||
if (result.handle != INVALID_HANDLE_VALUE)
|
||||
scheduler::once([]()
|
||||
{
|
||||
return result;
|
||||
}
|
||||
release_assets = true;
|
||||
const auto _0 = gsl::finally([]()
|
||||
{
|
||||
release_assets = false;
|
||||
});
|
||||
|
||||
if (!is_using_mods())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// .ff extension was added previously
|
||||
if (!std::strcmp(filename, "mod.ff") && mods::db_mod_file_exists())
|
||||
{
|
||||
char file_path[MAX_PATH]{};
|
||||
db_build_os_path_from_source("mod", game::FFD_MOD_DIR, sizeof(file_path), file_path);
|
||||
result.handle = game::Sys_OpenFileReliable(file_path);
|
||||
}
|
||||
|
||||
return result;
|
||||
game::Com_Shutdown("");
|
||||
}, scheduler::pipeline::main);
|
||||
}
|
||||
|
||||
void db_load_x_assets_stub(game::XZoneInfo* zone_info, unsigned int zone_count, game::DBSyncMode sync_mode)
|
||||
void full_restart(const std::string& arg)
|
||||
{
|
||||
std::vector<game::XZoneInfo> zones(zone_info, zone_info + zone_count);
|
||||
|
||||
if (db_mod_file_exists())
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
zones.emplace_back("mod", game::DB_ZONE_COMMON | game::DB_ZONE_CUSTOM, 0);
|
||||
command::execute("vid_restart");
|
||||
scheduler::once([]
|
||||
{
|
||||
//mods::read_stats();
|
||||
}, scheduler::main);
|
||||
return;
|
||||
}
|
||||
|
||||
game::DB_LoadXAssets(zones.data(), static_cast<unsigned int>(zones.size()), sync_mode);
|
||||
auto mode = game::environment::is_mp() ? " -multiplayer "s : " -singleplayer "s;
|
||||
|
||||
utils::nt::relaunch_self();
|
||||
utils::nt::terminate();
|
||||
}
|
||||
|
||||
const auto skip_extra_zones_stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
bool mod_requires_restart(const std::string& path)
|
||||
{
|
||||
const auto skip = a.newLabel();
|
||||
const auto original = a.newLabel();
|
||||
return utils::io::file_exists(path + "/mod.ff") || utils::io::file_exists(path + "/zone/mod.ff");
|
||||
}
|
||||
|
||||
a.pushad64();
|
||||
a.test(ebp, game::DB_ZONE_CUSTOM); // allocFlags
|
||||
a.jnz(skip);
|
||||
void set_filesystem_data(const std::string& path, bool change_fs_game)
|
||||
{
|
||||
if (mod_path.has_value())
|
||||
{
|
||||
filesystem::unregister_path(mod_path.value());
|
||||
}
|
||||
|
||||
a.bind(original);
|
||||
a.popad64();
|
||||
a.mov(rdx, 0x140835F28);
|
||||
a.mov(rcx, rsi);
|
||||
a.call_aligned(strcmp);
|
||||
a.jmp(0x1403217C0);
|
||||
if (change_fs_game)
|
||||
{
|
||||
game::Dvar_SetFromStringByNameFromSource("fs_game", path.data(), game::DVAR_SOURCE_INTERNAL);
|
||||
}
|
||||
|
||||
a.bind(skip);
|
||||
a.popad64();
|
||||
a.mov(r15d, 0x80);
|
||||
a.not_(r15d);
|
||||
a.and_(ebp, r15d);
|
||||
a.jmp(0x1403217F6);
|
||||
});
|
||||
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()
|
||||
@ -141,15 +160,72 @@ namespace mods
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't load eng_ + patch_ with loadzone
|
||||
utils::hook::nop(0x1403217B1, 15);
|
||||
utils::hook::jump(0x1403217B1, skip_extra_zones_stub, true);
|
||||
command::add("loadmod", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("Usage: loadmod mods/<modname>");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add custom zone paths
|
||||
sys_create_file_hook.create(game::Sys_CreateFile, sys_create_file_stub);
|
||||
/*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;
|
||||
}*/
|
||||
|
||||
// Load mod.ff
|
||||
utils::hook::call(0x1405E7113, db_load_x_assets_stub); // R_LoadGraphicsAssets According to myself but I don't remember where I got it from
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -195,6 +195,27 @@ namespace ui_scripting
|
||||
setup_functions();
|
||||
|
||||
lua["print"] = function(reinterpret_cast<game::hks::lua_function>(0x14017B120)); // hks::base_print
|
||||
|
||||
lua["directoryexists"] = [](const std::string& string)
|
||||
{
|
||||
return utils::io::directory_exists(string);
|
||||
};
|
||||
|
||||
lua["listfiles"] = [](const std::string& string)
|
||||
{
|
||||
return utils::io::list_files(string);
|
||||
};
|
||||
|
||||
lua["directoryisempty"] = [](const std::string& string)
|
||||
{
|
||||
return utils::io::directory_is_empty(string);
|
||||
};
|
||||
|
||||
lua["fileexists"] = [](const std::string& string)
|
||||
{
|
||||
return utils::io::file_exists(string);
|
||||
};
|
||||
|
||||
lua["table"]["unpack"] = lua["unpack"];
|
||||
lua["luiglobals"] = lua;
|
||||
|
||||
|
@ -8,6 +8,8 @@ namespace game
|
||||
* Functions
|
||||
**************************************************************/
|
||||
|
||||
WEAK symbol<void(char const* finalMessage)> Com_Shutdown{ 0x0, 0x140415B30 };
|
||||
|
||||
WEAK symbol<void(unsigned int id)> AddRefToObject{0x1403D7A10, 0x1404326D0};
|
||||
WEAK symbol<void(int type, VariableUnion u)> AddRefToValue{0x1403D7740, 0x1404326E0};
|
||||
WEAK symbol<unsigned int(unsigned int id)> AllocThread{0, 0x1404329B0};
|
||||
|
Loading…
x
Reference in New Issue
Block a user