#include #include "loader/component_loader.hpp" #include "game/dvars.hpp" #include "fastfiles.hpp" #include "command.hpp" #include "console.hpp" #include #include #include namespace fastfiles { static utils::concurrency::container current_fastfile; namespace { utils::hook::detour db_try_load_x_file_internal_hook; utils::hook::detour db_find_x_asset_header_hook; utils::hook::detour db_read_stream_file_hook; int db_read_stream_file_stub(int allow_abort, int finish) { // always use lz4 compressor type when reading stream files *game::g_compressor = game::DB_COMPRESSOR_LZX; return db_read_stream_file_hook.invoke(allow_abort, finish); } void db_try_load_x_file_internal(const char* zone_name, const int flags) { console::info("Loading fastfile %s\n", zone_name); current_fastfile.access([&](std::string& fastfile) { fastfile = zone_name; }); return db_try_load_x_file_internal_hook.invoke(zone_name, flags); } void dump_gsc_script(const std::string& name, game::XAssetHeader header) { if (!dvars::g_dump_scripts->current.enabled) { return; } std::string buffer; buffer.append(header.scriptfile->name, std::strlen(header.scriptfile->name) + 1); buffer.append(reinterpret_cast(&header.scriptfile->compressedLen), sizeof(int)); buffer.append(reinterpret_cast(&header.scriptfile->len), sizeof(int)); buffer.append(reinterpret_cast(&header.scriptfile->bytecodeLen), sizeof(int)); buffer.append(header.scriptfile->buffer, header.scriptfile->compressedLen); buffer.append(reinterpret_cast(header.scriptfile->bytecode), header.scriptfile->bytecodeLen); const auto out_name = std::format("gsc_dump/{}.gscbin", name); utils::io::write_file(out_name, buffer); console::info("Dumped %s\n", out_name.data()); } game::XAssetHeader db_find_x_asset_header_stub(game::XAssetType type, const char* name, int allow_create_default) { const auto start = game::Sys_Milliseconds(); const auto result = db_find_x_asset_header_hook.invoke(type, name, allow_create_default); const auto diff = game::Sys_Milliseconds() - start; if (type == game::ASSET_TYPE_SCRIPTFILE) { dump_gsc_script(name, result); } if (diff > 100) { console::print( result.data == nullptr ? console::con_type_error : console::con_type_warning, "Waited %i msec for asset '%s' of type '%s'.\n", diff, name, game::g_assetNames[type] ); } return result; } } std::string get_current_fastfile() { auto fastfile_copy = current_fastfile.access([&](std::string& fastfile) { return fastfile; }); return fastfile_copy; } constexpr int get_asset_type_size(const game::XAssetType type) { constexpr int asset_type_sizes[] = { 96, 88, 128, 56, 40, 216, 56, 680, 480, 32, 32, 32, 32, 32, 352, 1456, 104, 32, 24, 152, 152, 152, 16, 64, 640, 40, 16, 408, 24, 288, 176, 2800, 48, -1, 40, 24, 200, 88, 16, 120, 3560, 32, 64, 16, 16, -1, -1, -1, -1, 24, 40, 24, 40, 24, 128, 2256, 136, 32, 72, 24, 64, 88, 48, 32, 96, 152, 64, 32, }; return asset_type_sizes[type]; } template char* reallocate_asset_pool() { constexpr auto element_size = get_asset_type_size(Type); static char new_pool[element_size * Size]{0}; assert(get_asset_type_size(Type) == game::DB_GetXAssetTypeSize(Type)); std::memmove(new_pool, game::DB_XAssetPool[Type], game::g_poolSize[Type] * element_size); game::DB_XAssetPool[Type] = new_pool; game::g_poolSize[Type] = Size; return new_pool; } void enum_assets(const game::XAssetType type, const std::function& callback, const bool include_override) { game::DB_EnumXAssets_Internal(type, static_cast([](game::XAssetHeader header, void* data) { const auto& cb = *static_cast*>(data); cb(header); }), &callback, include_override); } class component final : public component_interface { public: void post_unpack() override { db_try_load_x_file_internal_hook.create(SELECT_VALUE(0x1401816F0, 0x1402741C0), &db_try_load_x_file_internal); db_find_x_asset_header_hook.create(game::DB_FindXAssetHeader, db_find_x_asset_header_stub); dvars::g_dump_scripts = game::Dvar_RegisterBool("g_dumpScripts", false, game::DVAR_FLAG_NONE); command::add("g_poolSizes", []() { for (auto i = 0; i < game::ASSET_TYPE_COUNT; i++) { console::info("g_poolSize[%i]: %i // %s\n", i, game::g_poolSize[i], game::g_assetNames[i]); } }); // Allow loading of mixed compressor types utils::hook::nop(SELECT_VALUE(0x1401536D7, 0x140242DF7), 2); reallocate_asset_pool(); if (!game::environment::is_sp()) { const auto* xmodel_pool = reallocate_asset_pool(); utils::hook::inject(0x14026FD63, xmodel_pool + 8); utils::hook::inject(0x14026FDB3, xmodel_pool + 8); utils::hook::inject(0x14026FFAC, xmodel_pool + 8); utils::hook::inject(0x14027463C, xmodel_pool + 8); utils::hook::inject(0x140274689, xmodel_pool + 8); // Reallocate asset pools // Disabled because it causes a crash in the main menu once you rejoin a server after // disconnecting and waiting for the server to map rotating. #if 0 reallocate_asset_pool(); reallocate_asset_pool(); reallocate_asset_pool(); reallocate_asset_pool(); reallocate_asset_pool(); reallocate_asset_pool(); reallocate_asset_pool(); reallocate_asset_pool(); reallocate_asset_pool(); reallocate_asset_pool(); reallocate_asset_pool(); reallocate_asset_pool(); reallocate_asset_pool(); reallocate_asset_pool(); #endif // Fix compressor type on streamed file load db_read_stream_file_hook.create(0x14027AA70, db_read_stream_file_stub); // Allow loading of unsigned fastfiles utils::hook::nop(0x1402427A5, 2); // DB_InflateInit } } }; } REGISTER_COMPONENT(fastfiles::component)