From 664a6b644a912979e0e03a3dfea225950c6847de Mon Sep 17 00:00:00 2001 From: m Date: Mon, 20 Jan 2025 15:18:35 -0600 Subject: [PATCH] basic custom fastfile support --- src/client/component/fastfiles.cpp | 420 +++++++++++++++++++++++++--- src/client/component/fastfiles.hpp | 6 + src/client/component/imagefiles.cpp | 239 ++++++++++++++++ src/client/component/imagefiles.hpp | 7 + src/client/component/weapon.cpp | 113 ++++++++ src/client/game/structs.hpp | 21 ++ src/client/game/symbols.hpp | 6 + 7 files changed, 780 insertions(+), 32 deletions(-) create mode 100644 src/client/component/imagefiles.cpp create mode 100644 src/client/component/imagefiles.hpp create mode 100644 src/client/component/weapon.cpp diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp index 9d02af4..93cd378 100644 --- a/src/client/component/fastfiles.cpp +++ b/src/client/component/fastfiles.cpp @@ -2,18 +2,43 @@ #include "loader/component_loader.hpp" #include "game/dvars.hpp" -#include "fastfiles.hpp" #include "command.hpp" #include "console.hpp" +#include "fastfiles.hpp" +#include #include #include -#include +#include namespace fastfiles { static utils::concurrency::container current_fastfile; + namespace + { + template + inline void merge(std::vector* target, T* source, size_t length) + { + if (source) + { + for (size_t i = 0; i < length; ++i) + { + target->push_back(source[i]); + } + } + } + + template + inline void merge(std::vector* target, std::vector source) + { + for (auto& entry : source) + { + target->push_back(entry); + } + } + } + namespace { utils::hook::detour db_try_load_x_file_internal_hook; @@ -21,11 +46,21 @@ namespace fastfiles void db_try_load_x_file_internal(const char* zone_name, const int flags) { + static std::unordered_map zone_overrides = {}; + + auto override_zone_name = zone_overrides.find(zone_name); + if (override_zone_name != zone_overrides.end()) + { + console::info("Overriding fastfile %s with %s\n", zone_name, override_zone_name->second.c_str()); + zone_name = override_zone_name->second.c_str(); + } + 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); } @@ -56,10 +91,12 @@ namespace fastfiles const auto result = db_find_x_asset_header_hook.invoke(type, name, allow_create_default); const auto diff = game::Sys_Milliseconds() - start; +#ifdef DEBUG if (type == game::ASSET_TYPE_SCRIPTFILE) { dump_gsc_script(name, result); } +#endif if (diff > 100) { @@ -73,6 +110,223 @@ namespace fastfiles return result; } + + utils::hook::detour db_link_xasset_entry1_hook; + game::XAssetEntry* db_link_xasset_entry1(game::XAssetType type, game::XAssetHeader* header) + { + if (type == game::ASSET_TYPE_SCRIPTFILE) + { + dump_gsc_script(header->scriptfile->name, *header); + } + + return db_link_xasset_entry1_hook.invoke(type, header); + } + + namespace mp + { + void skip_extra_zones_stub(utils::hook::assembler& a) + { + const auto skip = a.newLabel(); + const auto original = a.newLabel(); + + a.pushad64(); + a.test(esi, game::DB_ZONE_CUSTOM); // allocFlags + a.jnz(skip); + + a.bind(original); + a.popad64(); + a.mov(rdx, 0x140809D40); + a.mov(rcx, rbp); + a.call(0x1406FE120); + a.jmp(0x140271B63); + + a.bind(skip); + a.popad64(); + a.mov(r13d, game::DB_ZONE_CUSTOM); + a.not_(r13d); + a.and_(esi, r13d); + a.jmp(0x140271D02); + } + } + + namespace sp + { + void skip_extra_zones_stub(utils::hook::assembler& a) + { + const auto skip = a.newLabel(); + const auto original = a.newLabel(); + + a.pushad64(); + a.test(edi, game::DB_ZONE_CUSTOM); // allocFlags + a.jnz(skip); + + a.bind(original); + a.popad64(); + a.call(0x140379360); + a.xor_(ecx, ecx); + a.test(eax, eax); + a.setz(cl); + a.jmp(0x1401802D6); + + a.bind(skip); + a.popad64(); + a.mov(r13d, game::DB_ZONE_CUSTOM); + a.not_(r13d); + a.and_(edi, r13d); + a.jmp(0x1401803EF); + } + } + + utils::hook::detour db_read_stream_file_hook; + void db_read_stream_file_stub(int a1, int a2) + { + // always use lz4 compressor type when reading stream files + *game::g_compressor = 4; + return db_read_stream_file_hook.invoke(a1, a2); + } + + utils::hook::detour sys_createfile_hook; + HANDLE sys_create_file(game::Sys_Folder folder, const char* base_filename) + { + const auto* fs_basepath = game::Dvar_FindVar("fs_basepath"); + const auto* fs_game = game::Dvar_FindVar("fs_game"); + + const std::string dir = fs_basepath ? fs_basepath->current.string : ""; + const std::string mod_dir = fs_game ? fs_game->current.string : ""; + const std::string name = base_filename; + + if (name == "mod.ff") + { + if (!mod_dir.empty()) + { + const auto path = utils::string::va("%s\\%s\\%s", + dir.data(), mod_dir.data(), base_filename); + + if (utils::io::file_exists(path)) + { + return CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, nullptr); + } + } + + return INVALID_HANDLE_VALUE; + } + + return sys_createfile_hook.invoke(folder, base_filename); + } + + HANDLE sys_create_file_stub(game::Sys_Folder folder, const char* base_filename) + { + return sys_create_file(folder, base_filename); + } + + bool try_load_zone(std::string name, bool localized, bool game = false) + { + if (localized) + { + const auto language = game::SEH_GetCurrentLanguageCode(); + try_load_zone(language + "_"s + name, false); + if (game::environment::is_mp()) + { + try_load_zone(language + "_"s + name + "_mp"s, false); + } + } + + if (!fastfiles::exists(name)) + { + return false; + } + + game::XZoneInfo info{}; + info.name = name.data(); + info.allocFlags = (game ? game::DB_ZONE_GAME : game::DB_ZONE_COMMON) | game::DB_ZONE_CUSTOM; + info.freeFlags = 0; + game::DB_LoadXAssets(&info, 1u, game::DBSyncMode::DB_LOAD_ASYNC); + return true; + } + + void load_pre_gfx_zones(game::XZoneInfo* zoneInfo, unsigned int zoneCount, game::DBSyncMode syncMode) + { + //imagefiles::close_custom_handles(); + + std::vector data; + merge(&data, zoneInfo, zoneCount); + + // code_pre_gfx + + //weapon::clear_modifed_enums(); + try_load_zone("mod_pre_gfx", true); + try_load_zone("s1_mod_pre_gfx", true); + + game::DB_LoadXAssets(data.data(), static_cast(data.size()), syncMode); + } + + void load_post_gfx_and_ui_and_common_zones(game::XZoneInfo* zoneInfo, unsigned int zoneCount, game::DBSyncMode syncMode) + { + std::vector data; + merge(&data, zoneInfo, zoneCount); + + // code_post_gfx + // ui + // common + + try_load_zone("s1_mod_common", true); + + game::DB_LoadXAssets(data.data(), static_cast(data.size()), syncMode); + + try_load_zone("mod_common", true); + } + + void load_ui_zones(game::XZoneInfo* zoneInfo, unsigned int zoneCount, game::DBSyncMode syncMode) + { + std::vector data; + merge(&data, zoneInfo, zoneCount); + + // ui + + game::DB_LoadXAssets(data.data(), static_cast(data.size()), syncMode); + } + + void load_lua_file_asset_stub(void* a1) + { + const auto fastfile = fastfiles::get_current_fastfile(); + if (fastfile == "mod") + { + console::error("Mod tried to load a lua file!\n"); + return; + } + + // TODO: add usermap LUI loading checks + /* + const auto usermap = fastfiles::get_current_usermap(); + if (usermap.has_value()) + { + const auto& usermap_value = usermap.value(); + const auto usermap_load = usermap_value + "_load"; + + if (fastfile == usermap_value || fastfile == usermap_load) + { + console::error("Usermap tried to load a lua file!\n"); + return; + } + } + */ + + utils::hook::invoke(0x140276480, a1); + } + + utils::hook::detour db_level_load_add_zone_hook; + void db_level_load_add_zone_stub(void* load, const char* name, const unsigned int alloc_flags, + const size_t size_est) + { + if (!strcmp(name, "mp_terminal_cls")) + { + db_level_load_add_zone_hook.invoke(load, name, alloc_flags | game::DB_ZONE_CUSTOM, size_est); + return; + } + + db_level_load_add_zone_hook.invoke(load, name, alloc_flags, size_est); +; } } std::string get_current_fastfile() @@ -85,37 +339,35 @@ namespace fastfiles return fastfile_copy; } - constexpr int get_asset_type_size(const game::XAssetType type) + bool exists(const std::string& zone) { - 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, - }; + const auto is_localized = game::DB_IsLocalized(zone.data()); + const auto handle = sys_create_file((is_localized ? game::SF_ZONE_LOC : game::SF_ZONE), + utils::string::va("%s.ff", zone.data())); - return asset_type_sizes[type]; + if (handle != INVALID_HANDLE_VALUE) + { + CloseHandle(handle); + return true; + } + + return false; } - template - char* reallocate_asset_pool() + void reallocate_asset_pool(game::XAssetType Type, size_t Size) { - 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)); - + const size_t element_size = game::DB_GetXAssetTypeSize(Type); + auto* new_pool = utils::memory::get_allocator()->allocate(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] = Size; + game::g_poolSize[Type] = static_cast(Size); + } - return new_pool; + template + void reallocate_asset_pool_multiplier() + { + reallocate_asset_pool(Type, Multiplier * game::g_poolSize[Type]); } void enum_assets(const game::XAssetType type, const std::function& callback, const bool include_override) @@ -127,6 +379,75 @@ namespace fastfiles }), &callback, include_override); } + const char* get_zone_name(const unsigned int index) + { + if (game::environment::is_sp()) + { + return ""; + } + else + { + return game::g_zones[index].name; + } + } + + void reallocate_asset_pools() + { + reallocate_asset_pool(game::ASSET_TYPE_FONT, 48); + + if (!game::environment::is_sp()) + { + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + reallocate_asset_pool_multiplier(); + } + } + + void enum_asset_entries(const game::XAssetType type, const std::function& callback, bool include_override) + { + constexpr auto max_asset_count = 0x1D8A8; + auto hash = &game::db_hashTable[0]; + for (auto c = 0; c < max_asset_count; c++) + { + for (auto i = *hash; i; ) + { + const auto entry = &game::g_assetEntryPool[i]; + + if (entry->asset.type == type) + { + callback(entry); + + if (include_override && entry->nextOverride) + { + auto next_ovveride = entry->nextOverride; + while (next_ovveride) + { + const auto override = &game::g_assetEntryPool[next_ovveride]; + callback(override); + next_ovveride = override->nextOverride; + } + } + } + + i = entry->nextHash; + } + + ++hash; + } + } + class component final : public component_interface { public: @@ -135,6 +456,7 @@ namespace fastfiles 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); + db_link_xasset_entry1_hook.create(SELECT_VALUE(0x14017F390, 0x1402708F0), db_link_xasset_entry1); dvars::g_dump_scripts = game::Dvar_RegisterBool("g_dumpScripts", false, game::DVAR_FLAG_NONE); command::add("loadzone", [](const command::params& params) @@ -147,7 +469,7 @@ namespace fastfiles game::XZoneInfo info{}; info.name = params.get(1); - info.allocFlags = 1; + info.allocFlags = game::DB_ZONE_COMMON | game::DB_ZONE_CUSTOM; info.freeFlags = 0; game::DB_LoadXAssets(&info, 1u, game::DBSyncMode::DB_LOAD_SYNC); }); @@ -160,16 +482,50 @@ namespace fastfiles } }); - reallocate_asset_pool(); +#ifdef DEBUG + //reallocate_asset_pools(); +#endif + // Allow loading of unsigned fastfiles 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); + utils::hook::nop(0x1402427A5, 2); // DB_InflateInit + } + + // Don't load extra zones with loadzone + if (game::environment::is_sp()) + { + utils::hook::nop(0x1401802CA, 12); + utils::hook::jump(0x1401802CA, utils::hook::assemble(sp::skip_extra_zones_stub), true); + } + else + { + utils::hook::nop(0x140271B54, 15); + utils::hook::jump(0x140271B54, utils::hook::assemble(mp::skip_extra_zones_stub), true); + + // dont load localized zone for custom maps (proper patch for this is here: https://github.com/auroramod/h1-mod/blob/develop/src/client/component/fastfiles.cpp#L442) + db_level_load_add_zone_hook.create(0x1402705C0, db_level_load_add_zone_stub); + } + + // Allow loading of mixed compressor types + utils::hook::nop(SELECT_VALUE(0x1401536D7, 0x140242DF7), 2); + + // Fix compressor type on streamed file load + db_read_stream_file_hook.create(SELECT_VALUE(0x140187450, 0x14027AA70), db_read_stream_file_stub); + + // Add custom zone paths + sys_createfile_hook.create(game::Sys_CreateFile, sys_create_file_stub); + + // load our custom ui and common zones + utils::hook::call(SELECT_VALUE(0x140487CF8, 0x1405A562A), load_post_gfx_and_ui_and_common_zones); + + // load our custom ui zones + utils::hook::call(SELECT_VALUE(0x1402F91D4, 0x1403D06FC), load_ui_zones); + + // prevent mod.ff from loading lua files + if (game::environment::is_mp()) + { + utils::hook::call(0x14024F1B4, load_lua_file_asset_stub); } } }; diff --git a/src/client/component/fastfiles.hpp b/src/client/component/fastfiles.hpp index 8eb7bdd..9e5819b 100644 --- a/src/client/component/fastfiles.hpp +++ b/src/client/component/fastfiles.hpp @@ -6,5 +6,11 @@ namespace fastfiles { std::string get_current_fastfile(); + bool exists(const std::string& zone); + void enum_assets(game::XAssetType type, const std::function& callback, bool include_override); + + const char* get_zone_name(const unsigned int index); + + void enum_asset_entries(const game::XAssetType type, const std::function& callback, bool include_override); } diff --git a/src/client/component/imagefiles.cpp b/src/client/component/imagefiles.cpp new file mode 100644 index 0000000..4d06457 --- /dev/null +++ b/src/client/component/imagefiles.cpp @@ -0,0 +1,239 @@ +#include +#include "loader/component_loader.hpp" + +#include "console.hpp" +#include "filesystem.hpp" +#include "fastfiles.hpp" +#include "scheduler.hpp" +#include "imagefiles.hpp" + +#include "game/game.hpp" + +#include +#include +#include +#include + +#define CUSTOM_IMAGE_FILE_INDEX 96 + +namespace imagefiles +{ + namespace + { + utils::memory::allocator image_file_allocator; + std::unordered_map image_file_handles; + + std::string get_image_file_name() + { + return fastfiles::get_current_fastfile(); + } + + namespace mp + { + struct image_file_unk + { + char __pad0[112]; // 120 in H1 + }; + + std::unordered_map image_file_unk_map; + + void* get_image_file_unk_mp(unsigned int index) + { + if (index != CUSTOM_IMAGE_FILE_INDEX) + { + return &reinterpret_cast( + SELECT_VALUE(0x0, 0x143DC4E90))[index]; + } + + const auto name = get_image_file_name(); + if (image_file_unk_map.find(name) == image_file_unk_map.end()) + { + const auto unk = image_file_allocator.allocate(); + image_file_unk_map[name] = unk; + return unk; + } + + return image_file_unk_map[name]; + } + } + + /* + namespace sp + { + struct image_file_unk + { + char __pad0[96]; + }; + + std::unordered_map image_file_unk_map; + + void* get_image_file_unk_mp(unsigned int index) + { + if (index != CUSTOM_IMAGE_FILE_INDEX) + { + return &reinterpret_cast( + SELECT_VALUE(0x0, 0x143DC4E90))[index]; + } + + const auto name = get_image_file_name(); + if (image_file_unk_map.find(name) == image_file_unk_map.end()) + { + const auto unk = image_file_allocator.allocate(); + image_file_unk_map[name] = unk; + return unk; + } + + return image_file_unk_map[name]; + } + } + */ + + HANDLE get_image_file_handle(unsigned int index) + { + if (index != CUSTOM_IMAGE_FILE_INDEX) + { + return reinterpret_cast( + SELECT_VALUE(0x0, 0x143B74E80))[index]; + } + + const auto name = get_image_file_name(); + return image_file_handles[name]; + } + + void db_create_gfx_image_stream_stub(utils::hook::assembler& a) + { + const auto handle_is_open = a.newLabel(); + + a.movzx(eax, cx); + a.mov(esi, eax); + a.imul(rsi, 0x70); + a.add(rsi, r15); + + a.push(rax); + a.push(rax); + a.pushad64(); + a.mov(rcx, rax); + a.call_aligned(mp::get_image_file_unk_mp); + a.mov(qword_ptr(rsp, 0x80), rax); + a.popad64(); + a.pop(rax); + a.mov(rsi, rax); + a.pop(rax); + + a.push(rax); + a.push(rax); + a.pushad64(); + a.mov(rcx, rax); + a.call_aligned(get_image_file_handle); + a.mov(qword_ptr(rsp, 0x80), rax); + a.popad64(); + a.pop(rax); + a.mov(r12, rax); + a.pop(rax); + + a.cmp(r12, r13); + a.jnz(handle_is_open); + a.jmp(SELECT_VALUE(0x0, 0x140279C8B)); + + a.bind(handle_is_open); + a.jmp(SELECT_VALUE(0x0, 0x140279CDB)); + } + + void* pakfile_open_stub(void* /*handles*/, unsigned int count, int is_imagefile, + unsigned int index, short is_localized) + { +#ifdef DEBUG + console::info("Opening %s%d.pak (localized:%d)\n", is_imagefile ? "imagefile" : "soundfile", index, is_localized); +#endif + + if (index != CUSTOM_IMAGE_FILE_INDEX) + { + return utils::hook::invoke( + SELECT_VALUE(0x0, 0x1404CC180), + SELECT_VALUE(0x0, 0x143B74E80), + count, is_imagefile, index, is_localized + ); + } + + const auto name = get_image_file_name(); + const auto handle = game::Sys_CreateFile(is_localized ? game::SF_PAKFILE_LOC : game::SF_PAKFILE, utils::string::va("%s.pak", name.data())); + if (handle != nullptr) + { + image_file_handles[name] = handle; + } + return handle; + } + + int com_sprintf_stub(char* buffer, const int len, const char* fmt, unsigned int index) + { + if (index != CUSTOM_IMAGE_FILE_INDEX) + { + return game::Com_sprintf(buffer, len, fmt, index); + } + + const auto name = get_image_file_name(); + return game::Com_sprintf(buffer, len, "%s.pak", name.data()); + } + } + + void close_custom_handles() + { + for (const auto& handle : image_file_handles) + { + if (handle.second != nullptr) + { + utils::hook::invoke(0x140608A20, handle.second); // Sys_CloseFile + } + } + + image_file_handles.clear(); + //sp::image_file_unk_map.clear(); + mp::image_file_unk_map.clear(); + image_file_allocator.clear(); + } + + void close_handle(const std::string& fastfile) + { + if (!image_file_handles.contains(fastfile)) + { + return; + } + + const auto handle = image_file_handles[fastfile]; + if (handle != nullptr) + { + utils::hook::invoke(0x140608A20, handle); + } + + image_file_handles.erase(fastfile); + if (game::environment::is_sp()) + { + //image_file_allocator.free(sp::image_file_unk_map[fastfile]); + //sp::image_file_unk_map.erase(fastfile); + } + else + { + image_file_allocator.free(mp::image_file_unk_map[fastfile]); + mp::image_file_unk_map.erase(fastfile); + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + if (!game::environment::is_mp()) + { + return; + } + + utils::hook::jump(SELECT_VALUE(0x0, 0x140279C79), + utils::hook::assemble(db_create_gfx_image_stream_stub), true); + utils::hook::call(SELECT_VALUE(0x0, 0x140279CBD), pakfile_open_stub); + utils::hook::call(SELECT_VALUE(0x0, 0x140279C9F), com_sprintf_stub); + } + }; +} + +REGISTER_COMPONENT(imagefiles::component) diff --git a/src/client/component/imagefiles.hpp b/src/client/component/imagefiles.hpp new file mode 100644 index 0000000..ff30962 --- /dev/null +++ b/src/client/component/imagefiles.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace imagefiles +{ + void close_custom_handles(); + void close_handle(const std::string& fastfile); +} diff --git a/src/client/component/weapon.cpp b/src/client/component/weapon.cpp new file mode 100644 index 0000000..6840929 --- /dev/null +++ b/src/client/component/weapon.cpp @@ -0,0 +1,113 @@ +#include +#include "loader/component_loader.hpp" + +#include "console.hpp" +#include "fastfiles.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include +#include + +namespace weapon +{ + namespace + { + utils::hook::detour g_setup_level_weapon_def_hook; + void g_setup_level_weapon_def_stub() + { + // precache level weapons first + g_setup_level_weapon_def_hook.invoke(); + + std::vector weapons; + + // find all weapons in asset pools + fastfiles::enum_assets(game::ASSET_TYPE_WEAPON, [&weapons](game::XAssetHeader header) + { + weapons.push_back(reinterpret_cast(header.data)); + }, false); + + // sort weapons + std::sort(weapons.begin(), weapons.end(), [](game::WeaponDef* weapon1, game::WeaponDef* weapon2) + { + return std::string_view(weapon1->name) < + std::string_view(weapon2->name); + }); + + // precache items + for (std::size_t i = 0; i < weapons.size(); i++) + { + //console::info("precaching weapon \"%s\"\n", weapons[i]->name); + game::G_GetWeaponForName(weapons[i]->name); + } + } + + utils::hook::detour xmodel_get_bone_index_hook; + int xmodel_get_bone_index_stub(game::XModel* model, game::scr_string_t name, unsigned int offset, char* index) + { + auto result = xmodel_get_bone_index_hook.invoke(model, name, offset, index); + if (result) + { + return result; + } + + const auto original_index = *index; + const auto original_result = result; + + if (name == game::SL_FindString("tag_weapon_right") || + name == game::SL_FindString("tag_knife_attach")) + { + const auto tag_weapon = game::SL_FindString("tag_weapon"); + result = xmodel_get_bone_index_hook.invoke(model, tag_weapon, offset, index); + if (result) + { + console::info("using tag_weapon instead of %s (%s, %d, %d)\n", game::SL_ConvertToString(name), model->name, offset, *index); + return result; + } + } + + *index = original_index; + result = original_result; + + return result; + } + + void cw_mismatch_error_stub(int, const char* msg, ...) + { + char buffer[0x100]; + + va_list ap; + va_start(ap, msg); + + vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap); + + va_end(ap); + + console::error(buffer); + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + if (!game::environment::is_mp()) + { + return; + } + + // precache all weapons that are loaded in zones + g_setup_level_weapon_def_hook.create(0x140340DE0, g_setup_level_weapon_def_stub); + + // use tag_weapon if tag_weapon_right or tag_knife_attach are not found on model + xmodel_get_bone_index_hook.create(0x1404E2A50, xmodel_get_bone_index_stub); + + // make custom weapon index mismatch not drop in CG_SetupCustomWeapon + utils::hook::call(0x1401E973D, cw_mismatch_error_stub); + } + }; +} + +REGISTER_COMPONENT(weapon::component) diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index dbef2e0..dcf600c 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1951,4 +1951,25 @@ namespace game HksError m_error; }; } + + enum DBAllocFlags : std::int32_t + { + DB_ZONE_NONE = 0x0, + DB_ZONE_COMMON = 0x1, + DB_ZONE_UI = 0x2, + DB_ZONE_GAME = 0x4, + DB_ZONE_LOAD = 0x8, + DB_ZONE_DEV = 0x10, + DB_ZONE_BASEMAP = 0x20, + DB_ZONE_TRANSIENT_POOL = 0x40, + DB_ZONE_TRANSIENT_MASK = 0x40, + DB_ZONE_CUSTOM = 0x1000 // added for custom zone loading + }; + + struct XZone + { + char name[64]; + char pad_0040[432]; + }; + static_assert(sizeof(XZone) == 496); } diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 426712c..3e9e81d 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -58,6 +58,7 @@ namespace game WEAK symbol DB_GetRawFileLen{0x14017E890, 0x14026FCC0}; WEAK symbol DB_GetRawBuffer{0x14017E750, 0x14026FB90}; WEAK symbol DB_ReadRawFile{0x140180E30, 0x140273080}; + WEAK symbol DB_IsLocalized{0x14017EC80, 0x140270190}; WEAK symbol Dvar_FindVar{0x140370860, 0x1404BF8B0}; WEAK symbol Dvar_ClearModified{0x140370700, 0x1404BF690}; @@ -212,6 +213,7 @@ namespace game WEAK symbol UI_RunMenuScript{0, 0x140490060}; WEAK symbol UI_TextWidth{0, 0x140492380}; + WEAK symbol SEH_GetCurrentLanguageCode{0x140339280, 0x140474560}; WEAK symbol SEH_GetCurrentLanguageName{0x140339300, 0x1404745C0}; WEAK symbol PMem_AllocFromSource_NoDebug{0x1403775F0, 0x1404C7BA0}; @@ -260,6 +262,7 @@ namespace game WEAK symbol g_assetEntryPool{0x142CC2400, 0x14379F100}; WEAK symbol g_poolSize{0x140804140, 0x1409B4B90}; WEAK symbol g_assetNames{0x140803C90, 0x1409B3180}; + WEAK symbol g_compressor{0x141598580, 0x141E0B080}; WEAK symbol threadIds{0x149632EC0, 0x147DCEA30}; @@ -267,6 +270,9 @@ namespace game WEAK symbol tls_index{0x14F65DAF0, 0x150085C44}; + WEAK symbol g_zoneCount{0x0, 0x14379DBCC}; + WEAK symbol g_zones{0x0, 0x143B50618}; + namespace mp { WEAK symbol g_entities{0, 0x144758C70};