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
  <mod>\...\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.
This commit is contained in:
AyeZeeBB committed 2026-10-03 23:37:15 -05:00
1 parent d895c9e915
commit 6fd3845c55
1 file changed
+13 -3
+13 -3
View File
@@ -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;