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 "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);
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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();
|
||||||
}
|
}
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user