Allow mods to load other zones through zones.csv

This commit is contained in:
fed 2022-11-12 21:32:43 +01:00
parent 8a825bf188
commit 44c1a1ccd7
8 changed files with 197 additions and 20 deletions

View File

@ -6,10 +6,12 @@
#include "command.hpp" #include "command.hpp"
#include "console.hpp" #include "console.hpp"
#include "localized_strings.hpp" #include "localized_strings.hpp"
#include "mods.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/concurrency.hpp> #include <utils/concurrency.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
#include <utils/io.hpp>
namespace fastfiles namespace fastfiles
{ {
@ -113,7 +115,7 @@ namespace fastfiles
a.jmp(0x140415E29); a.jmp(0x140415E29);
} }
bool try_load_zone(std::string name, bool localized, bool game = false) bool try_load_zone(const std::string& name, bool localized, bool game = false)
{ {
if (localized) if (localized)
{ {
@ -134,6 +136,19 @@ namespace fastfiles
return true; return true;
} }
void load_mod_zones()
{
try_load_zone("mod", true);
const auto mod_zones = mods::get_mod_zones();
for (const auto& zone : mod_zones)
{
if (zone.alloc_flags & game::DB_ZONE_COMMON)
{
try_load_zone(zone.name, true);
}
}
}
void load_post_gfx_and_ui_and_common_zones(game::XZoneInfo* zoneInfo, void load_post_gfx_and_ui_and_common_zones(game::XZoneInfo* zoneInfo,
unsigned int zoneCount, game::DBSyncMode syncMode) unsigned int zoneCount, game::DBSyncMode syncMode)
{ {
@ -146,7 +161,7 @@ namespace fastfiles
game::DB_LoadXAssets(zoneInfo, zoneCount, syncMode); game::DB_LoadXAssets(zoneInfo, zoneCount, syncMode);
try_load_zone("mod", true); load_mod_zones();
} }
constexpr unsigned int get_asset_type_size(const game::XAssetType type) constexpr unsigned int get_asset_type_size(const game::XAssetType type)
@ -419,6 +434,37 @@ namespace fastfiles
add_custom_level_load_zone(load, name, size_est); add_custom_level_load_zone(load, name, size_est);
} }
void add_mod_zones(game::LevelLoad* load)
{
const auto mod_zones = mods::get_mod_zones();
for (const auto& zone : mod_zones)
{
if (zone.alloc_flags & game::DB_ZONE_GAME)
{
add_custom_level_load_zone(load, zone.name.data(), 0x40000);
}
}
}
void db_decide_level_load_stub(utils::hook::assembler& a)
{
const auto loc_140412859 = a.newLabel();
a.pushad64();
a.mov(rcx, rbx);
a.call_aligned(add_mod_zones);
a.popad64();
a.mov(rcx, rdi);
a.call_aligned(0x140609650);
a.test(al, al);
a.jz(loc_140412859);
a.jmp(0x140412817);
a.bind(loc_140412859);
a.jmp(0x140412859);
}
void db_load_level_add_map_zone_stub(game::LevelLoad* load, const char* name, const unsigned int alloc_flags, void db_load_level_add_map_zone_stub(game::LevelLoad* load, const char* name, const unsigned int alloc_flags,
const size_t size_est) const size_t size_est)
{ {
@ -554,10 +600,13 @@ namespace fastfiles
// only load extra zones with addon maps & common_specialops & common_survival & custom maps if they exist // only load extra zones with addon maps & common_specialops & common_survival & custom maps if they exist
utils::hook::call(0x1404128B0, db_load_level_add_map_zone_stub); utils::hook::call(0x1404128B0, db_load_level_add_map_zone_stub);
utils::hook::call(0x140412854, db_load_level_add_custom_zone_stub);
utils::hook::call(0x14041282D, db_load_level_add_custom_zone_stub); utils::hook::call(0x14041282D, db_load_level_add_custom_zone_stub);
utils::hook::call(0x140412854, db_load_level_add_custom_zone_stub);
utils::hook::call(0x14041287C, db_load_level_add_custom_zone_stub); utils::hook::call(0x14041287C, db_load_level_add_custom_zone_stub);
// Load custom mod zones with DB_ZONE_GAME alloc flag
utils::hook::jump(0x14041280B, utils::hook::assemble(db_decide_level_load_stub), true);
// Load assets from 2nd phase (common_specialops, addon map) with DB_LOAD_SYNC // Load assets from 2nd phase (common_specialops, addon map) with DB_LOAD_SYNC
utils::hook::call(0x140414EA1, db_load_xassets_stub); utils::hook::call(0x140414EA1, db_load_xassets_stub);

View File

@ -52,8 +52,7 @@ namespace filesystem
const auto mod_path = utils::flags::get_flag("mod"); const auto mod_path = utils::flags::get_flag("mod");
if (mod_path.has_value()) if (mod_path.has_value())
{ {
filesystem::register_path(mod_path.value()); mods::set_mod(mod_path.value());
mods::mod_path = mod_path.value();
} }
localized_strings::clear(); localized_strings::clear();

View File

@ -14,13 +14,41 @@
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/io.hpp> #include <utils/io.hpp>
#include <utils/string.hpp>
namespace mods namespace mods
{ {
std::string mod_path{};
namespace namespace
{ {
struct mod_zone_info
{
bool has_common_zones;
std::vector<mod_zone> zones;
};
struct
{
std::optional<std::string> path;
mod_zone_info zone_info;
} mod_info;
std::unordered_map<std::string, game::DBAllocFlags> alloc_flags_map =
{
{"common", game::DB_ZONE_COMMON},
{"game", game::DB_ZONE_GAME},
};
unsigned int get_alloc_flag(const std::string& name)
{
const auto lower = utils::string::to_lower(name);
if (alloc_flags_map.find(lower) != alloc_flags_map.end())
{
return alloc_flags_map[lower];
}
return game::DB_ZONE_COMMON;
}
utils::hook::detour db_release_xassets_hook; utils::hook::detour db_release_xassets_hook;
bool release_assets = false; bool release_assets = false;
@ -56,11 +84,85 @@ namespace mods
utils::nt::relaunch_self(" -singleplayer "s.append(arg), true); utils::nt::relaunch_self(" -singleplayer "s.append(arg), true);
utils::nt::terminate(); utils::nt::terminate();
} }
void clear_mod_zones()
{
mod_info.zone_info = {};
}
void parse_mod_zones()
{
clear_mod_zones();
if (!mod_info.path.has_value())
{
return;
}
const auto path = mod_info.path.value() + "/zones.csv";
std::string data{};
if (!utils::io::read_file(path, &data))
{
return;
}
const auto lines = utils::string::split_lines(data);
for (const auto& line : lines)
{
const auto values = utils::string::split(line, ',');
if (values.size() < 2)
{
continue;
}
const auto alloc_flags = get_alloc_flag(values[0]) | game::DB_ZONE_CUSTOM;
if (alloc_flags & game::DB_ZONE_COMMON)
{
mod_info.zone_info.has_common_zones = true;
}
mod_info.zone_info.zones.emplace_back(values[1], alloc_flags);
}
}
} }
bool mod_requires_restart(const std::string& path) bool mod_requires_restart(const std::string& path)
{ {
return utils::io::file_exists(path + "/mod.ff") || utils::io::file_exists(path + "/zone/mod.ff"); return mod_info.zone_info.has_common_zones ||
utils::io::file_exists(path + "/mod.ff") ||
utils::io::file_exists(path + "/zone/mod.ff");
}
void set_mod(const std::string& path)
{
if (mod_info.path.has_value())
{
filesystem::unregister_path(mod_info.path.value());
}
mod_info.path = path;
filesystem::register_path(path);
parse_mod_zones();
}
void clear_mod()
{
if (mod_info.path.has_value())
{
filesystem::unregister_path(mod_info.path.value());
}
mod_info.path.reset();
clear_mod_zones();
}
std::vector<mod_zone> get_mod_zones()
{
return mod_info.zone_info.zones;
}
std::optional<std::string> get_mod()
{
return mod_info.path;
} }
class component final : public component_interface class component final : public component_interface
@ -98,8 +200,10 @@ namespace mods
} }
console::info("Loading mod %s\n", path); console::info("Loading mod %s\n", path);
set_mod(path);
if (mod_requires_restart(mod_path) || mod_requires_restart(path)) if ((mod_info.path.has_value() && mod_requires_restart(mod_info.path.value())) ||
mod_requires_restart(path))
{ {
// vid_restart is still broken :( // vid_restart is still broken :(
console::info("Restarting...\n"); console::info("Restarting...\n");
@ -107,16 +211,13 @@ namespace mods
} }
else else
{ {
filesystem::unregister_path(mod_path);
filesystem::register_path(path);
mod_path = path;
restart(); restart();
} }
}); });
command::add("unloadmod", [](const command::params& params) command::add("unloadmod", [](const command::params& params)
{ {
if (mod_path.empty()) if (!mod_info.path.has_value())
{ {
console::info("No mod loaded\n"); console::info("No mod loaded\n");
return; return;
@ -129,17 +230,16 @@ namespace mods
return; return;
} }
console::info("Unloading mod %s\n", mod_path.data()); console::info("Unloading mod %s\n", mod_info.path.value().data());
if (mod_requires_restart(mod_path)) if (mod_requires_restart(mod_info.path.value()))
{ {
console::info("Restarting...\n"); console::info("Restarting...\n");
full_restart(""); full_restart("");
} }
else else
{ {
filesystem::unregister_path(mod_path); clear_mod();
mod_path.clear();
restart(); restart();
} }
}); });

View File

@ -2,7 +2,14 @@
namespace mods namespace mods
{ {
extern std::string mod_path; struct mod_zone
{
std::string name;
unsigned int alloc_flags;
};
bool mod_requires_restart(const std::string& path); bool mod_requires_restart(const std::string& path);
void set_mod(const std::string& path);
std::optional<std::string> get_mod();
std::vector<mod_zone> get_mod_zones();
} }

View File

@ -231,7 +231,8 @@ namespace ui_scripting
game_type["getloadedmod"] = [](const game&) game_type["getloadedmod"] = [](const game&)
{ {
return mods::mod_path; const auto& path = mods::get_mod();
return path.value_or("");
}; };
game_type["addlocalizedstring"] = [](const game&, const std::string& string, game_type["addlocalizedstring"] = [](const game&, const std::string& string,

View File

@ -761,7 +761,8 @@ namespace scripting::lua
game_type["getloadedmod"] = [](const game&) game_type["getloadedmod"] = [](const game&)
{ {
return mods::mod_path; const auto& mod = mods::get_mod();
return mod.value_or("");
}; };
game_type["addlocalizedstring"] = [](const game&, const std::string& string, game_type["addlocalizedstring"] = [](const game&, const std::string& string,

View File

@ -34,6 +34,25 @@ namespace utils::string
return elems; return elems;
} }
std::vector<std::string> split_lines(const std::string& s)
{
std::stringstream ss(s);
std::string item;
std::vector<std::string> elems;
while (std::getline(ss, item, '\n'))
{
if (item.ends_with('\r'))
{
item.pop_back();
}
elems.push_back(item); // elems.push_back(std::move(item)); // if C++11 (based on comment from @mchiasson)
}
return elems;
}
std::string to_lower(std::string text) std::string to_lower(std::string text)
{ {
std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input) std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input)

View File

@ -81,6 +81,7 @@ namespace utils::string
const char* va(const char* fmt, ...); const char* va(const char* fmt, ...);
std::vector<std::string> split(const std::string& s, char delim); std::vector<std::string> split(const std::string& s, char delim);
std::vector<std::string> split_lines(const std::string& s);
std::string to_lower(std::string text); std::string to_lower(std::string text);
std::string to_upper(std::string text); std::string to_upper(std::string text);