Mod merge: placeholder archive indexes are reserved; carry() keeps one list

A mod archive numbered 65534 or 65535 is placed under a free index, so no real placement is read as a placeholder. carry() records every payload in one list (settle() still writes each once per install chunk) and keeps one archive index, as write() does.
This commit is contained in:
worthlessnorms committed 2026-10-16 14:00:34 -05:00
1 parent cec00f412e
commit 3feeaa3893
4 files changed
+17 -21

No files matched your search

+2 -2
View File
@@ -289,8 +289,8 @@ MergeReport merge_mods(const Catalog& catalog, const MergeObserver& observe, con
// Archive 1 stays the patch's own file, never a link to a mod's:
// a live apply appends manifests and new mods to it while the
// game has it open, and a linked file cannot be swapped for a
// private copy then (Windows keeps it in use).
claimed.insert(manifestArchive);
// private copy then (Windows keeps it in use). Placeholder indexes name no file.
claimed.insert({manifestArchive, CasStore::waiting_archive, CasStore::carried_archive});
if (number == manifestArchive) {
if (const auto free = claim(directory); free) {
spot.archive = *free;
+9 -10
View File
@@ -84,19 +84,19 @@ CasStore CasStore::waiting() const {
copy.held_.clear();
copy.offsets_.clear();
copy.carried_.clear();
copy.carriedIndex_.clear();
copy.carriedAt_.clear();
return copy;
}
fb::BundleFileInfo CasStore::carry(std::uint32_t installChunk, std::uint16_t archive, std::span<const std::byte> encoded) {
if (!waiting_) throw std::logic_error("Only a waiting store carries payloads");
// An install chunk no package names fails here, for this payload alone, and not when it settles.
(void)archive_path(installChunk, archive);
const std::pair key{installChunk, sha1_of(encoded)};
const auto [found, fresh] = carriedIndex_.try_emplace(key, carried_.size());
if (fresh) carried_.push_back({installChunk, archive, key.second, encoded, {}});
return {{true, installChunk, carried_archive}, static_cast<std::uint32_t>(found->second),
if (placeholder(archive) || (archive_ && *archive_ != archive))
throw std::logic_error("A waiting store writes to one archive index");
// An install chunk no package names fails here, for this payload alone.
(void)directory(installChunk);
archive_ = archive;
carried_.push_back({installChunk, sha1_of(encoded), encoded, {}});
return {{true, installChunk, carried_archive}, static_cast<std::uint32_t>(carried_.size() - 1),
static_cast<std::uint32_t>(encoded.size())};
}
@@ -106,13 +106,12 @@ void CasStore::settle(CasStore& waiting) {
held.at = append(fs::path(path), held.bytes);
held.bytes = {};
}
// Then the carried assets, in the order the superbundle first named them. An asset an
// earlier superbundle settled in this install chunk is not written again.
// Then the carried assets, each written once per install chunk.
for (auto& carried : waiting.carried_) {
const std::pair key{carried.installChunk, carried.sha1};
auto found = carriedAt_.find(key);
if (found == carriedAt_.end())
found = carriedAt_.emplace(key, write(carried.installChunk, carried.archive, carried.bytes)).first;
found = carriedAt_.emplace(key, write(carried.installChunk, *waiting.archive_, carried.bytes)).first;
carried.at = found->second;
}
}
+1 -2
View File
@@ -370,8 +370,7 @@ fb::TocDocument combine(const fs::path& baseToc, const fs::path& baseRoot,
else if (addition.asset.kind == fb::AssetKind::resource)
companions.emplace(std::tuple{bundleName, addition.mod, lower(addition.asset.name)}, &addition);
}
// Where an addition's payload already went in this superbundle's patch
// archive, by install chunk: every copy that is given it reads the one.
// The carried place each addition got in this superbundle, by install chunk.
std::map<std::pair<std::uint32_t, const AssetAddition*>, fb::BundleFileInfo> carriedAt;
// The names any mod changed, to rule the rest out without a copy of each (name_hash).
std::unordered_set<std::uint64_t> changedNames, scriptNames;
+5 -7
View File
@@ -132,8 +132,7 @@ public:
// own has been settled, settle() appends what it holds to the real archives, and
// settled() then moves a placement that points into it to where that went.
static constexpr std::uint16_t waiting_archive = 0xFFFF;
// An asset carried into copies in several superbundles. A waiting store names it by its
// place in the store's carried list, and settle() writes it once per install chunk.
// A carried payload, by its index in the waiting store's list: settle() writes it once per install chunk.
static constexpr std::uint16_t carried_archive = 0xFFFE;
[[nodiscard]] CasStore waiting() const;
[[nodiscard]] bool holding() const noexcept { return !held_.empty() || !carried_.empty(); }
@@ -169,16 +168,15 @@ private:
bool waiting_{};
std::optional<std::uint16_t> archive_;
std::map<std::wstring, Held> held_;
// Carried payloads (waiting store) and where each landed (settling store).
struct Carried {
std::uint32_t installChunk{};
std::uint16_t archive{};
fb::Sha1 sha1;
std::span<const std::byte> bytes;
fb::BundleFileInfo at; // where settle() put it
fb::BundleFileInfo at;
};
std::vector<Carried> carried_; // waiting: first-reference order
std::map<std::pair<std::uint32_t, fb::Sha1>, std::size_t> carriedIndex_; // waiting: into carried_
std::map<std::pair<std::uint32_t, fb::Sha1>, fb::BundleFileInfo> carriedAt_; // settled: one per install chunk
std::vector<Carried> carried_;
std::map<std::pair<std::uint32_t, fb::Sha1>, fb::BundleFileInfo> carriedAt_;
};
using ArchiveUse = std::set<std::pair<std::uint32_t, std::uint16_t>>;