30 std::optional<std::string> compareAnimCaptures(
const std::map<std::size_t, FileBuffer>& first,
31 const std::map<std::size_t, FileBuffer>& second) {
32 for (
const auto& [index, buffer] : first) {
33 const auto it = second.find(index);
34 if (it == second.end())
return std::format(
"anim {} missing from the rewrite", index);
36 "anim {}: {}", index, *divergence);
38 for (
const auto& [index, buffer] : second)
39 if (!first.contains(index))
return std::format(
"anim {} appeared only in the rewrite", index);
49 template <ClientVersion V>
67 const auto* gateSequences = &model.
root.sequences;
69 if constexpr (
requires { model.chunks; }) {
73 hasSkel = !model.chunks.skeletonFdid.empty() && model.chunks.skeletonFdid.front() != 0;
75 gateSequences = &model.skel.sequenceBlock.sequences;
83 const auto resplitStability = [&](
const auto& entity,
const char* what) -> std::optional<RoundtripReport> {
84 using Entity = std::remove_cvref_t<
decltype(entity)>;
86 const auto sinkInto = [&](std::map<std::size_t, FileBuffer>& animOut) {
87 return [&animOut, gateSequences](std::size_t i) ->
FileBuffer* {
88 if (i >= gateSequences->size())
return nullptr;
89 const auto& s = (*gateSequences)[i];
90 if (!s.ownsAnimFile())
return nullptr;
95 std::map<std::size_t, FileBuffer> animsFirst;
98 const auto first = entity.write(firstCtx);
102 const std::span<const std::byte> firstSpan{*first};
104 readCtx.
sequenceBase = [& ](std::size_t i) -> std::span<const std::byte> {
105 const auto it = animsFirst.find(i);
106 if (it == animsFirst.end())
return firstSpan;
107 return std::span<const std::byte>{it->second};
110 if (
auto r = reparsed.read(firstSpan, readCtx); !r)
113 std::map<std::size_t, FileBuffer> animsSecond;
116 const auto second = reparsed.write(secondCtx);
122 if (
auto mismatch = compareAnimCaptures(animsFirst, animsSecond))
127 if (
auto fail = resplitStability(model.
root,
"body"))
return *fail;
129 if constexpr (
requires { model.skel; })
131 if (
auto fail = resplitStability(model.skel.boneBlock,
"SKB1"))
return *fail;
132 if (
auto fail = resplitStability(model.skel.attachmentBlock,
"SKA1"))
return *fail;
133 if (
auto fail = resplitStability(model.skel.sequenceBlock,
"SKS1"))
return *fail;
138 if constexpr (
requires { model.skins; })
139 for (std::size_t i = 0; i < model.skins.size(); ++i) {
140 using Skin = std::remove_cvref_t<
decltype(model.skins[i])>;
141 const auto first = model.skins[i].
write();
145 if (
auto r = reparsed.read(std::span<const std::byte>{*first}); !r)
147 const auto second = reparsed.write();
163 return roundtripM2<V>(
fs, path);
Internals of the round-trip audit (namespace wowlib::audit::detail): the per-format driver class — on...
The M2 entity (namespace wowlib::formats::m2): a whole model with its satellite files baked in,...
RoundtripReport failWith(RoundtripReport &report, std::string stage, std::string error)
Mark report failed in place and hand it back — keeps the unknown-chunk tally gathered before the fail...
std::optional< RoundtripReport > classifyRead(RoundtripReport &report, std::string stage, const Result< FileBuffer > &result)
Classify a client-file read for the audit: content behind an unknown TACT key and zero-byte archive e...
bool withVersion(const ClientVersion v, F &&f)
Invoke f with the targeted release constant matching v as a compile-time template argument — the runt...
constexpr bool versionSupported(std::span< const ClientVersion > grid, const ClientVersion v)
Whether a format's instantiation grid carries v — the per-format if constexpr guard of the version di...
RoundtripReport skipped(std::string_view reason)
A skipped outcome — ok, tagged with the reason.
RoundtripReport guarded(F &&f)
Run one file's driver, converting any escaping exception into a failed outcome — one pathological fil...
std::optional< std::string > firstDivergence(const FileBuffer &original, const FileBuffer &rewritten)
The first byte divergence between an original and a rewrite, described by offset and sizes — the stab...
void tallyUnknown(RoundtripReport &report, const formats::ChunkExtras &extras, formats::FourCCEndian endian=formats::FourCCEndian::Reversed)
Append every unmodeled chunk of a parsed entity to the report's tally, one fourcc spelling per occurr...
RoundtripReport unrecognizedVersion(const ClientVersion v)
The dispatch-failure outcome for a version outside the targeted releases.
@ EncryptedContent
Content is behind an unknown TACT encryption key.
std::vector< std::byte > FileBuffer
Owning byte buffer for file contents read out of a client storage.
The offset-format storage vocabulary and serializer engine for the M2 family, in one header.
Resolution of SequenceData members while reading: where each outer element's nested data lives.
std::function< std::span< const std::byte >(std::size_t i)> sequenceBase
The base span the inner arrays of outer element i resolve their offsets against — the matching ....
Resolution of SequenceData members while writing: which buffer each outer element's nested data block...
std::function< FileBuffer *(std::size_t i) > sequenceSink
The destination buffer for outer element i's nested data — the .anim file buffer being assembled for ...