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/hook.hpp>
|
||||||
#include <utils/memory.hpp>
|
#include <utils/memory.hpp>
|
||||||
|
#include <utils/string.hpp>
|
||||||
#include <utils/io.hpp>
|
#include <utils/io.hpp>
|
||||||
|
|
||||||
namespace fastfiles
|
namespace fastfiles
|
||||||
@ -19,6 +20,25 @@ namespace fastfiles
|
|||||||
utils::hook::detour db_try_load_x_file_internal_hook;
|
utils::hook::detour db_try_load_x_file_internal_hook;
|
||||||
utils::hook::detour db_find_x_asset_header_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)
|
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);
|
console::info("Loading fastfile %s\n", zone_name);
|
||||||
@ -90,6 +110,42 @@ namespace fastfiles
|
|||||||
console::info("Unloaded fastfile %s\n", name);
|
console::info("Unloaded fastfile %s\n", name);
|
||||||
game::PMem_Free(name, alloc_dir);
|
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)
|
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);
|
}), &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
|
class component final : public component_interface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -118,6 +225,19 @@ namespace fastfiles
|
|||||||
utils::hook::set<uint8_t>(0x1402FBF23, 0xEB); // DB_LoadXFile
|
utils::hook::set<uint8_t>(0x1402FBF23, 0xEB); // DB_LoadXFile
|
||||||
utils::hook::nop(0x1402FC445, 2); // DB_SetFileLoadCompressor
|
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)
|
command::add("materiallist", [](const command::params& params)
|
||||||
{
|
{
|
||||||
game::DB_EnumXAssets_FastFile(game::ASSET_TYPE_MATERIAL, [](const game::XAssetHeader header, void*)
|
game::DB_EnumXAssets_FastFile(game::ASSET_TYPE_MATERIAL, [](const game::XAssetHeader header, void*)
|
||||||
|
@ -3,16 +3,27 @@
|
|||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
#include "game/dvars.hpp"
|
#include "game/dvars.hpp"
|
||||||
|
|
||||||
|
#include "command.hpp"
|
||||||
|
#include "console.hpp"
|
||||||
|
#include "filesystem.hpp"
|
||||||
|
#include "scheduler.hpp"
|
||||||
|
|
||||||
#include "mods.hpp"
|
#include "mods.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
|
#include <utils/io.hpp>
|
||||||
|
|
||||||
namespace mods
|
namespace mods
|
||||||
{
|
{
|
||||||
|
|
||||||
|
std::optional<std::string> mod_path;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
utils::hook::detour sys_create_file_hook;
|
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)
|
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]{};
|
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);
|
scheduler::once([]()
|
||||||
|
|
||||||
if (result.handle != INVALID_HANDLE_VALUE)
|
|
||||||
{
|
{
|
||||||
return result;
|
release_assets = true;
|
||||||
}
|
const auto _0 = gsl::finally([]()
|
||||||
|
|
||||||
if (!is_using_mods())
|
|
||||||
{
|
{
|
||||||
return result;
|
release_assets = false;
|
||||||
}
|
|
||||||
|
|
||||||
// .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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void db_load_x_assets_stub(game::XZoneInfo* zone_info, unsigned int zone_count, game::DBSyncMode sync_mode)
|
|
||||||
{
|
|
||||||
std::vector<game::XZoneInfo> zones(zone_info, zone_info + zone_count);
|
|
||||||
|
|
||||||
if (db_mod_file_exists())
|
|
||||||
{
|
|
||||||
zones.emplace_back("mod", game::DB_ZONE_COMMON | game::DB_ZONE_CUSTOM, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
game::DB_LoadXAssets(zones.data(), static_cast<unsigned int>(zones.size()), sync_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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()
|
bool is_using_mods()
|
||||||
@ -141,15 +160,72 @@ namespace mods
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't load eng_ + patch_ with loadzone
|
command::add("loadmod", [](const command::params& params)
|
||||||
utils::hook::nop(0x1403217B1, 15);
|
{
|
||||||
utils::hook::jump(0x1403217B1, skip_extra_zones_stub, true);
|
if (params.size() < 2)
|
||||||
|
{
|
||||||
|
console::info("Usage: loadmod mods/<modname>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Add custom zone paths
|
/*if (!game::Com_InFrontend() && (game::environment::is_mp() && !game::VirtualLobby_Loaded()))
|
||||||
sys_create_file_hook.create(game::Sys_CreateFile, sys_create_file_stub);
|
{
|
||||||
|
console::info("Cannot load mod while in-game!\n");
|
||||||
|
game::CG_GameMessage(0, "^1Cannot load mod while in-game!");
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
|
|
||||||
// Load mod.ff
|
const auto path = params.get(1);
|
||||||
utils::hook::call(0x1405E7113, db_load_x_assets_stub); // R_LoadGraphicsAssets According to myself but I don't remember where I got it from
|
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();
|
setup_functions();
|
||||||
|
|
||||||
lua["print"] = function(reinterpret_cast<game::hks::lua_function>(0x14017B120)); // hks::base_print
|
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["table"]["unpack"] = lua["unpack"];
|
||||||
lua["luiglobals"] = lua;
|
lua["luiglobals"] = lua;
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ namespace game
|
|||||||
* Functions
|
* Functions
|
||||||
**************************************************************/
|
**************************************************************/
|
||||||
|
|
||||||
|
WEAK symbol<void(char const* finalMessage)> Com_Shutdown{ 0x0, 0x140415B30 };
|
||||||
|
|
||||||
WEAK symbol<void(unsigned int id)> AddRefToObject{0x1403D7A10, 0x1404326D0};
|
WEAK symbol<void(unsigned int id)> AddRefToObject{0x1403D7A10, 0x1404326D0};
|
||||||
WEAK symbol<void(int type, VariableUnion u)> AddRefToValue{0x1403D7740, 0x1404326E0};
|
WEAK symbol<void(int type, VariableUnion u)> AddRefToValue{0x1403D7740, 0x1404326E0};
|
||||||
WEAK symbol<unsigned int(unsigned int id)> AllocThread{0, 0x1404329B0};
|
WEAK symbol<unsigned int(unsigned int id)> AllocThread{0, 0x1404329B0};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user