Modding Improvements #18

Closed
HighTechRedNeck wants to merge 2 commits from (deleted):master into master
8 changed files with 350 additions and 55 deletions

View File

@ -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*)

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "game/game.hpp"
namespace fastfiles namespace fastfiles
{ {
void enum_assets(game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, bool include_override); void enum_assets(game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, bool include_override);

View File

@ -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();
}
});
} }
}; };
} }

View File

@ -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;

View File

@ -0,0 +1,58 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "fastfiles.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
namespace weapon
{
namespace
{
utils::hook::detour g_setup_level_weapon_def_hook;
void g_setup_level_weapon_def_stub()
{
// precache level weapons first
g_setup_level_weapon_def_hook.invoke<void>();
std::vector<game::WeaponCompleteDef*> weapons;
// find all weapons in asset pools
fastfiles::enum_assets(game::ASSET_TYPE_WEAPON, [&weapons](game::XAssetHeader header)
{
weapons.push_back(header.weapon);
}, false);
// sort weapons
std::sort(weapons.begin(), weapons.end(), [](game::WeaponCompleteDef* weapon1, game::WeaponCompleteDef* weapon2)
{
return std::string_view(weapon1->name) <
std::string_view(weapon2->name);
});
// precache items
for (std::size_t i = 0; i < weapons.size(); i++)
{
//console::debug("precaching weapon \"%s\"\n", weapons[i]->name);
game::G_GetWeaponForName(weapons[i]->name);
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_sp())
{
// precache all weapons that are loaded in zones
g_setup_level_weapon_def_hook.create(0x1403DA910, g_setup_level_weapon_def_stub);
}
}
};
}
REGISTER_COMPONENT(weapon::component)

View File

@ -0,0 +1,6 @@
#pragma once
namespace weapon
{
}

View File

@ -1955,6 +1955,16 @@ namespace game
weapInventoryType_t inventoryType; weapInventoryType_t inventoryType;
}; // Incomplete }; // Incomplete
struct WeaponCompleteDef
{
union
{
const char* szInternalName;
const char* name;
};
WeaponDef* weapDef;
};
struct RawFile struct RawFile
{ {
const char* name; const char* name;
@ -1998,9 +2008,9 @@ namespace game
menuDef_t *menu; menuDef_t *menu;
AnimationClass *animClass; AnimationClass *animClass;
LocalizeEntry *localize; LocalizeEntry *localize;
WeaponAttachment *attachment; WeaponAttachment *attachment;*/
WeaponCompleteDef* weapon; WeaponCompleteDef* weapon;
SndDriverGlobals *sndDriverGlobals; /*SndDriverGlobals* sndDriverGlobals;
FxEffectDef *fx; FxEffectDef *fx;
FxImpactTable *impactFx; FxImpactTable *impactFx;
SurfaceFxTable *surfaceFx;*/ SurfaceFxTable *surfaceFx;*/

View File

@ -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};