#include "mod_merge_internal.h" #include "Engine/Resource/cas_codec.h" #include "Engine/Resource/ebx_document.h" #include "Engine/Resource/ebx_merge.h" #include "Engine/Resource/ebx_writer.h" #include #include #include #include #include #include #include #include #include #include namespace dingosdk::mods::detail { namespace { // The game's side of what every mod is held against, read once for all of them: forty // cosmetic mods each patch the same few bundles, the largest with some 17,000 assets, and // each used to list that bundle again for itself. struct GameBundle { std::optional listing; // The game's copy of each asset: its sha1, and where in the listing it is. std::map, std::less<>> original; std::map> scripts; std::set> resources; }; struct GameToc { fb::TocDocument document; std::map> bundles; std::set chunks; }; // Made on first use by whichever thread asks first; the others wait for it. One that could // not be read throws for each asker, as reading it for each did. template class Shared { public: template const Value& get(const std::string& key, Make&& make) { Entry* entry{}; { std::lock_guard lock(mutex_); entry = &entries_[key]; } std::call_once(entry->once, [&] { make(entry->value); }); return entry->value; } private: struct Entry { std::once_flag once; Value value; }; std::mutex mutex_; std::map> entries_; // nodes stay where they are }; // The game's assets a mod changed. They wait until its additions are settled: one that // another mod's asset of the same name stands in for is not where a copy can find it, and a // change must not name it there. struct Change { std::string bundle; // lower case: where the mod changed it std::string name; // lower case std::string asset; // as the bundle spells it fb::Sha1 game; // the game's copy it replaces fb::Sha1 sha1; std::uint64_t originalSize{}; std::vector encoded; std::optional gameFile; // where the game's copy is }; struct Script { std::string name; // lower case fb::Sha1 game; // the game's copy it replaces std::optional value; // none: it could not be read, and `note` says so std::size_t note{}; // its place in the mod's notes }; // What one mod's files say, read without regard to any other mod: the part of the work that // is reading and decoding, done for several mods at once. What it means beside the mods // above it is settled afterwards, one mod at a time in priority order. struct ModRead { // This mod's new assets, and the partitions its recorded changes import. // An added asset only follows a change that refers to it: copied on its // own, it could arrive in another mod's bundle without the resources // and chunks it was added with. std::vector> candidates, companions; std::vector unreadable; // per candidate: never follows std::map, std::less<>> addedIn; // the documents it added to each bundle, by guid std::vector newChunks; // TOC chunks the game's TOCs do not have std::set imported; std::vector changes; std::vector