wowlib 0.0.0
Read & write World of Warcraft client files — a C++26 core
Loading...
Searching...
No Matches
satellites.hpp
Go to the documentation of this file.
1#pragma once
2
18
19#include <cstdint>
20#include <cstring>
21#include <format>
22#include <functional>
23#include <initializer_list>
24#include <map>
25#include <optional>
26#include <set>
27#include <span>
28#include <string>
29#include <string_view>
30#include <vector>
31
33#include <wowlib/core/error.hpp>
37
38namespace wowlib::formats::m2 {
42 struct SequenceKey {
43 std::uint16_t id = 0;
44 std::uint16_t variation = 0;
45
46 constexpr auto operator<=>(const SequenceKey&) const = default;
47 };
48
52 public:
56 explicit SatellitePaths(std::string_view modelPath)
57 : _stem{modelPath} {
58 for (std::string_view ext : {".m2", ".M2", ".mdx", ".MDX", ".mdl", ".MDL", ".skel", ".SKEL"})
59 if (modelPath.ends_with(ext)) {
60 _stem = std::string{modelPath.substr(0, modelPath.size() - ext.size())};
61 break;
62 }
63 }
64
66 const std::string& stem() const { return _stem; }
67
69 std::string skin(std::uint32_t index) const {
70 return std::format("{}{:02}.skin", _stem, index);
71 }
72
74 std::string lodSkin(std::uint32_t band) const {
75 return std::format("{}_lod{:02}.skin", _stem, band);
76 }
77
79 std::string anim(SequenceKey key) const {
80 return std::format("{}{:04}-{:02}.anim", _stem, key.id, key.variation);
81 }
82
84 std::string bone(std::uint32_t variant) const {
85 return std::format("{}_{:02}.bone", _stem, variant);
86 }
87
89 std::string skel() const { return _stem + ".skel"; }
90
92 std::string phys() const { return _stem + ".phys"; }
93
94 private:
95 std::string _stem;
96 };
97
103 class AnimCache {
104 public:
105 // .anim chunk ids, forward like all M2-family fourccs.
106 static constexpr std::uint32_t Afm2Magic = fourcc("AFM2", FourCCEndian::Forward);
107 static constexpr std::uint32_t AfsaMagic = fourcc("AFSA", FourCCEndian::Forward);
108 static constexpr std::uint32_t AfsbMagic = fourcc("AFSB", FourCCEndian::Forward);
109
111 static std::uint32_t afidLookup(std::span<const chunked::record::AnimFileEntry> entries, SequenceKey key) {
112 for (const chunked::record::AnimFileEntry& e : entries)
113 if (e.animId == key.id && e.subAnimId == key.variation) return e.fileId;
114 return 0;
115 }
116
118 explicit AnimCache(std::function<Result<FileBuffer>(SequenceKey)> loader)
119 : _loader{std::move(loader)} {}
120
123 std::span<const std::byte> window(SequenceKey key, std::uint32_t target) {
124 auto [it, inserted] = _files.try_emplace(key);
125 if (inserted)
126 if (auto file = _loader(key)) it->second = std::move(*file);
127 if (!it->second) return {};
128 const FileBuffer& buf = *it->second;
129 std::uint32_t lead = 0;
130 if (buf.size() >= 8) std::memcpy(&lead, buf.data(), 4);
131 const bool chunked = lead == Afm2Magic;
132 if (!chunked)
133 return target == Afm2Magic ? std::span<const std::byte>{buf} : std::span<const std::byte>{};
134 for (std::size_t pos = 0; pos + 8 <= buf.size();) {
135 std::uint32_t fourcc = 0;
136 std::uint32_t size = 0;
137 std::memcpy(&fourcc, buf.data() + pos, 4);
138 std::memcpy(&size, buf.data() + pos + 4, 4);
139 if (size > buf.size() - pos - 8) break;
140 if (fourcc == target) return std::span<const std::byte>{buf}.subspan(pos + 8, size);
141 pos += 8 + size;
142 }
143 return {};
144 }
145
158 template <typename Sequence>
159 auto sequenceBase(const std::vector<Sequence>& sequences,
160 std::span<const std::byte> inlineBase,
161 std::uint32_t target) {
162 return [this, &sequences, inlineBase, target](std::size_t i) -> std::span<const std::byte> {
163 if (i >= sequences.size()) return inlineBase;
164 const Sequence& s = sequences[i];
165 if (s.isAlias()) return {}; // aliases own no data; their stored records are stale
166 if (!s.ownsAnimFile()) return inlineBase;
167 return window(SequenceKey{s.id, s.variationIndex}, target);
168 };
169 }
170
171 private:
172 std::function<Result<FileBuffer>(SequenceKey)> _loader;
173 std::map<SequenceKey, std::optional<FileBuffer>> _files;
174 };
175
182 public:
187 template <typename Sequence>
188 OffsetWriteContext sink(const std::vector<Sequence>& sequences) {
190 ctx.sequenceSink = [this, &sequences](std::size_t i) -> FileBuffer* {
191 if (i >= sequences.size()) return nullptr;
192 const Sequence& s = sequences[i];
193 if (!s.ownsAnimFile()) return nullptr;
194 return &_bufs[SequenceKey{s.id, s.variationIndex}];
195 };
196 return ctx;
197 }
198
200 const std::map<SequenceKey, FileBuffer>& entries() const { return _bufs; }
201
206 void appendChunkTo(FileBuffer& out, std::uint32_t fourcc, SequenceKey key) const {
207 const auto it = _bufs.find(key);
208 _appendChunk(out, fourcc,
209 it == _bufs.end() ? std::span<const std::byte>{} : std::span<const std::byte>{it->second});
210 }
211
214 static std::set<SequenceKey> mergedKeys(std::initializer_list<const AnimBuffers*> sets) {
215 std::set<SequenceKey> keys;
216 for (const AnimBuffers* set : sets)
217 for (const auto& [key, buf] : set->_bufs) keys.insert(key);
218 return keys;
219 }
220
221 private:
222 static void _appendChunk(FileBuffer& out, std::uint32_t fourcc, std::span<const std::byte> payload) {
223 const auto size = static_cast<std::uint32_t>(payload.size());
224 const std::size_t at = out.size();
225 out.resize(at + 8);
226 std::memcpy(out.data() + at, &fourcc, 4);
227 std::memcpy(out.data() + at + 4, &size, 4);
228 out.insert(out.end(), payload.begin(), payload.end());
229 }
230
231 std::map<SequenceKey, FileBuffer> _bufs;
232 };
233}
The owning byte buffer file contents are read into.
The per-sequence external write buffers every M2-family write path shares: an external sequence's dat...
const std::map< SequenceKey, FileBuffer > & entries() const
The filled buffers, keyed and ordered by sequence key.
static std::set< SequenceKey > mergedKeys(std::initializer_list< const AnimBuffers * > sets)
The union of every set's keys — one .anim file exists per key across all sections (AFM2 + AFSA + AFSB...
void appendChunkTo(FileBuffer &out, std::uint32_t fourcc, SequenceKey key) const
Append this set's buffer for key to out as one fourcc+size+payload chunk (assembling ....
OffsetWriteContext sink(const std::vector< Sequence > &sequences)
The write context routing external sequences into this buffer set.
std::span< const std::byte > window(SequenceKey key, std::uint32_t target)
The resolution window of sequence key's file for chunk target (afm2/afsa/AfsbMagic); empty when unava...
auto sequenceBase(const std::vector< Sequence > &sequences, std::span< const std::byte > inlineBase, std::uint32_t target)
The per-sequence base resolver every M2-family read path shares: sequence i's inner arrays resolve ag...
static constexpr std::uint32_t Afm2Magic
AnimCache(std::function< Result< FileBuffer >(SequenceKey)> loader)
static std::uint32_t afidLookup(std::span< const chunked::record::AnimFileEntry > entries, SequenceKey key)
The AFID FileDataID for sequence key, 0 when unlisted.
static constexpr std::uint32_t AfsbMagic
static constexpr std::uint32_t AfsaMagic
std::string skel() const
"{stem}.skel".
std::string skin(std::uint32_t index) const
"{stem}0N.skin" — the numbered view naming convention.
std::string anim(SequenceKey key) const
"{stem}AAAA-SS.anim" — the external-sequence naming.
std::string phys() const
"{stem}.phys".
std::string lodSkin(std::uint32_t band) const
"{stem}_lod0N.skin" — LOD bands number from 1.
SatellitePaths(std::string_view modelPath)
std::string bone(std::uint32_t variant) const
"{stem}_NN.bone" — one per FacePose variant.
const std::string & stem() const
The model path without its extension.
The error-handling vocabulary: ErrorCode, Error and the Result<T> alias every fallible wowlib operati...
FourCC chunk identifiers: compile-time conversion of the four-letter codes to the host integers chunk...
Legion+ companion-chunk records (namespace wowlib::formats::m2::chunked::record): the payload types o...
The Legion+ chunked .m2 shell (M2ChunkedFile) with its per-version classes and the companion-chunk pa...
Definition chunked.hpp:29
constexpr std::uint32_t fourcc(const char(&cc)[5], FourCCEndian endian=FourCCEndian::Reversed)
The host integer a scanned chunk id compares equal to for code cc.
Definition fourcc.hpp:33
@ Forward
The characters are stored as written: 'AFID' appears in the file as the bytes "AFID".
Definition fourcc.hpp:22
std::expected< T, Error > Result
Every fallible wowlib operation returns Result<T>; bindings translate the error branch into a target-...
Definition error.hpp:100
std::vector< std::byte > FileBuffer
Owning byte buffer for file contents read out of a client storage.
Definition buffer.hpp:16
The offset-format storage vocabulary and serializer engine for the M2 family, in one header.
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 ...
A sequence's satellite identity: the (animation id, variation) pair the .anim naming convention ("{st...
std::uint16_t variation
Variation index within the id.
std::uint16_t id
Animation id (AnimationData.dbc).
constexpr auto operator<=>(const SequenceKey &) const =default
One AFID entry: which FileDataID serves a sequence's .anim data.
Definition records.hpp:22