Allow mods to load other zones through zones.csv
This commit is contained in:
parent
8a825bf188
commit
44c1a1ccd7
@ -6,10 +6,12 @@
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
#include "mods.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace fastfiles
|
||||
{
|
||||
@ -113,7 +115,7 @@ namespace fastfiles
|
||||
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)
|
||||
{
|
||||
@ -134,6 +136,19 @@ namespace fastfiles
|
||||
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,
|
||||
unsigned int zoneCount, game::DBSyncMode syncMode)
|
||||
{
|
||||
@ -146,7 +161,7 @@ namespace fastfiles
|
||||
|
||||
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)
|
||||
@ -419,6 +434,37 @@ namespace fastfiles
|
||||
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,
|
||||
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
|
||||
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(0x140412854, 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
|
||||
utils::hook::call(0x140414EA1, db_load_xassets_stub);
|
||||
|
||||
|
@ -52,8 +52,7 @@ namespace filesystem
|
||||
const auto mod_path = utils::flags::get_flag("mod");
|
||||
if (mod_path.has_value())
|
||||
{
|
||||
filesystem::register_path(mod_path.value());
|
||||
mods::mod_path = mod_path.value();
|
||||
mods::set_mod(mod_path.value());
|
||||
}
|
||||
|
||||
localized_strings::clear();
|
||||
|
@ -14,13 +14,41 @@
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace mods
|
||||
{
|
||||
std::string mod_path{};
|
||||
|
||||
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;
|
||||
bool release_assets = false;
|
||||
|
||||
@ -56,11 +84,85 @@ namespace mods
|
||||
utils::nt::relaunch_self(" -singleplayer "s.append(arg), true);
|
||||
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)
|
||||
{
|
||||
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
|
||||
@ -98,8 +200,10 @@ namespace mods
|
||||
}
|
||||
|
||||
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 :(
|
||||
console::info("Restarting...\n");
|
||||
@ -107,16 +211,13 @@ namespace mods
|
||||
}
|
||||
else
|
||||
{
|
||||
filesystem::unregister_path(mod_path);
|
||||
filesystem::register_path(path);
|
||||
mod_path = path;
|
||||
restart();
|
||||
}
|
||||
});
|
||||
|
||||
command::add("unloadmod", [](const command::params& params)
|
||||
{
|
||||
if (mod_path.empty())
|
||||
if (!mod_info.path.has_value())
|
||||
{
|
||||
console::info("No mod loaded\n");
|
||||
return;
|
||||
@ -129,17 +230,16 @@ namespace mods
|
||||
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");
|
||||
full_restart("");
|
||||
}
|
||||
else
|
||||
{
|
||||
filesystem::unregister_path(mod_path);
|
||||
mod_path.clear();
|
||||
clear_mod();
|
||||
restart();
|
||||
}
|
||||
});
|
||||
|
@ -2,7 +2,14 @@
|
||||
|
||||
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);
|
||||
void set_mod(const std::string& path);
|
||||
std::optional<std::string> get_mod();
|
||||
std::vector<mod_zone> get_mod_zones();
|
||||
}
|
@ -231,7 +231,8 @@ namespace ui_scripting
|
||||
|
||||
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,
|
||||
|
@ -761,7 +761,8 @@ namespace scripting::lua
|
||||
|
||||
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,
|
||||
|
@ -34,6 +34,25 @@ namespace utils::string
|
||||
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::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input)
|
||||
|
@ -81,6 +81,7 @@ namespace utils::string
|
||||
const char* va(const char* fmt, ...);
|
||||
|
||||
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_upper(std::string text);
|
||||
|
Loading…
Reference in New Issue
Block a user