From 6fd3845c556e9650fe83e3058d808fbdb0db9347 Mon Sep 17 00:00:00 2001 From: AyeZeeBB Date: Sat, 3 Oct 2026 23:37:15 -0500 Subject: [PATCH] Combine the item collections every cosmetic mod adds to With several cosmetic mods installed, some mods' items were missing from the inventory, and an outfit using one logged it as "no longer installed". Bundle merge (Engine/Vfs/mod_merge_combine.cpp) - Every cosmetic mod adds its items to shared collections (items/itemmastercollection and one per season). Two mods editing one collection are combined, but the combine was failing with "Cannot read \...\cas_01.cas" and keeping the highest-priority copy, so the other mods' entries were lost. - The failed read was not of an edit at all. A mod's untouched copy of an asset another mod changed is rewritten to carry that change, and the rewritten copy lives in the merged patch. It was still recorded as that mod's own contribution, so the combine went to the mod's folder for it, at the patch's offset, and one bad read made the whole asset fall back. Renumbered collision resources were recorded the same way. - A rewritten copy is no longer a contribution. Only the mods that really edited an asset are combined, and an asset one mod edited is not "combined" at all. --- Engine/Vfs/mod_merge_combine.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Engine/Vfs/mod_merge_combine.cpp b/Engine/Vfs/mod_merge_combine.cpp index 09faf0a..6d52ecc 100644 --- a/Engine/Vfs/mod_merge_combine.cpp +++ b/Engine/Vfs/mod_merge_combine.cpp @@ -382,11 +382,21 @@ fb::TocDocument combine(const fs::path& baseToc, const fs::path& baseRoot, if (casBacked) state.casBacked = true; if (!region.files.empty()) state.manifestChunk = region.files.front().location.installChunk; if (fresh || state.metadata.empty()) state.metadata = manifest.chunkMetadata; - for (auto& entry : flat) { + const std::size_t firstFile = casBacked ? 1 : 0; + for (std::size_t position = 0; position < flat.size(); ++position) { + auto& entry = flat[position]; entry.base = isBase; auto id = asset_key(entry.asset); - if (entry.asset.kind == fb::AssetKind::ebx || - entry.asset.kind == fb::AssetKind::resource) { + // A copy rewritten above (another mod's change, renumbered collision) + // is not this mod's edit and no longer lies in this mod's archives: it + // is in the merged patch. Recorded as a contribution it was read back + // from the mod's folder at the patch's offset, the read failed, and + // the assets that really had two edits (the item collections every + // cosmetic mod adds its items to) fell back to one mod's copy. + const bool rewritten = renumbered.contains(firstFile + position) || + overridden.contains(firstFile + position); + if (!rewritten && (entry.asset.kind == fb::AssetKind::ebx || + entry.asset.kind == fb::AssetKind::resource)) { // The contributor's own copy still sits at its original offset in // its own folder, which is where a later merge reads it from. auto unshifted = entry.file;