#include #include "loader/component_loader.hpp" #include "game/game.hpp" #include "scheduler.hpp" #include #include #include namespace script { namespace { constexpr size_t GSC_MAGIC = 0x1C000A0D43534780; utils::hook::detour db_findxassetheader_hook; utils::hook::detour load_gametype_script_hook; utils::hook::detour gscr_get_bgb_remaining_hook; std::unordered_map loaded_scripts; game::RawFile* get_loaded_script(const std::string& name) { const auto itr = loaded_scripts.find(name); return (itr == loaded_scripts.end()) ? nullptr : itr->second; } void print_loading_script(const std::string& name) { printf("Loading GSC script '%s'\n", name.data()); } void load_script(std::string& name, const std::string& data) { auto& allocator = *utils::memory::get_allocator(); const auto* file_string = allocator.duplicate_string(data); const auto appdata_path = (game::get_appdata_path() / "data/").generic_string(); const auto host_path = (utils::nt::library{}.get_folder() / "boiii/").generic_string(); auto i = name.find(appdata_path); if (i != std::string::npos) { name.erase(i, appdata_path.length()); } i = name.find(host_path); if (i != std::string::npos) { name.erase(i, host_path.length()); } auto* rawfile = allocator.allocate(); rawfile->name = name.data(); rawfile->buffer = file_string; rawfile->len = static_cast(data.length()); loaded_scripts[name] = rawfile; game::Scr_LoadScript(game::SCRIPTINSTANCE_SERVER, name.substr(0, name.size() - 4).data()); // .gsc suffix will be readded by Scr_LoadScript } void load_scripts_folder(const std::string& script_dir) { if (!utils::io::directory_exists(script_dir)) { return; } const auto scripts = utils::io::list_files(script_dir); for (const auto& script : scripts) { std::string data; auto script_file = script.generic_string(); if (!std::filesystem::is_directory(script) && utils::io::read_file(script_file, &data)) { if (data.size() >= sizeof(GSC_MAGIC) && !std::memcmp(data.data(), &GSC_MAGIC, sizeof(GSC_MAGIC))) { print_loading_script(script_file); load_script(script_file, data); } } else if (std::filesystem::is_directory(script)) { load_scripts_folder(script_file); } } } void load_scripts() { loaded_scripts = {}; const utils::nt::library host{}; load_scripts_folder((game::get_appdata_path() / "data/scripts").string()); load_scripts_folder((host.get_folder() / "boiii/scripts").string()); } game::RawFile* db_findxassetheader_stub(const game::XAssetType type, const char* name, const bool error_if_missing, const int wait_time) { auto* asset_header = db_findxassetheader_hook.invoke( type, name, error_if_missing, wait_time); if (type != game::ASSET_TYPE_SCRIPTPARSETREE) { return asset_header; } auto* script = get_loaded_script(name); if (script) { return script; } return asset_header; } void load_gametype_script_stub() { if (!game::Com_IsInGame() || game::Com_IsRunningUILevel()) { load_gametype_script_hook.invoke(); return; } load_gametype_script_hook.invoke(); load_scripts(); } int server_script_checksum_stub() { return 1; } void gscr_get_bgb_remaining_stub(game::scriptInstance_t inst, void* entref) { game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 255); } } struct component final : generic_component { void post_unpack() override { // Return custom or overrided scripts if found db_findxassetheader_hook.create(game::select(0x141420ED0, 0x1401D5FB0), db_findxassetheader_stub); // Load our scripts when the gametype script is loaded load_gametype_script_hook.create(game::select(0x141AAD850, 0x1402D7140), load_gametype_script_stub); // Force GSC checksums to be valid utils::hook::call(game::select(0x1408F2E5D, 0x1400E2D22), server_script_checksum_stub); // Workaround for "Out of X" gobblegum gscr_get_bgb_remaining_hook.create(game::select(0x141A8CAB0, 0x1402D2310), gscr_get_bgb_remaining_stub); } }; }; REGISTER_COMPONENT(script::component)