#include #include "loader/component_loader.hpp" #include "game/game.hpp" #include "game/dvars.hpp" #include "command.hpp" #include "console.hpp" #include "fastfiles.hpp" #include #include #include #include namespace fastfiles { namespace { utils::hook::detour db_try_load_x_file_internal_hook; utils::hook::detour db_find_x_asset_header_hook; void db_try_load_x_file_internal(const char* zone_name, const int zone_flags, const int is_base_map) { console::info("Loading fastfile %s\n", zone_name); db_try_load_x_file_internal_hook.invoke(zone_name, zone_flags, is_base_map); } void dump_gsc_script(const std::string& name, game::XAssetHeader header) { if (!dvars::g_dump_scripts->current.enabled) { return; } const auto out_name = std::format("gsc_dump/{}.gscbin", name); if (utils::io::file_exists(out_name)) { return; } std::string buffer; buffer.append(header.scriptfile->name, std::strlen(header.scriptfile->name) + 1); buffer.append(reinterpret_cast(&header.scriptfile->compressedLen), 4); buffer.append(reinterpret_cast(&header.scriptfile->len), 4); buffer.append(reinterpret_cast(&header.scriptfile->bytecodeLen), 4); buffer.append(header.scriptfile->buffer, header.scriptfile->compressedLen); buffer.append(reinterpret_cast(header.scriptfile->bytecode), header.scriptfile->bytecodeLen); utils::io::write_file(out_name, buffer); console::info("Dumped %s\n", out_name.c_str()); } void dump_csv_table(const std::string& name, game::XAssetHeader header) { if (!dvars::g_dump_string_tables->current.enabled) { return; } const auto out_name = std::format("csv_dump/{}.csv", name); if (utils::io::file_exists(out_name)) { return; } std::string buffer; for (auto row = 0; row < header.stringTable->rowCount; row++) { for (auto column = 0; column < header.stringTable->columnCount; column++) { const auto* string = header.stringTable->values[(row * header.stringTable->columnCount) + column].string; buffer.append(utils::string::va("%s%s", string ? string : "", (column == header.stringTable->columnCount - 1) ? "\n" : ",")); } } utils::io::write_file(out_name, buffer); console::info("Dumped %s\n", out_name.c_str()); } 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 (type == game::ASSET_TYPE_STRINGTABLE) { dump_csv_table(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; } void reallocate_asset_pool(const game::XAssetType type, const unsigned int new_size) { const size_t element_size = game::DB_GetXAssetTypeSize(type); auto* new_pool = utils::memory::get_allocator()->allocate(new_size * element_size); std::memmove(new_pool, game::DB_XAssetPool[type], game::g_poolSize[type] * element_size); game::DB_XAssetPool[type] = new_pool; game::g_poolSize[type] = new_size; } void p_mem_free_stub(const char* name, game::PMem_Direction alloc_dir) { console::info("Unloaded fastfile %s\n", name); game::PMem_Free(name, alloc_dir); } } 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(0x140275850, 0x1403237F0), &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, "Dump GSC scripts to binary format"); dvars::g_dump_string_tables = game::Dvar_RegisterBool("g_dumpStringTables", false, game::DVAR_FLAG_NONE, "Dump CSV files"); utils::hook::call(SELECT_VALUE(0x1402752DF, 0x140156350), p_mem_free_stub); utils::hook::call(SELECT_VALUE(0x140276004, 0x140324259), p_mem_free_stub); command::add("materiallist", [](const command::params& params) { game::DB_EnumXAssets_FastFile(game::ASSET_TYPE_MATERIAL, [](const game::XAssetHeader header, void*) { if(header.material && header.material->name) { console::info("%s\n", header.material->name); } }, nullptr, false); }); if (!game::environment::is_sp()) { reallocate_asset_pool(game::ASSET_TYPE_WEAPON, 320); // Allow loading of unsigned fastfiles utils::hook::set(0x1402FBF23, 0xEB); // DB_LoadXFile utils::hook::nop(0x1402FC445, 2); // DB_SetFileLoadCompressor } } }; } REGISTER_COMPONENT(fastfiles::component)