diff --git a/src/Components/Modules/AssetHandler.cpp b/src/Components/Modules/AssetHandler.cpp index ae538b8b..b76def14 100644 --- a/src/Components/Modules/AssetHandler.cpp +++ b/src/Components/Modules/AssetHandler.cpp @@ -224,6 +224,18 @@ namespace Components void AssetHandler::ModifyAsset(Game::XAssetType type, Game::XAssetHeader asset, const std::string& name) { +#ifdef DEBUG + if (type == Game::XAssetType::ASSET_TYPE_IMAGE && name[0] != ',') + { + const auto image = asset.image; + const auto cat = static_cast(image->category); + if (cat == Game::ImageCategory::IMG_CATEGORY_UNKNOWN) + { + Logger::Warning(Game::CON_CHANNEL_GFX, "Image {} has wrong category IMG_CATEGORY_UNKNOWN, this is an IMPORTANT ISSUE that should be fixed!\n", name); + } + } +#endif + if (type == Game::ASSET_TYPE_MATERIAL && (name == "gfx_distortion_knife_trail" || name == "gfx_distortion_heat_far" || name == "gfx_distortion_ring_light" || name == "gfx_distortion_heat") && asset.material->info.sortKey >= 43) { if (Zones::Version() >= VERSION_ALPHA2) diff --git a/src/Components/Modules/StructuredData.cpp b/src/Components/Modules/StructuredData.cpp index 7036559e..80a1fb8c 100644 --- a/src/Components/Modules/StructuredData.cpp +++ b/src/Components/Modules/StructuredData.cpp @@ -149,10 +149,161 @@ namespace Components { if (Dedicated::IsEnabled()) return; - // Correctly upgrade stats - Utils::Hook(0x42F088, StructuredData::UpdateVersionOffsets, HOOK_CALL).install()->quick(); + // Do not execute this when building zones + if (!ZoneBuilder::IsEnabled()) + { + // Correctly upgrade stats + Utils::Hook(0x42F088, StructuredData::UpdateVersionOffsets, HOOK_CALL).install()->quick(); - // 15 or more custom classes - Utils::Hook::Set(0x60A2FE, NUM_CUSTOM_CLASSES); + // 15 or more custom classes + Utils::Hook::Set(0x60A2FE, NUM_CUSTOM_CLASSES); + + return; + } + + + // TODO: Since all of the following is zonebuilder-only code, move it to IW4OF or IStructuredDataDefSet.cpp + AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& filename, bool* /*restrict*/) + { + // Only intercept playerdatadef loading + if (type != Game::ASSET_TYPE_STRUCTURED_DATA_DEF || filename != "mp/playerdata.def") return; + + // Store asset + Game::StructuredDataDefSet* data = asset.structuredDataDefSet; + if (!data) return; + + if (data->defCount != 1) + { + Logger::Error(Game::ERR_FATAL, "PlayerDataDefSet contains more than 1 definition!"); + return; + } + + if (data->defs[0].version != 155) + { + Logger::Error(Game::ERR_FATAL, "Initial PlayerDataDef is not version 155, patching not possible!"); + return; + } + + std::unordered_map>> patchDefinitions; + std::unordered_map> otherPatchDefinitions; + + // First check if all versions are present + for (int i = 156;; ++i) + { + // We're on DB thread (OnLoad) so use DB thread for FS + FileSystem::File definition(std::format("{}/{}.json", filename, i), Game::FsThread::FS_THREAD_DATABASE); + if (!definition.exists()) break; + + std::vector> enumContainer; + std::unordered_map otherPatches; + + nlohmann::json defData; + try + { + defData = nlohmann::json::parse(definition.getBuffer()); + } + catch (const nlohmann::json::parse_error& ex) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "JSON Parse Error: {}\n", ex.what()); + return; + } + + if (!defData.is_object()) + { + Logger::Error(Game::ERR_FATAL, "PlayerDataDef patch for version {} is invalid!", i); + return; + } + + for (auto pType = 0; pType < StructuredData::PlayerDataType::COUNT; ++pType) + { + auto enumData = defData[StructuredData::EnumTranslation[pType]]; + + std::vector entryData; + + if (enumData.is_array()) + { + for (const auto& rawEntry : enumData) + { + if (rawEntry.is_string()) + { + entryData.push_back(rawEntry.get()); + } + } + } + + enumContainer.push_back(entryData); + } + + auto other = defData["other"]; + + if (other.is_object()) + { + for (auto& item : other.items()) + { + if (item.value().is_string()) + { + otherPatches[item.key()] = item.value().get(); + } + } + } + + patchDefinitions[i] = enumContainer; + otherPatchDefinitions[i] = otherPatches; + } + + // Nothing to patch + if (patchDefinitions.empty()) return; + + // Reallocate the definition + auto* newData = StructuredData::MemAllocator.allocateArray(data->defCount + patchDefinitions.size()); + std::memcpy(&newData[patchDefinitions.size()], data->defs, sizeof Game::StructuredDataDef * data->defCount); + + // Prepare the buffers + for (unsigned int i = 0; i < patchDefinitions.size(); ++i) + { + std::memcpy(&newData[i], data->defs, sizeof Game::StructuredDataDef); + newData[i].version = (patchDefinitions.size() - i) + 155; + + // Reallocate the enum array + auto* newEnums = StructuredData::MemAllocator.allocateArray(data->defs->enumCount); + std::memcpy(newEnums, data->defs->enums, sizeof Game::StructuredDataEnum * data->defs->enumCount); + newData[i].enums = newEnums; + } + + // Apply new data + data->defs = newData; + data->defCount += patchDefinitions.size(); + + // Patch the definition + for (unsigned int i = 0; i < data->defCount; ++i) + { + // No need to patch version 155 + if (newData[i].version == 155) continue; + + if (patchDefinitions.contains(newData[i].version)) + { + auto patchData = patchDefinitions[newData[i].version]; + auto otherData = otherPatchDefinitions[newData[i].version]; + + // Invalid patch data + if (patchData.size() != StructuredData::PlayerDataType::COUNT) + { + Logger::Error(Game::ERR_FATAL, "PlayerDataDef patch for version {} wasn't parsed correctly!", newData[i].version); + continue; + } + + // Apply the patch data + for (auto pType = 0; pType < StructuredData::PlayerDataType::COUNT; ++pType) + { + if (!patchData[pType].empty()) + { + StructuredData::PatchPlayerDataEnum(&newData[i], static_cast(pType), patchData[pType]); + } + } + + StructuredData::PatchAdditionalData(&newData[i], otherData); + } + } + }); } }