From cc895c766e1382e248d1d8215dc9c21e91e06df1 Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Sat, 21 Oct 2023 19:37:39 +0200 Subject: [PATCH 1/6] Weapon dumping (waiting for IW4OF support) --- .../Modules/AssetInterfaces/IWeapon.cpp | 22 +++++++++++++++++++ .../Modules/AssetInterfaces/IWeapon.hpp | 1 + 2 files changed, 23 insertions(+) diff --git a/src/Components/Modules/AssetInterfaces/IWeapon.cpp b/src/Components/Modules/AssetInterfaces/IWeapon.cpp index 40e8fdd5..57a65816 100644 --- a/src/Components/Modules/AssetInterfaces/IWeapon.cpp +++ b/src/Components/Modules/AssetInterfaces/IWeapon.cpp @@ -782,4 +782,26 @@ namespace Assets buffer->popBlock(); } + + IWeapon::IWeapon() + { + Components::Command::Add("dumpweapon", [](const Components::Command::Params* params) + { + if (params->size() < 2) return; + + std::string weapon = params->get(1); + + const auto header = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_WEAPON, weapon.data()); + if (header.data) + { + Components::ZoneBuilder::RefreshExporterWorkDirectory(); + Components::ZoneBuilder::GetExporter()->write(Game::XAssetType::ASSET_TYPE_WEAPON, header.data); + } + else + { + Components::Logger::Print("Could not find weapon {}!\n", weapon); + } + } + ); + } } diff --git a/src/Components/Modules/AssetInterfaces/IWeapon.hpp b/src/Components/Modules/AssetInterfaces/IWeapon.hpp index e9f05d5b..55d5ae5b 100644 --- a/src/Components/Modules/AssetInterfaces/IWeapon.hpp +++ b/src/Components/Modules/AssetInterfaces/IWeapon.hpp @@ -6,6 +6,7 @@ namespace Assets { public: Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_WEAPON; } + IWeapon(); void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; From cdb8a9b9c41a48720cbca207669dd4b098902954 Mon Sep 17 00:00:00 2001 From: Roxanne Date: Fri, 17 Nov 2023 14:06:21 +0100 Subject: [PATCH 2/6] Allow OnLoad unsubscription, weapon implementation fixes, formattting --- deps/iw4-open-formats | 2 +- src/Components/Modules/AssetHandler.cpp | 6 +- src/Components/Modules/AssetHandler.hpp | 2 +- .../Modules/AssetInterfaces/IWeapon.cpp | 37 +- src/Components/Modules/Weapon.cpp | 19 + src/Components/Modules/Weapon.hpp | 4 + src/Components/Modules/ZoneBuilder.cpp | 920 +++++++++--------- src/Utils/Utils.hpp | 25 + 8 files changed, 569 insertions(+), 446 deletions(-) diff --git a/deps/iw4-open-formats b/deps/iw4-open-formats index 6af596a0..fa074d9b 160000 --- a/deps/iw4-open-formats +++ b/deps/iw4-open-formats @@ -1 +1 @@ -Subproject commit 6af596a010eebf727e5d914bf9a01903c14ae128 +Subproject commit fa074d9ba5f61c200db05878bb9fba5ee37a8994 diff --git a/src/Components/Modules/AssetHandler.cpp b/src/Components/Modules/AssetHandler.cpp index 5995d3c4..ae538b8b 100644 --- a/src/Components/Modules/AssetHandler.cpp +++ b/src/Components/Modules/AssetHandler.cpp @@ -349,9 +349,13 @@ namespace Components AssetHandler::TypeCallbacks[type] = callback; } - void AssetHandler::OnLoad(Utils::Slot callback) + std::function AssetHandler::OnLoad(Utils::Slot callback) { AssetHandler::RestrictSignal.connect(callback); + + return [callback](){ + AssetHandler::RestrictSignal.disconnect(callback); + }; } void AssetHandler::ClearRelocations() diff --git a/src/Components/Modules/AssetHandler.hpp b/src/Components/Modules/AssetHandler.hpp index c965e58b..34a21a43 100644 --- a/src/Components/Modules/AssetHandler.hpp +++ b/src/Components/Modules/AssetHandler.hpp @@ -23,7 +23,7 @@ namespace Components ~AssetHandler(); static void OnFind(Game::XAssetType type, Utils::Slot callback); - static void OnLoad(Utils::Slot callback); + static std::function OnLoad(Utils::Slot callback); static void ClearRelocations(); static void Relocate(void* start, void* to, DWORD size = 4); diff --git a/src/Components/Modules/AssetInterfaces/IWeapon.cpp b/src/Components/Modules/AssetInterfaces/IWeapon.cpp index 57a65816..e3b24bc1 100644 --- a/src/Components/Modules/AssetInterfaces/IWeapon.cpp +++ b/src/Components/Modules/AssetInterfaces/IWeapon.cpp @@ -3,8 +3,16 @@ namespace Assets { - void IWeapon::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) + void IWeapon::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { + header->weapon = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_WEAPON, name); + + if (header->weapon) + { + return; + } + + // Try loading raw weapon if (Components::FileSystem::File(std::format("weapons/mp/{}", name))) { @@ -193,6 +201,33 @@ namespace Assets LoadWeapSound(missileConeSoundAlias); LoadWeapSound(missileConeSoundAliasAtBase); + + for (size_t i = 0; i < 37; i++) + { + { + const auto anim = asset->weapDef->szXAnimsLeftHanded[i]; + if (anim && strnlen(anim, 1) > 0) { + builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_XANIMPARTS, anim, false); + } + } + { + const auto anim = asset->weapDef->szXAnimsRightHanded[i]; + if (anim && strnlen(anim, 1) > 0) { + builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_XANIMPARTS, anim, false); + } + } + { + const auto anim = asset->szXAnims[i]; + if (anim && strnlen(anim, 1) > 0) { + builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_XANIMPARTS, anim, false); + } + } + } + + if (asset->szAltWeaponName && *asset->szAltWeaponName != 0 && !asset->dpadIcon) // A very bad way to check if this is already an alt + { + builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_WEAPON, asset->szAltWeaponName, false); + } } void IWeapon::writeWeaponDef(Game::WeaponDef* def, Components::ZoneBuilder::Zone* builder, Utils::Stream* buffer) diff --git a/src/Components/Modules/Weapon.cpp b/src/Components/Modules/Weapon.cpp index 2451420c..b9eb33ab 100644 --- a/src/Components/Modules/Weapon.cpp +++ b/src/Components/Modules/Weapon.cpp @@ -6,6 +6,7 @@ namespace Components { const Game::dvar_t* Weapon::BGWeaponOffHandFix; + Game::XModel* Weapon::G_ModelIndexReallocated[G_MODELINDEX_LIMIT]; Game::WeaponCompleteDef* Weapon::LoadWeaponCompleteDef(const char* name) { @@ -433,6 +434,20 @@ namespace Components Utils::Hook::Set(0x4F76FB, 0x12EC + (sizeof(bg_sharedAmmoCaps) - (1200 * 4))); // Move arg4 pointers Utils::Hook::Set(0x4F7630, 0x12DC + (sizeof(bg_sharedAmmoCaps) - (1200 * 4))); + + + // Reallocate G_ModelIndex + Utils::Hook::Set(0x420654 + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x43BCE4 + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x44F27B + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x479087 + 1, G_ModelIndexReallocated); + Utils::Hook::Set(0x48069D + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x48F088 + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x4F457C + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x5FC762 + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x5FC7BE + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x44F256 + 2, G_MODELINDEX_LIMIT); + } void* Weapon::LoadNoneWeaponHook() @@ -636,6 +651,10 @@ namespace Components Utils::Hook::Nop(0x408230, 5); // is asset default Utils::Hook::Nop(0x40823A, 2); // jump + // Automatically register weapons, even if the level is not loading + Utils::Hook::Nop(0x49E547, 2); + Utils::Hook::Set(0x44F240, 0xEB); + // Skip double loading for fs_game Utils::Hook::Set(0x4081FD, 0xEB); diff --git a/src/Components/Modules/Weapon.hpp b/src/Components/Modules/Weapon.hpp index 30af745b..4da87eb2 100644 --- a/src/Components/Modules/Weapon.hpp +++ b/src/Components/Modules/Weapon.hpp @@ -4,6 +4,9 @@ // Was 1200 before #define WEAPON_LIMIT 2400 #define MAX_CONFIGSTRINGS (4139 - 1200 + WEAPON_LIMIT) +#define G_MODELINDEX_LIMIT (526 + WEAPON_LIMIT - 1200) + +#define G_MODELINDEX_HAS_BEEN_REALLOCATED namespace Components { @@ -11,6 +14,7 @@ namespace Components { public: Weapon(); + static Game::XModel* G_ModelIndexReallocated[G_MODELINDEX_LIMIT]; private: static const Game::dvar_t* BGWeaponOffHandFix; diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index 3bce1cb6..d045a34a 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -30,7 +30,7 @@ namespace Components buffer(0xC800000), zoneName(name), dataMap("zone_source/" + name + ".csv"), - branding{nullptr}, + branding{ nullptr }, assetDepth(0), iw4ofApi(getIW4OfApiParams()) { @@ -207,7 +207,7 @@ namespace Components { return false; } - + } return true; @@ -354,8 +354,8 @@ namespace Components Game::XAssetHeader ZoneBuilder::Zone::saveSubAsset(Game::XAssetType type, void* ptr) { - Game::XAssetHeader header { ptr }; - Game::XAsset asset { type, header }; + Game::XAssetHeader header{ ptr }; + Game::XAsset asset{ type, header }; std::string name = Game::DB_GetXAssetName(&asset); int assetIndex = this->findAsset(type, name); @@ -428,7 +428,7 @@ namespace Components zoneBuffer.insert(zoneBuffer.begin(), static_cast(Utils::Cryptography::Rand::GenerateInt())); char lastByte = 0; - for(unsigned int i = 0; i < zoneBuffer.size(); ++i ) + for (unsigned int i = 0; i < zoneBuffer.size(); ++i) { char oldLastByte = lastByte; lastByte = zoneBuffer[i]; @@ -442,9 +442,9 @@ namespace Components Utils::IO::WriteFile("uncompressed", zoneBuffer); const auto _0 = gsl::finally([] - { - Utils::IO::RemoveFile("uncompressed"); - }); + { + Utils::IO::RemoveFile("uncompressed"); + }); zoneBuffer = Utils::Compression::ZLib::Compress(zoneBuffer); outBuffer.append(zoneBuffer); @@ -536,11 +536,11 @@ namespace Components void ZoneBuilder::Zone::addBranding() { const auto now = std::chrono::system_clock::now(); - + auto zoneBranding = std::format("Built using the IW4x ZoneBuilder! {:%d-%m-%Y %H:%M:%OS}", now); auto brandingLen = zoneBranding.size(); // + 1 is added by the save code - this->branding = {this->zoneName.data(), 0, static_cast(brandingLen), getAllocator()->duplicateString(zoneBranding)}; + this->branding = { this->zoneName.data(), 0, static_cast(brandingLen), getAllocator()->duplicateString(zoneBranding) }; if (this->findAsset(Game::ASSET_TYPE_RAWFILE, this->branding.name) != -1) { @@ -647,7 +647,7 @@ namespace Components void ZoneBuilder::Zone::addRawAsset(Game::XAssetType type, void* ptr) { - this->loadedAssets.push_back({type, {ptr}}); + this->loadedAssets.push_back({ type, {ptr} }); } // Remap a scriptString to it's corresponding value in the local scriptString table. @@ -783,38 +783,46 @@ namespace Components params.write_only_once = true; params.find_other_asset = [this](int type, const std::string& name) -> void* - { - return AssetHandler::FindAssetForZone(static_cast(type), name, this).data; - }; + { + return AssetHandler::FindAssetForZone(static_cast(type), name, this).data; + }; + + params.request_mark_asset = [this](int type, void* data) -> void + { + Game::XAsset asset {static_cast(type), {data}}; + + AssetHandler::ZoneMark(asset, this); + this->addRawAsset(static_cast(type), data); + }; params.fs_read_file = [](const std::string& filename) -> std::string - { - auto file = FileSystem::File(filename); - if (file.exists()) { - return file.getBuffer(); - } + auto file = FileSystem::File(filename); + if (file.exists()) + { + return file.getBuffer(); + } - return {}; - }; + return {}; + }; params.store_in_string_table = [](const std::string& text) -> unsigned int - { - return Game::SL_GetString(text.data(), 0); - }; + { + return Game::SL_GetString(text.data(), 0); + }; params.print = [](iw4of::params_t::print_type t, const std::string& message) -> void - { - switch (t) { - case iw4of::params_t::P_ERR: - Logger::Error(Game::ERR_FATAL, "{}", message); - break; - case iw4of::params_t::P_WARN: - Logger::Print("{}", message); - break; - } - }; + switch (t) + { + case iw4of::params_t::P_ERR: + Logger::Error(Game::ERR_FATAL, "{}", message); + break; + case iw4of::params_t::P_WARN: + Logger::Print("{}", message); + break; + } + }; if (*Game::fs_basepath && *Game::fs_gameDirVar) { @@ -828,7 +836,7 @@ namespace Components return params; } - int ZoneBuilder::StoreTexture(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image) + int ZoneBuilder::StoreTexture(Game::GfxImageLoadDef** loadDef, Game::GfxImage* image) { size_t size = 16 + (*loadDef)->resourceSize; void* data = Utils::Memory::GetAllocator()->allocate(size); @@ -869,7 +877,7 @@ namespace Components // block the main thread from doing anything "main thread" specific while // the other thread is interrupting - + //while (ZoneBuilder::mainThreadInterrupted) std::this_thread::sleep_for(100ms); // normal functionality @@ -928,7 +936,7 @@ namespace Components Console::ShowAsyncConsole(); Utils::Hook::Call(0x43D140)(); // Com_EventLoop } - + Utils::Hook::Call(0x502580)(static_cast(__rdtsc())); // Netchan_Init Utils::Hook::Call(0x429080)(); // FS_InitFileSystem @@ -961,7 +969,7 @@ namespace Components Utils::Hook::Call(0x4454C0)(); // Item_SetupKeywordHash (for loading menus) Utils::Hook::Call(0x501BC0)(); // Menu_SetupKeywordHash (for loading menus) Utils::Hook::Call(0x4A1280)(); // something related to uiInfoArray - + Utils::Hook::Call(0x464A90)(GetCommandLineA()); // Com_ParseCommandLine Utils::Hook::Call(0x60C3D0)(); // Com_AddStartupCommands @@ -1028,7 +1036,7 @@ namespace Components if (code == Game::ERR_FATAL) { ExitProcess(1); - } + } } __declspec(naked) void ZoneBuilder::SoftErrorAssetOverflow() @@ -1055,19 +1063,19 @@ namespace Components const char* ret = "default"; Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [techniqueName, &ret](Game::XAssetEntry* entry) - { - if (!replacementFound) { - Game::XAssetHeader header = entry->asset.header; - std::string name = techniqueName; - if (name[0] == ',') name = name.substr(1); - if (name == header.material->techniqueSet->name) + if (!replacementFound) { - ret = header.material->info.name; - replacementFound = true; + Game::XAssetHeader header = entry->asset.header; + std::string name = techniqueName; + if (name[0] == ',') name = name.substr(1); + if (name == header.material->techniqueSet->name) + { + ret = header.material->info.name; + replacementFound = true; + } } - } - }, false); + }, false); if (replacementFound) return ret; return ""; @@ -1094,7 +1102,7 @@ namespace Components { file = Game::Sys_CreateFile("zone\\zonebuilder\\", filename); } - + return file; } @@ -1105,44 +1113,39 @@ namespace Components params.write_only_once = true; params.find_other_asset = [](int type, const std::string& name) -> void* - { - if (ZoneBuilder::DumpingZone.empty()) { + // Do not deadlock the DB return Game::DB_FindXAssetHeader(static_cast(type), name.data()).data; - } - - // Do not deadlock the DB - return nullptr; - }; + }; params.fs_read_file = [](const std::string& filename) -> std::string - { - auto file = FileSystem::File(filename); - if (file.exists()) { - return file.getBuffer(); - } + auto file = FileSystem::File(filename); + if (file.exists()) + { + return file.getBuffer(); + } - return {}; - }; + return {}; + }; params.get_from_string_table = [](const unsigned int& id) -> std::string - { - return Game::SL_ConvertToString(static_cast(id)); - }; + { + return Game::SL_ConvertToString(static_cast(id)); + }; params.print = [](iw4of::params_t::print_type t, const std::string& message) -> void - { - switch (t) { - case iw4of::params_t::P_ERR: - Logger::Error(Game::ERR_FATAL, "{}", message); - break; - case iw4of::params_t::P_WARN: - Logger::Print("{}", message); - break; - } - }; + switch (t) + { + case iw4of::params_t::P_ERR: + Logger::Error(Game::ERR_FATAL, "{}", message); + break; + case iw4of::params_t::P_WARN: + Logger::Print("{}", message); + break; + } + }; return params; } @@ -1217,22 +1220,22 @@ namespace Components // The domain func of fs_game should NOT be used to set the value itself! // Hook should be moved further!! Utils::Hook::Set(0x643203, [](Game::dvar_t* dvar, Game::DvarValue value) - { - // Call original FS_GameDirDomainFunc - int result = Utils::Hook::Call(0x642FC0)(dvar, value); - - if (result) { - if (std::strcmp(value.string, dvar->current.string) != 0) - { - // CopyStringInternal - dvar->current.string = Game::CopyStringInternal(value.string); - Game::FS_Restart(0, 0); - } - } + // Call original FS_GameDirDomainFunc + int result = Utils::Hook::Call(0x642FC0)(dvar, value); - return result; - }); + if (result) + { + if (std::strcmp(value.string, dvar->current.string) != 0) + { + // CopyStringInternal + dvar->current.string = Game::CopyStringInternal(value.string); + Game::FS_Restart(0, 0); + } + } + + return result; + }); // set new entry point Utils::Hook(0x4513DA, ZoneBuilder::EntryPoint, HOOK_JUMP).install()->quick(); @@ -1248,308 +1251,385 @@ namespace Components // Don't exec startup config in fs_restart Utils::Hook::Set(0x461B48, 0xEB); - + // remove overriding asset messages Utils::Hook::Nop(0x5BC74E, 5); // don't remap techsets Utils::Hook::Nop(0x5BC791, 5); - AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& name, bool* /*restrict*/) - { - if (!ZoneBuilder::TraceZone.empty() && ZoneBuilder::TraceZone == FastFiles::Current()) + AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader /* asset*/, const std::string& name, bool* /*restrict*/) { - ZoneBuilder::TraceAssets.emplace_back(std::make_pair(type, name)); -#ifdef _DEBUG - OutputDebugStringA(Utils::String::Format("%s\n", name)); -#endif - } - - if (!ZoneBuilder::DumpingZone.empty()) - { - if (ExporterAPI.is_type_supported(type) && name[0] != ',') + if (!ZoneBuilder::TraceZone.empty() && ZoneBuilder::TraceZone == FastFiles::Current()) { - ExporterAPI.write(type, asset.data); - Components::Logger::Print("."); + ZoneBuilder::TraceAssets.emplace_back(std::make_pair(type, name)); +#ifdef _DEBUG + OutputDebugStringA(Utils::String::Format("%s\n", name)); +#endif } - } - }); + }); Command::Add("dumpzone", [](const Command::Params* params) - { - if (params->size() < 2) return; - - std::string zone = params->get(1); - ZoneBuilder::DumpingZone = zone; - ZoneBuilder::RefreshExporterWorkDirectory(); - - Game::XZoneInfo info; - info.name = zone.data(); - info.allocFlags = Game::DB_ZONE_MOD; - info.freeFlags = 0; - - Logger::Print("Dumping zone '{}'...\n", zone); - - Game::DB_LoadXAssets(&info, 1, true); - AssetHandler::FindOriginalAsset(Game::ASSET_TYPE_RAWFILE, zone.data()); // Lock until zone is loaded - - Logger::Print("Unloading zone '{}'...\n", zone); - info.freeFlags = Game::DB_ZONE_MOD; - info.allocFlags = 0; - info.name = nullptr; - - Game::DB_LoadXAssets(&info, 1, true); - AssetHandler::FindOriginalAsset(Game::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded - Logger::Print("Zone '{}' dumped", ZoneBuilder::DumpingZone); - ZoneBuilder::DumpingZone = std::string(); - }); - - Command::Add("verifyzone", [](const Command::Params* params) - { - if (params->size() < 2) return; - - std::string zone = params->get(1); - - ZoneBuilder::BeginAssetTrace(zone); - - Game::XZoneInfo info; - info.name = zone.data(); - info.allocFlags = Game::DB_ZONE_MOD; - info.freeFlags = 0; - - Logger::Print("Loading zone '{}'...\n", zone); - - Game::DB_LoadXAssets(&info, 1, true); - AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, zone.data()); // Lock until zone is loaded - - auto assets = ZoneBuilder::EndAssetTrace(); - - Logger::Print("Unloading zone '{}'...\n", zone); - info.freeFlags = Game::DB_ZONE_MOD; - info.allocFlags = 0; - info.name = nullptr; - - Game::DB_LoadXAssets(&info, 1, true); - AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded - - Logger::Print("Zone '{}' loaded with {} assets:\n", zone, assets.size()); - - int count = 0; - for (auto i = assets.begin(); i != assets.end(); ++i, ++count) { - Logger::Print(" {}: {}: {}\n", count, Game::DB_GetXAssetTypeName(i->first), i->second); - } + if (params->size() < 2) return; - Logger::Print("\n"); - }); + std::string zone = params->get(1); + ZoneBuilder::DumpingZone = zone; + ZoneBuilder::RefreshExporterWorkDirectory(); - Command::Add("buildzone", [](const Command::Params* params) - { - if (params->size() < 2) return; + Game::XZoneInfo info; + info.name = zone.data(); + info.allocFlags = Game::DB_ZONE_MOD; + info.freeFlags = 0; - std::string zoneName = params->get(1); - Logger::Print("Building zone '{}'...\n", zoneName); + Logger::Print("Loading zone '{}'...\n", zone); - Zone(zoneName).build(); - }); - - Command::Add("buildall", []() - { - auto path = std::format("{}\\zone_source", (*Game::fs_basepath)->current.string); - auto zoneSources = FileSystem::GetSysFileList(path, "csv", false); - - for (auto source : zoneSources) - { - if (Utils::String::EndsWith(source, ".csv")) { - source = source.substr(0, source.find(".csv")); + struct asset_t + { + Game::XAssetType type; + char name[128]; + }; + + std::vector assets{}; + const auto handle = AssetHandler::OnLoad([&](Game::XAssetType type, Game::XAssetHeader asset, const std::string& name, bool* /*restrict*/) + { + if (ExporterAPI.is_type_supported(type) && name[0] != ',') + { + Game::XAsset xasset = { type, asset }; + asset_t assetIdentifier{}; + + // Fetch name + const auto assetName = Game::DB_GetXAssetName(&xasset); + std::memcpy(assetIdentifier.name, assetName, strnlen(assetName, ARRAYSIZE(assetIdentifier.name) - 1)); + assetIdentifier.name[ARRAYSIZE(assetIdentifier.name) - 1] = '\x00'; + + assetIdentifier.type = type; + + assets.push_back(assetIdentifier); + } + }); + + Game::DB_LoadXAssets(&info, 1, true); + AssetHandler::FindOriginalAsset(Game::ASSET_TYPE_RAWFILE, zone.data()); // Lock until zone is loaded + + Logger::Print("Dumping zone '{}'...\n", zone); + handle(); // Release + for (const auto& asset : assets) + { + const auto assetHeader = Game::DB_FindXAssetHeader(asset.type, asset.name); + if (assetHeader.data) + { + ExporterAPI.write(asset.type, assetHeader.data); + } + else + { + Logger::Warning(Game::conChannel_t::CON_CHANNEL_ERROR, "Asset {} has disappeared while dumping!", asset.name); + } + } } - Command::Execute(std::format("buildzone {}", source), true); - } - }); + Logger::Print("Unloading zone '{}'...\n", zone); + info.freeFlags = Game::DB_ZONE_MOD; + info.allocFlags = 0; + info.name = nullptr; + + Game::DB_LoadXAssets(&info, 1, true); + AssetHandler::FindOriginalAsset(Game::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded + Logger::Print("Zone '{}' dumped", ZoneBuilder::DumpingZone); + ZoneBuilder::DumpingZone = std::string(); + }); + + Command::Add("verifyzone", [](const Command::Params* params) + { + if (params->size() < 2) return; + + std::string zone = params->get(1); + + ZoneBuilder::BeginAssetTrace(zone); + + Game::XZoneInfo info; + info.name = zone.data(); + info.allocFlags = Game::DB_ZONE_MOD; + info.freeFlags = 0; + + Logger::Print("Loading zone '{}'...\n", zone); + + Game::DB_LoadXAssets(&info, 1, true); + AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, zone.data()); // Lock until zone is loaded + + auto assets = ZoneBuilder::EndAssetTrace(); + + Logger::Print("Unloading zone '{}'...\n", zone); + info.freeFlags = Game::DB_ZONE_MOD; + info.allocFlags = 0; + info.name = nullptr; + + Game::DB_LoadXAssets(&info, 1, true); + AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded + + Logger::Print("Zone '{}' loaded with {} assets:\n", zone, assets.size()); + + int count = 0; + for (auto i = assets.begin(); i != assets.end(); ++i, ++count) + { + Logger::Print(" {}: {}: {}\n", count, Game::DB_GetXAssetTypeName(i->first), i->second); + } + + Logger::Print("\n"); + }); + + Command::Add("buildzone", [](const Command::Params* params) + { + if (params->size() < 2) return; + + std::string zoneName = params->get(1); + Logger::Print("Building zone '{}'...\n", zoneName); + + Zone(zoneName).build(); + }); + + Command::Add("buildall", []() + { + auto path = std::format("{}\\zone_source", (*Game::fs_basepath)->current.string); + auto zoneSources = FileSystem::GetSysFileList(path, "csv", false); + + for (auto source : zoneSources) + { + if (Utils::String::EndsWith(source, ".csv")) + { + source = source.substr(0, source.find(".csv")); + } + + Command::Execute(std::format("buildzone {}", source), true); + } + }); static std::set curTechsets_list; static std::set techsets_list; AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader, const std::string& name, bool*) - { - if (type == Game::ASSET_TYPE_TECHNIQUE_SET) { - if (name[0] == ',') return; // skip techsets from common_mp - if (techsets_list.find(name) == techsets_list.end()) + if (type == Game::ASSET_TYPE_TECHNIQUE_SET) { - curTechsets_list.emplace(name); - techsets_list.emplace(name); + if (name[0] == ',') return; // skip techsets from common_mp + if (techsets_list.find(name) == techsets_list.end()) + { + curTechsets_list.emplace(name); + techsets_list.emplace(name); + } } - } - }); + }); AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, [[maybe_unused]] const std::string& name, [[maybe_unused]] bool* restrict) - { - if (type != Game::ASSET_TYPE_SOUND) { - return; - } - - auto sound = asset.sound; - - for (size_t i = 0; i < sound->count; i++) - { - auto thisSound = sound->head[i]; - - if (thisSound.soundFile->type == Game::SAT_LOADED) + if (type != Game::ASSET_TYPE_SOUND) { - if (thisSound.soundFile->u.loadSnd->sound.data == nullptr) + return; + } + + auto sound = asset.sound; + + for (size_t i = 0; i < sound->count; i++) + { + auto thisSound = sound->head[i]; + + if (thisSound.soundFile->type == Game::SAT_LOADED) { - // ouch - // This should never happen and will cause a memory leak - // Let's change it to a streamed sound instead - thisSound.soundFile->type = Game::SAT_STREAMED; + if (thisSound.soundFile->u.loadSnd->sound.data == nullptr) + { + // ouch + // This should never happen and will cause a memory leak + // Let's change it to a streamed sound instead + thisSound.soundFile->type = Game::SAT_STREAMED; - auto virtualPath = std::filesystem::path(thisSound.soundFile->u.loadSnd->name); + auto virtualPath = std::filesystem::path(thisSound.soundFile->u.loadSnd->name); - thisSound.soundFile->u.streamSnd.filename.info.raw.name = Utils::Memory::DuplicateString(virtualPath.filename().string()); + thisSound.soundFile->u.streamSnd.filename.info.raw.name = Utils::Memory::DuplicateString(virtualPath.filename().string()); - auto dir = virtualPath.remove_filename().string(); - dir = dir.substr(0, dir.size() - 1); // remove / - thisSound.soundFile->u.streamSnd.filename.info.raw.dir = Utils::Memory::DuplicateString(dir); + auto dir = virtualPath.remove_filename().string(); + dir = dir.substr(0, dir.size() - 1); // remove / + thisSound.soundFile->u.streamSnd.filename.info.raw.dir = Utils::Memory::DuplicateString(dir); + } } } - } - }); + }); Command::Add("buildtechsets", [](const Command::Params*) - { - Utils::IO::CreateDir("zone_source/techsets"); - Utils::IO::CreateDir("zone/techsets"); - - std::string csvStr; - - const auto dir = std::format("zone/{}", Game::Win_GetLanguage()); - auto fileList = Utils::IO::ListFiles(dir, false); - for (const auto& entry : fileList) { - auto zone = entry.path().string(); - Utils::String::Replace(zone, Utils::String::VA("zone/%s/", Game::Win_GetLanguage()), ""); - Utils::String::Replace(zone, ".ff", ""); + Utils::IO::CreateDir("zone_source/techsets"); + Utils::IO::CreateDir("zone/techsets"); - if (Utils::IO::FileExists("zone/techsets/" + zone + "_techsets.ff")) + std::string csvStr; + + const auto dir = std::format("zone/{}", Game::Win_GetLanguage()); + auto fileList = Utils::IO::ListFiles(dir, false); + for (const auto& entry : fileList) { - Logger::Print("Skipping previously generated zone {}\n", zone); - continue; - } + auto zone = entry.path().string(); + Utils::String::Replace(zone, Utils::String::VA("zone/%s/", Game::Win_GetLanguage()), ""); + Utils::String::Replace(zone, ".ff", ""); - if (zone.find("_load") != std::string::npos) - { - Logger::Print("Skipping loadscreen zone {}\n", zone); - continue; - } + if (Utils::IO::FileExists("zone/techsets/" + zone + "_techsets.ff")) + { + Logger::Print("Skipping previously generated zone {}\n", zone); + continue; + } - if (Game::DB_IsZoneLoaded(zone.c_str()) || !FastFiles::Exists(zone)) - { - continue; - } + if (zone.find("_load") != std::string::npos) + { + Logger::Print("Skipping loadscreen zone {}\n", zone); + continue; + } - if (zone[0] == '.') continue; // fucking mac dotfiles + if (Game::DB_IsZoneLoaded(zone.c_str()) || !FastFiles::Exists(zone)) + { + continue; + } - curTechsets_list.clear(); // clear from last run + if (zone[0] == '.') continue; // fucking mac dotfiles - // load the zone - Game::XZoneInfo info; - info.name = zone.c_str(); - info.allocFlags = Game::DB_ZONE_MOD; - info.freeFlags = 0x0; - Game::DB_LoadXAssets(&info, 1, 0); + curTechsets_list.clear(); // clear from last run - while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(100ms); // wait till its fully loaded + // load the zone + Game::XZoneInfo info; + info.name = zone.c_str(); + info.allocFlags = Game::DB_ZONE_MOD; + info.freeFlags = 0x0; + Game::DB_LoadXAssets(&info, 1, 0); - if (curTechsets_list.empty()) - { - Logger::Print("Skipping empty zone {}\n", zone); - // unload zone + while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(100ms); // wait till its fully loaded + + if (curTechsets_list.empty()) + { + Logger::Print("Skipping empty zone {}\n", zone); + // unload zone + info.name = nullptr; + info.allocFlags = 0x0; + info.freeFlags = Game::DB_ZONE_MOD; + Game::DB_LoadXAssets(&info, 1, true); + continue; + } + + // ok so we're just gonna use the materials because they will use the techsets + csvStr.clear(); + for (auto tech : curTechsets_list) + { + std::string mat = ZoneBuilder::FindMaterialByTechnique(tech); + if (mat.length() == 0) + { + csvStr.append("techset," + tech + "\n"); + } + else + { + csvStr.append("material," + mat + "\n"); + } + } + + // save csv + Utils::IO::WriteFile("zone_source/techsets/" + zone + "_techsets.csv", csvStr); + + // build the techset zone + std::string zoneName = "techsets/" + zone + "_techsets"; + Logger::Print("Building zone '{}'...\n", zoneName); + Zone(zoneName).build(); + + // unload original zone info.name = nullptr; info.allocFlags = 0x0; info.freeFlags = Game::DB_ZONE_MOD; Game::DB_LoadXAssets(&info, 1, true); - continue; + + while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); // wait till its fully loaded } - // ok so we're just gonna use the materials because they will use the techsets - csvStr.clear(); - for (auto tech : curTechsets_list) - { - std::string mat = ZoneBuilder::FindMaterialByTechnique(tech); - if (mat.length() == 0) + curTechsets_list.clear(); + techsets_list.clear(); + + Game::DB_EnumXAssets(Game::ASSET_TYPE_TECHNIQUE_SET, [](Game::XAssetHeader header, void*) { - csvStr.append("techset," + tech + "\n"); + curTechsets_list.emplace(header.techniqueSet->name); + techsets_list.emplace(header.techniqueSet->name); + }, nullptr, false); + + // HACK: set language to 'techsets' to load from that dir + const char* language = Utils::Hook::Get(0x649E740); + Utils::Hook::Set(0x649E740, "techsets"); + + // load generated techset fastfiles + auto list = Utils::IO::ListFiles("zone/techsets", false); + int i = 0; + int subCount = 0; + for (const auto& entry : list) + { + auto it = entry.path().string(); + + Utils::String::Replace(it, "zone/techsets/", ""); + Utils::String::Replace(it, ".ff", ""); + + if (it.find("_techsets") == std::string::npos) continue; // skip files we didn't generate for this + + if (!Game::DB_IsZoneLoaded(it.data())) + { + Game::XZoneInfo info; + info.name = it.data(); + info.allocFlags = Game::DB_ZONE_MOD; + info.freeFlags = 0; + + Game::DB_LoadXAssets(&info, 1, 0); + while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); // wait till its fully loaded } else { - csvStr.append("material," + mat + "\n"); + Logger::Print("Zone '{}' already loaded\n", it); } + + if (i == 20) // cap at 20 just to be safe + { + // create csv with the techsets in it + csvStr.clear(); + for (auto tech : curTechsets_list) + { + std::string mat = ZoneBuilder::FindMaterialByTechnique(tech); + if (mat.length() == 0) + { + csvStr.append("techset," + tech + "\n"); + } + else + { + csvStr.append("material," + mat + "\n"); + } + } + + std::string tempZoneFile = Utils::String::VA("zone_source/techsets/techsets%d.csv", subCount); + std::string tempZone = Utils::String::VA("techsets/techsets%d", subCount); + + Utils::IO::WriteFile(tempZoneFile, csvStr); + + Logger::Print("Building zone '{}'...\n", tempZone); + Zone(tempZone).build(); + + // unload all zones + Game::XZoneInfo info; + info.name = nullptr; + info.allocFlags = 0x0; + info.freeFlags = Game::DB_ZONE_MOD; + Game::DB_LoadXAssets(&info, 1, true); + + Utils::Hook::Set(0x649E740, "techsets"); + + i = 0; + subCount++; + curTechsets_list.clear(); + techsets_list.clear(); + } + + i++; } - // save csv - Utils::IO::WriteFile("zone_source/techsets/" + zone + "_techsets.csv", csvStr); - - // build the techset zone - std::string zoneName = "techsets/" + zone + "_techsets"; - Logger::Print("Building zone '{}'...\n", zoneName); - Zone(zoneName).build(); - - // unload original zone - info.name = nullptr; - info.allocFlags = 0x0; - info.freeFlags = Game::DB_ZONE_MOD; - Game::DB_LoadXAssets(&info, 1, true); - - while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); // wait till its fully loaded - } - - curTechsets_list.clear(); - techsets_list.clear(); - - Game::DB_EnumXAssets(Game::ASSET_TYPE_TECHNIQUE_SET, [](Game::XAssetHeader header, void*) - { - curTechsets_list.emplace(header.techniqueSet->name); - techsets_list.emplace(header.techniqueSet->name); - }, nullptr, false); - - // HACK: set language to 'techsets' to load from that dir - const char* language = Utils::Hook::Get(0x649E740); - Utils::Hook::Set(0x649E740, "techsets"); - - // load generated techset fastfiles - auto list = Utils::IO::ListFiles("zone/techsets", false); - int i = 0; - int subCount = 0; - for (const auto& entry : list) - { - auto it = entry.path().string(); - - Utils::String::Replace(it, "zone/techsets/", ""); - Utils::String::Replace(it, ".ff", ""); - - if (it.find("_techsets") == std::string::npos) continue; // skip files we didn't generate for this - - if (!Game::DB_IsZoneLoaded(it.data())) - { - Game::XZoneInfo info; - info.name = it.data(); - info.allocFlags = Game::DB_ZONE_MOD; - info.freeFlags = 0; - - Game::DB_LoadXAssets(&info, 1, 0); - while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); // wait till its fully loaded - } - else - { - Logger::Print("Zone '{}' already loaded\n", it); - } - - if (i == 20) // cap at 20 just to be safe + // last iteration + if (i != 0) { // create csv with the techsets in it csvStr.clear(); @@ -1558,6 +1638,7 @@ namespace Components std::string mat = ZoneBuilder::FindMaterialByTechnique(tech); if (mat.length() == 0) { + Logger::Print("Couldn't find a material for techset {}. Sort Keys will be incorrect.\n", tech); csvStr.append("techset," + tech + "\n"); } else @@ -1581,28 +1662,36 @@ namespace Components info.freeFlags = Game::DB_ZONE_MOD; Game::DB_LoadXAssets(&info, 1, true); - Utils::Hook::Set(0x649E740, "techsets"); - - i = 0; subCount++; - curTechsets_list.clear(); - techsets_list.clear(); } - i++; - } + // build final techsets fastfile + if (subCount > 24) + { + Logger::Error(Game::ERR_DROP, "How did you have 576 fastfiles?\n"); + } + + curTechsets_list.clear(); + techsets_list.clear(); + + for (int j = 0; j < subCount; ++j) + { + Game::XZoneInfo info; + info.name = Utils::String::VA("techsets%d", j); + info.allocFlags = Game::DB_ZONE_MOD; + info.freeFlags = 0; + + Game::DB_LoadXAssets(&info, 1, 0); + while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); // wait till its fully loaded + } - // last iteration - if (i != 0) - { // create csv with the techsets in it csvStr.clear(); - for (auto tech : curTechsets_list) + for (const auto& tech : curTechsets_list) { - std::string mat = ZoneBuilder::FindMaterialByTechnique(tech); + auto mat = ZoneBuilder::FindMaterialByTechnique(tech); if (mat.length() == 0) { - Logger::Print("Couldn't find a material for techset {}. Sort Keys will be incorrect.\n", tech); csvStr.append("techset," + tech + "\n"); } else @@ -1611,115 +1700,62 @@ namespace Components } } - std::string tempZoneFile = Utils::String::VA("zone_source/techsets/techsets%d.csv", subCount); - std::string tempZone = Utils::String::VA("techsets/techsets%d", subCount); + Utils::IO::WriteFile("zone_source/techsets/techsets.csv", csvStr); - Utils::IO::WriteFile(tempZoneFile, csvStr); + // set language back + Utils::Hook::Set(0x649E740, language); - Logger::Print("Building zone '{}'...\n", tempZone); - Zone(tempZone).build(); + Logger::Print("Building zone 'techsets/techsets'...\n"); + Zone("techsets/techsets").build(); + }); - // unload all zones + Command::Add("listassets", [](const Command::Params* params) + { + if (params->size() < 2) return; + Game::XAssetType type = Game::DB_GetXAssetNameType(params->get(1)); + + if (type != Game::XAssetType::ASSET_TYPE_INVALID) + { + Game::DB_EnumXAssets(type, [](Game::XAssetHeader header, void* data) + { + Game::XAsset asset = { *reinterpret_cast(data), header }; + Logger::Print("{}\n", Game::DB_GetXAssetName(&asset)); + }, &type, false); + } + }); + + Command::Add("loadtempzone", [](const Command::Params* params) + { + if (params->size() < 2) return; + + if (FastFiles::Exists(params->get(1))) + { + Game::XZoneInfo info; + info.name = params->get(1); + info.allocFlags = 0x80; + info.freeFlags = 0x0; + Game::DB_LoadXAssets(&info, 1, 0); + } + }); + + Command::Add("unloadtempzones", [](const Command::Params*) + { Game::XZoneInfo info; info.name = nullptr; info.allocFlags = 0x0; - info.freeFlags = Game::DB_ZONE_MOD; + info.freeFlags = 0x80; Game::DB_LoadXAssets(&info, 1, true); - - subCount++; - } - - // build final techsets fastfile - if (subCount > 24) - { - Logger::Error(Game::ERR_DROP, "How did you have 576 fastfiles?\n"); - } - - curTechsets_list.clear(); - techsets_list.clear(); - - for (int j = 0; j < subCount; ++j) - { - Game::XZoneInfo info; - info.name = Utils::String::VA("techsets%d", j); - info.allocFlags = Game::DB_ZONE_MOD; - info.freeFlags = 0; - - Game::DB_LoadXAssets(&info, 1, 0); - while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); // wait till its fully loaded - } - - // create csv with the techsets in it - csvStr.clear(); - for (const auto& tech : curTechsets_list) - { - auto mat = ZoneBuilder::FindMaterialByTechnique(tech); - if (mat.length() == 0) - { - csvStr.append("techset," + tech + "\n"); - } - else - { - csvStr.append("material," + mat + "\n"); - } - } - - Utils::IO::WriteFile("zone_source/techsets/techsets.csv", csvStr); - - // set language back - Utils::Hook::Set(0x649E740, language); - - Logger::Print("Building zone 'techsets/techsets'...\n"); - Zone("techsets/techsets").build(); - }); - - Command::Add("listassets", [](const Command::Params* params) - { - if (params->size() < 2) return; - Game::XAssetType type = Game::DB_GetXAssetNameType(params->get(1)); - - if (type != Game::XAssetType::ASSET_TYPE_INVALID) - { - Game::DB_EnumXAssets(type, [](Game::XAssetHeader header, void* data) - { - Game::XAsset asset = { *reinterpret_cast(data), header }; - Logger::Print("{}\n", Game::DB_GetXAssetName(&asset)); - }, &type, false); - } - }); - - Command::Add("loadtempzone", [](const Command::Params* params) - { - if (params->size() < 2) return; - - if (FastFiles::Exists(params->get(1))) - { - Game::XZoneInfo info; - info.name = params->get(1); - info.allocFlags = 0x80; - info.freeFlags = 0x0; - Game::DB_LoadXAssets(&info, 1, 0); - } - }); - - Command::Add("unloadtempzones", [](const Command::Params*) - { - Game::XZoneInfo info; - info.name = nullptr; - info.allocFlags = 0x0; - info.freeFlags = 0x80; - Game::DB_LoadXAssets(&info, 1, true); - AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded - }); + AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded + }); Command::Add("materialInfoDump", [](const Command::Params*) - { - Game::DB_EnumXAssets(Game::ASSET_TYPE_MATERIAL, [](Game::XAssetHeader header, void*) { - Logger::Print("{}: {:#X} {:#X} {:#X}\n", - header.material->info.name, header.material->info.sortKey & 0xFF, header.material->info.gameFlags & 0xFF, header.material->stateFlags & 0xFF); - }, nullptr, false); - }); + Game::DB_EnumXAssets(Game::ASSET_TYPE_MATERIAL, [](Game::XAssetHeader header, void*) + { + Logger::Print("{}: {:#X} {:#X} {:#X}\n", + header.material->info.name, header.material->info.sortKey & 0xFF, header.material->info.gameFlags & 0xFF, header.material->stateFlags & 0xFF); + }, nullptr, false); + }); } } diff --git a/src/Utils/Utils.hpp b/src/Utils/Utils.hpp index d75e1130..eba34b59 100644 --- a/src/Utils/Utils.hpp +++ b/src/Utils/Utils.hpp @@ -82,6 +82,31 @@ namespace Utils Utils::Merge(&this->slots, obj.getSlots()); } + void disconnect(const Slot slot) + { + std::lock_guard _(this->mutex); + + if (slot) + { + this->slots.erase( + std::remove_if( + this->slots.begin(), + this->slots.end(), + [&](std::function& a) + { + if (a.target() == slot.target()) + { + return true; + } + + return false; + } + + ), this->slots.end() + ); + } + } + void connect(const Slot slot) { std::lock_guard _(this->mutex); From a54337af9ba42612fde0e43496978bc29a8cebc3 Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Mon, 20 Nov 2023 00:55:26 +0100 Subject: [PATCH 3/6] Load tracerdef, allow checking for reallocated gmodelindex --- src/Components/Loader.cpp | 2 +- src/Components/Modules/AssetInterfaces/ITracerDef.cpp | 4 ++-- src/Components/Modules/AssetInterfaces/IWeapon.cpp | 5 ++++- .../Modules/AssetInterfaces/IXAnimParts.cpp | 8 ++++---- src/Components/Modules/ClientCommand.cpp | 11 ++++++++++- src/Components/Modules/Weapon.cpp | 4 +++- src/Components/Modules/Weapon.hpp | 4 ++-- src/Game/Structs.hpp | 4 ++-- 8 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index fc09bb18..4b50eadc 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -12,6 +12,7 @@ #include "Modules/Changelog.hpp" #include "Modules/Chat.hpp" #include "Modules/ClanTags.hpp" +#include "Modules/Weapon.hpp" // Load before for G_ModelIndex #include "Modules/ClientCommand.hpp" #include "Modules/ConnectProtocol.hpp" #include "Modules/Console.hpp" @@ -62,7 +63,6 @@ #include "Modules/VisionFile.hpp" #include "Modules/Voice.hpp" #include "Modules/Vote.hpp" -#include "Modules/Weapon.hpp" #include "Modules/Window.hpp" #include "Modules/BotLib/lPrecomp.hpp" diff --git a/src/Components/Modules/AssetInterfaces/ITracerDef.cpp b/src/Components/Modules/AssetInterfaces/ITracerDef.cpp index 98962180..7500b23b 100644 --- a/src/Components/Modules/AssetInterfaces/ITracerDef.cpp +++ b/src/Components/Modules/AssetInterfaces/ITracerDef.cpp @@ -3,9 +3,9 @@ namespace Assets { - void ITracerDef::load(Game::XAssetHeader* /*header*/, const std::string& /*name*/, Components::ZoneBuilder::Zone* /*builder*/) + void ITracerDef::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - // don't load from filesystem right now + header->tracerDef = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_TRACER, name); } void ITracerDef::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/AssetInterfaces/IWeapon.cpp b/src/Components/Modules/AssetInterfaces/IWeapon.cpp index e3b24bc1..a192662b 100644 --- a/src/Components/Modules/AssetInterfaces/IWeapon.cpp +++ b/src/Components/Modules/AssetInterfaces/IWeapon.cpp @@ -129,7 +129,10 @@ namespace Assets if (asset->weapDef->projIgnitionEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->projIgnitionEffect); if (asset->weapDef->turretOverheatEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->turretOverheatEffect); -#define LoadWeapSound(sound) if (asset->weapDef->##sound##) builder->loadAsset(Game::XAssetType::ASSET_TYPE_SOUND, asset->weapDef->##sound##) + + + // They are not subassets, because they don't get loaded automatically +#define LoadWeapSound(sound) if (asset->weapDef->##sound##) builder->loadAsset(Game::XAssetType::ASSET_TYPE_SOUND, asset->weapDef->##sound##, false) LoadWeapSound(pickupSound); LoadWeapSound(pickupSoundPlayer); diff --git a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp index e8b9860c..0a9bd565 100644 --- a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp +++ b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp @@ -14,7 +14,7 @@ namespace Assets if (asset->names) { - for (char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) + for (unsigned char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) { builder->addScriptString(asset->names[i]); } @@ -22,7 +22,7 @@ namespace Assets if (asset->notify) { - for (char i = 0; i < asset->notifyCount; ++i) + for (unsigned char i = 0; i < asset->notifyCount; ++i) { builder->addScriptString(asset->notify[i].name); } @@ -165,7 +165,7 @@ namespace Assets unsigned short* destTagnames = buffer->dest(); buffer->saveArray(asset->names, asset->boneCount[Game::PART_TYPE_ALL]); - for (char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) + for (unsigned char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) { builder->mapScriptString(destTagnames[i]); } @@ -181,7 +181,7 @@ namespace Assets Game::XAnimNotifyInfo* destNotetracks = buffer->dest(); buffer->saveArray(asset->notify, asset->notifyCount); - for (char i = 0; i < asset->notifyCount; ++i) + for (unsigned char i = 0; i < asset->notifyCount; ++i) { builder->mapScriptString(destNotetracks[i].name); } diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index f80f9eb8..f7c8f5ca 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -1,6 +1,8 @@ #include #include "ClientCommand.hpp" +#include "Weapon.hpp" + #include "GSC/Script.hpp" using namespace Utils::String; @@ -382,7 +384,14 @@ namespace Components Game::XModel* model = nullptr; if (ent->model) { - model = Game::G_GetModel(ent->model); + if (Components::Weapon::GModelIndexHasBeenReallocated) + { + model = Components::Weapon::G_ModelIndexReallocated[ent->model]; + } + else + { + model = Game::G_GetModel(ent->model); + } } Game::vec3_t point, angles; diff --git a/src/Components/Modules/Weapon.cpp b/src/Components/Modules/Weapon.cpp index b9eb33ab..5f3fd9f0 100644 --- a/src/Components/Modules/Weapon.cpp +++ b/src/Components/Modules/Weapon.cpp @@ -7,6 +7,7 @@ namespace Components { const Game::dvar_t* Weapon::BGWeaponOffHandFix; Game::XModel* Weapon::G_ModelIndexReallocated[G_MODELINDEX_LIMIT]; + bool Weapon::GModelIndexHasBeenReallocated; Game::WeaponCompleteDef* Weapon::LoadWeaponCompleteDef(const char* name) { @@ -447,7 +448,8 @@ namespace Components Utils::Hook::Set(0x5FC762 + 3, G_ModelIndexReallocated); Utils::Hook::Set(0x5FC7BE + 3, G_ModelIndexReallocated); Utils::Hook::Set(0x44F256 + 2, G_MODELINDEX_LIMIT); - + + GModelIndexHasBeenReallocated = true; } void* Weapon::LoadNoneWeaponHook() diff --git a/src/Components/Modules/Weapon.hpp b/src/Components/Modules/Weapon.hpp index 4da87eb2..cc92d59d 100644 --- a/src/Components/Modules/Weapon.hpp +++ b/src/Components/Modules/Weapon.hpp @@ -6,8 +6,6 @@ #define MAX_CONFIGSTRINGS (4139 - 1200 + WEAPON_LIMIT) #define G_MODELINDEX_LIMIT (526 + WEAPON_LIMIT - 1200) -#define G_MODELINDEX_HAS_BEEN_REALLOCATED - namespace Components { class Weapon : public Component @@ -16,6 +14,8 @@ namespace Components Weapon(); static Game::XModel* G_ModelIndexReallocated[G_MODELINDEX_LIMIT]; + static bool GModelIndexHasBeenReallocated; + private: static const Game::dvar_t* BGWeaponOffHandFix; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 4b72f36d..21842c63 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -1044,8 +1044,8 @@ namespace Game unsigned __int16 randomDataIntCount; unsigned __int16 numframes; char flags; - char boneCount[10]; - char notifyCount; + unsigned char boneCount[10]; + unsigned char notifyCount; char assetType; bool isDefault; unsigned int randomDataShortCount; From b7d36cfed9c0880156af4145cb574ababaecbace Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Wed, 22 Nov 2023 00:35:31 +0100 Subject: [PATCH 4/6] Prevent error in GetPrevSourcePos --- src/Components/Loader.cpp | 2 +- src/Components/Modules/GSC/ScriptError.cpp | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 4b50eadc..fc09bb18 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -12,7 +12,6 @@ #include "Modules/Changelog.hpp" #include "Modules/Chat.hpp" #include "Modules/ClanTags.hpp" -#include "Modules/Weapon.hpp" // Load before for G_ModelIndex #include "Modules/ClientCommand.hpp" #include "Modules/ConnectProtocol.hpp" #include "Modules/Console.hpp" @@ -63,6 +62,7 @@ #include "Modules/VisionFile.hpp" #include "Modules/Voice.hpp" #include "Modules/Vote.hpp" +#include "Modules/Weapon.hpp" #include "Modules/Window.hpp" #include "Modules/BotLib/lPrecomp.hpp" diff --git a/src/Components/Modules/GSC/ScriptError.cpp b/src/Components/Modules/GSC/ScriptError.cpp index 94943f5a..edf1795a 100644 --- a/src/Components/Modules/GSC/ScriptError.cpp +++ b/src/Components/Modules/GSC/ScriptError.cpp @@ -194,7 +194,16 @@ namespace Components::GSC unsigned int ScriptError::Scr_GetPrevSourcePos(const char* codePos, unsigned int index) { - return ScrParserGlob.sourcePosLookup[index + Scr_GetPrevSourcePosOpcodeLookup(codePos)->sourcePosIndex].sourcePos; + const auto prevIndex = Scr_GetPrevSourcePosOpcodeLookup(codePos)->sourcePosIndex; + const auto sPos = ScrParserGlob.sourcePosLookup[index + prevIndex].sourcePos; + + // I'm not sure why this is necessary - when given a valid codePos, sometimes + // the sourcePos ends up being a negative number (got a case where it was -2) + // which will output a giantic unsigned number and crash + // So we make sure it's not gonna happen here by clamping the number + const auto uPos = static_cast(std::max(0, static_cast(sPos))); + + return uPos; } Game::OpcodeLookup* ScriptError::Scr_GetPrevSourcePosOpcodeLookup(const char* codePos) @@ -326,7 +335,9 @@ namespace Components::GSC if (Game::scrVarPub->programBuffer && Scr_IsInOpcodeMemory(codePos)) { auto bufferIndex = Scr_GetSourceBuffer(codePos - 1); - Scr_PrintSourcePos(channel, ScrParserPub.sourceBufferLookup[bufferIndex].buf, ScrParserPub.sourceBufferLookup[bufferIndex].sourceBuf, Scr_GetPrevSourcePos(codePos - 1, index)); + const auto prevSourcePos = Scr_GetPrevSourcePos(codePos - 1, index); + + Scr_PrintSourcePos(channel, ScrParserPub.sourceBufferLookup[bufferIndex].buf, ScrParserPub.sourceBufferLookup[bufferIndex].sourceBuf, prevSourcePos); return; } } From 25c4c4d99fba02a79309c89e862028895c6e5fd2 Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Wed, 22 Nov 2023 00:45:51 +0100 Subject: [PATCH 5/6] Bump IW4OF --- deps/iw4-open-formats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/iw4-open-formats b/deps/iw4-open-formats index 45405b08..0baf2935 160000 --- a/deps/iw4-open-formats +++ b/deps/iw4-open-formats @@ -1 +1 @@ -Subproject commit 45405b08ed2afc0929cb332bfe4288b467ab0ed8 +Subproject commit 0baf29352008266683758170e9687c2e8ab95f13 From 3af216004c747297ef2dd05d1ac4b8e354b34fdb Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Sun, 26 Nov 2023 10:50:56 +0100 Subject: [PATCH 6/6] Bump GModelIndex limit even more --- src/Components/Modules/Weapon.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Components/Modules/Weapon.hpp b/src/Components/Modules/Weapon.hpp index cc92d59d..60c17c60 100644 --- a/src/Components/Modules/Weapon.hpp +++ b/src/Components/Modules/Weapon.hpp @@ -4,7 +4,11 @@ // Was 1200 before #define WEAPON_LIMIT 2400 #define MAX_CONFIGSTRINGS (4139 - 1200 + WEAPON_LIMIT) -#define G_MODELINDEX_LIMIT (526 + WEAPON_LIMIT - 1200) + +// Double the limit to allow loading of some heavy-duty MW3 maps +#define ADDITIONAL_GMODELS 512 + +#define G_MODELINDEX_LIMIT (512 + WEAPON_LIMIT - 1200 + ADDITIONAL_GMODELS) namespace Components {