55 =welder::weld_as(
"M2"),
58 A whole model, abstract over the client version — the MD20 body with
59 its satellite files (.skin, .anim) baked in. Construct the concrete
60 version with M2.for_version(expansion), then read()/write(); the
61 per-version M2* classes are subclasses. See https://wowdev.wiki/M2.)")
63 bool operator==(
const M2Base&)
const =
default;
73 template <ClientVersion V>
75 [[=welder::mark::exclude]]
82 =welder::mark::no_reassign,
83 =welder::doc(R
"(The model's LOD views, in view order ("{model}0N.skin"
84 files, WotLK+). The source of truth for the view
85 count: write() stamps the body's num_skin_profiles
86 layout field from this vector's length.)")]]
87 std::vector<Skin<V>> skins;
89 [[=welder::mark::exclude]]
95 template <ClientVersion V>
97 [[=welder::mark::exclude]]
104 =welder::doc(R
"(The chunked .m2 stream (Legion+): the satellite chunks
105 (FileDataIDs, extended particles, parent overrides)
106 plus preserved unknown chunks. The MD21 transport blob
107 inside is TRANSIENT: read() decodes it into `root` and
108 drops the bytes; write() re-encodes root into the
109 stream and refreshes the FileDataID chunks.)")]]
113 =welder::mark::no_reassign,
114 =welder::doc("The LOD-band skins (the SFID entries beyond "
115 "num_skin_profiles), in band order.")]]
116 std::vector<Skin<V>> lodSkins;
119 =welder::doc(
"The referenced .phys file bytes (PFID), baked in "
120 "verbatim — structured physics is a follow-up "
121 "milestone. Inline physics (PFDC) stays a chunk on "
126 =welder::doc(R
"(The shared skeleton (SKID), fully baked in — bones,
127 attachments, sequences and the parent link. Engaged
128 exactly when the chunk stream carries a skeleton
129 FileDataID; skeletons are shared between models, so
130 edit with care or write the .skel standalone.)")]]
136 =welder::mark::no_reassign,
137 =welder::doc(
"The .bone facial-pose files (BFID; the skeleton's when "
138 "skel-based), in variant order.")]]
139 std::vector<BoneFile> boneFiles;
141 [[=welder::mark::exclude]]
158 template <ClientVersion V>
162 A whole model for one client version: the MD20 body with skins and
163 external sequence data baked in. A written model is canonical-layout
164 and re-reads equal (no byte-perfect guarantee for offset formats).
165 See https://wowdev.wiki/M2.)")
171 [[=welder::doc(
"The MD20 body — the model's root, uniform across every "
172 "era (pre-Legion it IS the file; Legion+ it is the "
173 "decoded MD21 image, whose transport blob `chunks` drops "
183 =welder::doc(
"Load the model and all its satellite files from a client "
184 "filesystem, replacing this entity's contents.")]]
186 const FileKey& key [[=welder::doc(
"the .m2 file identity (path and/or FileDataID)")]]);
189 =welder::doc(
"Serialize and store the model and every satellite file "
190 "through the filesystem's project overlay; satellite names "
191 "derive from the key, which must resolve to a path.")]]
195 "the .m2 file identity; must resolve to a path")]])
const;
204 Check the logical integrity contracts this model must satisfy to LOAD
205 in the client — across the body AND every skin — which write()
206 deliberately never enforces. Call it before writing when you want to
207 know the model will load. A model read from a client and left
208 unmodified reports no errors; warnings mark states real client files
210 =welder::returns(R"(every violated contract, each with its member path
211 ("root..." / "skins[i]..."))")]]
216 "Validate and raise on the first error instead of returning "
217 "a report — the assert-style face of validate()."),
218 =welder::returns(
"nothing; raises when validate() finds any error")]]
232 static Result<void> _checkHeader(std::uint32_t magic, std::uint32_t formatVersion);
372 const auto& sequences,
373 const std::vector<std::uint32_t>& boneFdids,
400 static bool _skeletonEngaged(std::span<const std::uint32_t> skeletonFdid) {
401 return !skeletonFdid.empty() && skeletonFdid.front() != 0;
409 template <ClientVersion V>
420 template <ClientVersion V>
421 Result<void> detail::M2<V>::_checkHeader(std::uint32_t magic, std::uint32_t formatVersion) {
425 "not an MD20 model (magic {:#010x}; a Legion+ chunked " "file starts with MD21 instead)",
428 if (formatVersion < range.first || formatVersion > range.second)
430 std::format(
"MD20 version {} is outside the requested client's " "era [{}, {}]", formatVersion,
431 range.first, range.second));
435 template <ClientVersion V>
436 Result<void> detail::M2<V>::_readSkinInto(fs::FileSystem& fs,
const FileKey& key, std::string_view what,
auto& out)
438 const auto bytes = fs.readFile(key);
440 return makeError(bytes.error().code, std::format(
"{}: {}", what, bytes.error().message),
441 bytes.error().nativeError);
443 if (
auto r =
skin.read(std::span<const std::byte>{*bytes}); !r)
444 return makeError(r.error().code, std::format(
"{}: {}", what, r.error().message), r.error().nativeError);
447 std::format(
"{} magic is {:#010x}, expected 'SKIN'", what,
skin.magic));
448 out.push_back(std::move(
skin));
452 template <ClientVersion V>
453 Result<void> detail::M2<V>::_readMonolithic(fs::FileSystem& fs,
const FileKey& key, std::span<const std::byte> main)
455 const FileKey resolved = fs.resolve(key);
458 "a monolithic M2's satellite files (.skin/.anim) need a " "resolvable path for the model key");
468 return fs.readFile(FileKey{paths.anim(seq)});
473 if (
auto r = this->
root.read(main, ctx); !r)
return r;
474 if (
auto r = _checkHeader(this->
root.magic, this->root.formatVersion); !r)
return r;
476 this->skins.reserve(this->
root.numSkinProfiles);
477 for (std::uint32_t i = 0; i < this->
root.numSkinProfiles; ++i)
478 if (
auto r = _readSkinInto(fs, FileKey{paths.skin(i)}, std::format(
"skin {}", i), this->skins); !r)
return r;
482 template <ClientVersion V>
483 Result<void> detail::M2<V>::_readChunked(fs::FileSystem& fs,
const FileKey& key, std::span<const std::byte> main)
486 if (
auto r = this->chunks.read(main); !r)
return r;
490 const bool hasSkel = _skeletonEngaged(this->chunks.skeletonFdid);
492 if (
auto r = _loadSkeleton(fs); !r)
return r;
498 if (
auto r = _readChunkedBody(fs, key, hasSkel); !r)
return r;
499 this->chunks.md21.bytes = {};
503 return _readChunkedSkins(fs).and_then([&] {
return _readBoneFiles(fs, hasSkel); }).transform([&] {
508 template <ClientVersion V>
510 if (
auto r = this->skel.read(fs, FileKey{FileDataID{this->chunks.skeletonFdid.front()}}); !r)
511 return makeError(r.error().code, std::format(
".skel: {}", r.error().message), r.error().nativeError);
515 template <ClientVersion V>
516 Result<void> detail::M2<V>::_readChunkedBody(fs::FileSystem& fs,
const FileKey& key,
bool hasSkel)
requires (V >=
519 std::optional<SatellitePaths> paths;
520 if (
const FileKey resolved = fs.resolve(key); resolved.path) paths.emplace(*resolved.path);
522 const std::span<const std::byte> md21{this->chunks.md21.bytes};
526 const auto* sequences = hasSkel ? &this->skel.sequenceBlock.sequences : &this->
root.sequences;
527 const auto& afids = hasSkel ? this->skel.effectiveAnimFdids() : this->chunks.animFdids;
533 if (paths)
return fs.readFile(
FileKey{paths->anim(seq)});
539 if (
auto r = this->
root.read(md21, ctx); !r)
return r;
540 return _checkHeader(this->
root.magic, this->root.formatVersion);
543 template <ClientVersion V>
547 if (this->chunks.skinFdids.size() < this->root.numSkinProfiles)
549 this->chunks.skinFdids.size(),
550 this->root.numSkinProfiles));
551 for (
const auto [i, fdid] : std::views::enumerate(this->chunks.skinFdids) | std::views::take(
552 this->root.numSkinProfiles))
553 if (
auto r = _readSkinInto(
fs,
FileKey{
FileDataID{fdid}}, std::format(
"skin {}", i), this->skins); !r)
return r;
554 for (
const auto [i, fdid] : std::views::enumerate(this->chunks.skinFdids) | std::views::drop(
555 this->root.numSkinProfiles))
557 if (
auto r = _readSkinInto(
fs,
FileKey{
FileDataID{fdid}}, std::format(
"lod skin {}", i), this->lodSkins); !r)
562 template <ClientVersion V>
563 Result<void> detail::M2<V>::_readBoneFiles(fs::FileSystem& fs,
bool hasSkel)
requires (V >=
M2ChunkedContainer) {
564 const auto& bfids = hasSkel ? this->skel.effectiveBoneFdids() : this->chunks.boneFdids;
565 for (
const auto [i, fdid] : std::views::enumerate(bfids)) {
566 if (fdid == 0)
continue;
569 return makeError(bytes.error().code, std::format(
".bone {}: {}", i, bytes.error().message),
570 bytes.error().nativeError);
572 if (
auto r =
bone.read(std::span<const std::byte>{*bytes}); !r)
573 return makeError(r.error().code, std::format(
".bone {}: {}", i, r.error().message), r.error().nativeError);
574 this->boneFiles.push_back(std::move(
bone));
579 template <ClientVersion V>
580 void detail::M2<V>::_readPhysics(
fs::FileSystem&
fs)
requires (V >= M2ChunkedContainer) {
581 if (!this->chunks.physFdid.empty() && this->chunks.physFdid.front() != 0)
582 if (
auto bytes =
fs.readFile(
FileKey{FileDataID{this->chunks.physFdid.front()}})) this->phys.bytes =
586 template <ClientVersion V>
587 Result<void> detail::M2<V>::read(fs::FileSystem& fs,
const FileKey& key) {
588 const auto main = fs.readFile(key);
589 if (!main)
return std::unexpected{main.error()};
595 this->lodSkins.clear();
598 this->boneFiles.clear();
602 if (
auto r = root.read(std::span<const std::byte>{*main}); !r)
return r;
603 return _checkHeader(root.magic, root.formatVersion);
606 return _readMonolithic(fs, key, std::span<const std::byte>{*main});
609 std::uint32_t lead = 0;
610 if (main->size() >= 4) std::memcpy(&lead, main->data(), 4);
611 if (lead == Md20Magic) {
615 if constexpr (V <
M2ChunkedOnly)
return _readMonolithic(fs, key, std::span<const std::byte>{*main});
617 return makeError(ErrorCode::FormatVersionMismatch,
618 "bare MD20 model under a BfA+ target — raw (unchunked) .m2 files "
619 "no longer exist from 8.0 on; if this is a Legion-era file, read "
620 "it with the legion target");
622 return _readChunked(fs, key, std::span<const std::byte>{*main});
626 template <ClientVersion V>
627 Result<void> detail::M2<V>::write(fs::FileSystem& fs,
const FileKey& key)
const {
628 const FileKey resolved = fs.resolve(key);
630 return makeError(ErrorCode::PathNotResolvable,
"saving an M2 needs a path for the key");
631 const SatellitePaths paths{*resolved.path};
634 const auto bytes = root.write();
635 if (!bytes)
return std::unexpected{bytes.error()};
636 if (
auto r = fs.addFile(*resolved.path, *bytes); !r)
return std::unexpected{r.error()};
639 else if constexpr (V <
M2ChunkedContainer)
return _writeMonolithic(fs, *resolved.path, paths);
640 else return _writeChunked(fs, *resolved.path, paths);
643 template <ClientVersion V>
644 Result<FileBuffer> detail::M2<V>::_writeBodyImage(AnimBuffers& afm2Bufs,
const auto& sequences)
const requires (V
650 auto bytes = root.write(afm2Bufs.sink(sequences));
651 if (!bytes)
return std::unexpected{bytes.error()};
656 constexpr std::size_t countAt = M2Root < V > ::memberOffset(
"numSkinProfiles");
657 const auto count =
static_cast<std::uint32_t
>(this->skins.size());
658 std::memcpy(bytes->data() + countAt, &count,
sizeof count);
662 template <ClientVersion V>
663 Result<void> detail::M2<V>::_writeMonolithic(fs::FileSystem& fs,
664 const std::string& path,
665 const SatellitePaths& paths)
const requires (V >=
667 AnimBuffers afm2Bufs;
668 const auto bytes = _writeBodyImage(afm2Bufs, root.sequences);
669 if (!bytes)
return std::unexpected{bytes.error()};
672 if (
auto r = fs.addFile(path, *bytes); !r)
return std::unexpected{r.error()};
673 for (
const auto& [seq, buf] : afm2Bufs.entries())
674 if (
auto r = fs.addFile(paths.anim(seq), buf); !r)
675 return makeError(r.error().code, std::format(
"anim {:04}-{:02}: {}", seq.id, seq.variation, r.error().message),
676 r.error().nativeError);
677 for (
const auto [i, skin] : std::views::enumerate(this->skins)) {
678 const auto skinBytes = skin.write();
679 if (!skinBytes)
return std::unexpected{skinBytes.error()};
680 if (
auto r = fs.addFile(paths.skin(
static_cast<std::uint32_t
>(i)), *skinBytes); !r)
681 return makeError(r.error().code, std::format(
"skin {}: {}", i, r.error().message), r.error().nativeError);
686 template <ClientVersion V>
687 Result<void> detail::M2<V>::_writeChunked(fs::FileSystem& fs,
688 const std::string& path,
693 const bool hasSkel = _skeletonEngaged(this->chunks.skeletonFdid);
695 AnimBuffers afm2Bufs;
697 ? _writeBodyImage(afm2Bufs, this->skel.sequenceBlock.sequences)
698 : _writeBodyImage(afm2Bufs, root.sequences);
699 if (!bytes)
return std::unexpected{bytes.error()};
703 M2ChunkedFile < V > stream = this->chunks;
704 stream.md21.bytes = std::move(*bytes);
707 const auto boneFdids = _writeBoneFiles(fs, paths);
708 if (!boneFdids)
return std::unexpected{boneFdids.error()};
712 stream.animFdids.clear();
714 if (
auto r = _writePlainAnims(fs, paths, afm2Bufs, stream); !r)
return r;
715 stream.boneFdids = *boneFdids;
717 else if (
auto r = _writeSkeletonSatellites(fs, paths, afm2Bufs, this->skel.sequenceBlock.sequences, *boneFdids,
718 stream); !r)
return r;
723 return _writeChunkedSkins(fs, paths, stream).and_then([&] {
return _writeChunkedPhys(fs, paths, stream); }).
724 and_then([&] {
return stream.write(); }).and_then(
725 [&](
const FileBuffer& streamBytes) {
726 return fs.addFile(path, streamBytes).transform(
731 template <ClientVersion V>
732 Result<std::vector<std::uint32_t>>
733 detail::M2<V>::_writeBoneFiles(fs::FileSystem& fs,
const SatellitePaths& paths)
const requires (V >=
735 std::vector<std::uint32_t> boneFdids;
736 for (
const auto [i, bone] : std::views::enumerate(this->boneFiles)) {
737 const auto boneBytes = bone.
write();
738 if (!boneBytes)
return std::unexpected{boneBytes.error()};
739 const auto r = fs.addFile(paths.bone(
static_cast<std::uint32_t
>(i)), *boneBytes);
741 return makeError(r.error().code, std::format(
".bone {}: {}", i, r.error().message), r.error().nativeError);
742 boneFdids.push_back(r->value);
747 template <ClientVersion V>
748 Result<void> detail::M2<V>::_writePlainAnims(fs::FileSystem& fs,
749 const SatellitePaths& paths,
750 AnimBuffers& afm2Bufs,
753 for (
const auto& [seq, buf] : afm2Bufs.entries()) {
755 if (
hasFlag(root.globalFlags, GlobalFlags::ChunkedAnimFiles)) afm2Bufs.appendChunkTo(
756 file, AnimCache::Afm2Magic, seq);
758 const auto r = fs.addFile(paths.anim(seq), file);
760 return makeError(r.error().code, std::format(
"anim {:04}-{:02}: {}", seq.id, seq.variation, r.error().message),
761 r.error().nativeError);
762 stream.animFdids.push_back({seq.id, seq.variation, r->value});
767 template <ClientVersion V>
768 Result<void> detail::M2<V>::_writeSkeletonSatellites(fs::FileSystem& fs,
769 const SatellitePaths& paths,
770 AnimBuffers& afm2Bufs,
771 const auto& sequences,
772 const std::vector<std::uint32_t>& boneFdids,
780 auto skelCopy = this->skel;
781 AnimBuffers afsaBufs;
782 AnimBuffers afsbBufs;
784 auto encoded = this->skel.boneBlock.write(afsbBufs.sink(sequences));
785 if (!encoded)
return std::unexpected{encoded.error()};
786 skelCopy.skb1.bytes = std::move(*encoded);
787 auto attachments = this->skel.attachmentBlock.write(afsaBufs.sink(sequences));
788 if (!attachments)
return std::unexpected{attachments.error()};
789 skelCopy.ska1.bytes = std::move(*attachments);
792 skelCopy.animFdids.clear();
793 for (
const SequenceKey seq : AnimBuffers::mergedKeys({&afm2Bufs, &afsaBufs, &afsbBufs})) {
795 afm2Bufs.appendChunkTo(file, AnimCache::Afm2Magic, seq);
796 afsaBufs.appendChunkTo(file, AnimCache::AfsaMagic, seq);
797 afsbBufs.appendChunkTo(file, AnimCache::AfsbMagic, seq);
798 const auto r = fs.addFile(paths.anim(seq), file);
800 return makeError(r.error().code, std::format(
"anim {:04}-{:02}: {}", seq.id, seq.variation, r.error().message),
801 r.error().nativeError);
802 skelCopy.animFdids.push_back({seq.id, seq.variation, r->value});
804 skelCopy.boneFdids = boneFdids;
807 if (!skelBytes)
return std::unexpected{skelBytes.error()};
808 const auto r =
fs.addFile(paths.skel(), *skelBytes);
810 return makeError(r.error().code, std::format(
".skel: {}", r.error().message), r.error().nativeError);
811 stream.skeletonFdid.assign(1, r->value);
815 template <ClientVersion V>
816 Result<void> detail::M2<V>::_writeChunkedSkins(fs::FileSystem& fs,
const SatellitePaths& paths,
auto& stream)
const
818 stream.skinFdids.clear();
819 for (
const auto [i, skin] : std::views::enumerate(this->skins)) {
820 const auto skinBytes = skin.write();
821 if (!skinBytes)
return std::unexpected{skinBytes.error()};
822 const auto r = fs.addFile(paths.skin(
static_cast<std::uint32_t
>(i)), *skinBytes);
824 return makeError(r.error().code, std::format(
"skin {}: {}", i, r.error().message), r.error().nativeError);
825 stream.skinFdids.push_back(r->value);
827 for (
const auto [i, skin] : std::views::enumerate(this->lodSkins)) {
828 const auto skinBytes = skin.write();
829 if (!skinBytes)
return std::unexpected{skinBytes.error()};
830 const auto r = fs.addFile(paths.lodSkin(
static_cast<std::uint32_t
>(i) + 1), *skinBytes);
832 return makeError(r.error().code, std::format(
"lod skin {}: {}", i, r.error().message), r.error().nativeError);
833 stream.skinFdids.push_back(r->value);
838 template <ClientVersion V>
839 Result<void> detail::M2<V>::_writeChunkedPhys(fs::FileSystem& fs,
const SatellitePaths& paths,
auto& stream)
const
841 stream.physFdid.clear();
842 if (!this->phys.bytes.empty()) {
843 const auto r =
fs.addFile(paths.phys(), this->phys.bytes);
845 return makeError(r.error().code, std::format(
".phys: {}", r.error().message), r.error().nativeError);
846 stream.physFdid.push_back(r->value);
860 template <
typename Profile,
typename Root>
861 void validateProfileAgainstRoot(
const Profile& profile,
const Root& root,
ValidationReport& report) {
865 for (std::size_t i = 0; i < profile.submeshes.size() && !report.
full(); ++i) {
866 const auto& submesh = profile.submeshes[i];
867 if (std::size_t{submesh.boneComboIndex} + submesh.boneCount > root.boneLookupTable.size())
868 report.
addError(std::format(
"submeshes[{}]", i),
869 std::format(
"bone-lookup range [{}, {}) overruns the {} entries", submesh.boneComboIndex,
870 submesh.boneComboIndex + submesh.boneCount, root.boneLookupTable.size()));
873 for (std::size_t i = 0; i < profile.batches.size() && !report.
full(); ++i) {
874 const auto& batch = profile.batches[i];
875 const std::string path = std::format(
"batches[{}]", i);
876 if (batch.materialIndex >= root.materials.size())
877 report.
addError(path, std::format(
"material_index {} out of range: {} materials", batch.materialIndex,
878 root.materials.size()));
879 if (!formats::detail::isNoIndex(batch.colorIndex) && batch.colorIndex >= root.colors.size())
880 report.
addError(path, std::format(
"color_index {} out of range: {} colors", batch.colorIndex,
881 root.colors.size()));
889 if (std::size_t{batch.textureComboIndex} + batch.textureCount > root.textureLookupTable.size())
890 report.
addError(path, std::format(
"texture-lookup range [{}, {}) overruns the {} entries",
891 batch.textureComboIndex, batch.textureComboIndex + batch.textureCount,
892 root.textureLookupTable.size()));
893 const auto lookupStart = [&](std::uint16_t first,
const auto& table, std::string_view what) {
894 if (!table.empty() && first >= table.size())
895 report.
addError(path, std::format(
"{} start {} out of range: {} entries", what, first, table.size()));
897 lookupStart(batch.textureWeightComboIndex,
root.transparencyLookupTable,
"transparency-lookup");
898 lookupStart(batch.textureTransformComboIndex,
root.textureTransformsLookupTable,
899 "texture-transform-lookup");
904 template <ClientVersion V>
905 ValidationReport detail::M2<V>::validate()
const {
906 ValidationReport report;
908 const std::size_t mark = report.size();
910 report.prefixFrom(mark,
"root");
917 const auto* bones = &root.bones;
918 if constexpr (
requires { this->skel.boneBlock.bones; })
919 if (root.bones.empty() && !this->skel.boneBlock.bones.empty()) bones = &this->skel.boneBlock.bones;
920 formats::detail::validateOptionalIndexElements(root.boneLookupTable, bones->size(),
"root.bone_lookup_table",
921 "the model bones", report);
922 formats::detail::validateOptionalIndexElements(root.keyBoneLookup, bones->size(),
"root.key_bone_lookup",
923 "the model bones", report);
927 if constexpr (
requires { this->skins; }) {
928 const auto walk = [&](
const auto& views, std::string_view what) {
929 for (std::size_t i = 0; i < views.size() && !report.full(); ++i) {
930 const std::size_t mark = report.size();
931 formats::detail::validateEntity(views[i], report);
935 const std::size_t profileMark = report.size();
936 detail::validateProfileAgainstRoot(views[i].profile, root, report);
937 report.prefixFrom(profileMark,
"profile");
939 report.prefixFrom(mark, std::format(
"{}[{}]", what, i));
942 walk(this->skins,
"skins");
943 if constexpr (
requires { this->lodSkins; })
944 walk(this->lodSkins,
"lod_skins");
947 for (std::size_t i = 0; i < root.skinProfiles.size() && !report.full(); ++i) {
948 const std::size_t mark = report.size();
949 detail::validateProfileAgainstRoot(root.skinProfiles[i], root, report);
950 report.prefixFrom(mark, std::format(
"root.skin_profiles[{}]", i));
956 template <ClientVersion V>
957 Result<void> detail::M2<V>::ensureValid()
const {
958 return validate().toResult();
The .bone entity (namespace wowlib::formats::m2::bone), WoD+: a tiny chunked file (with a raw u32 pre...
The Legion+ chunked .m2 shell (namespace wowlib::formats::m2::chunked): M2ChunkedFile — the on-disk c...
Client version identity, the flavor axis that separates a client's CONTENT version from the engine ge...
The error-handling vocabulary: ErrorCode, Error and the Result<T> alias every fallible wowlib operati...
File identity types: the strong FileDataID and the FileKey a read request travels as.
The runtime facade over the static compositions — the primary welder binding surface of the fs layer.
Flag-testing convenience for the binary formats' bit-mask enums.
Binding-language identities welder's core does not name.
#define WOWLIB_CS_FAMILY_SURFACE
The C# rod's family-surface opt-in, spellable in every build.
The MD20 body entity (namespace wowlib::formats::m2::root): M2Root — the client's own name for the of...
constexpr welder::lang Cs
C#/.NET — the welder-csharp rod's identity (user-range slot 0), respelled for wowlib's annotation sit...
std::expected< T, Error > Result
Every fallible wowlib operation returns Result<T>; bindings translate the error branch into a target-...
std::unexpected< Error > makeError(ErrorCode code, std::string message, std::uint32_t nativeError=0)
Shorthand for constructing the error branch of a Result.
@ FileNotFound
The file exists nowhere in the overlay or storage.
@ InvalidEntityState
An entity's members disagree (e.g.
@ PathNotResolvable
No FileDataID is known for the given path (listfile miss).
@ FormatVersionMismatch
The file's version chunk disagrees with the requested version.
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.
The M2 family's satellite-file vocabulary (namespace wowlib::formats::m2), shared by the fs-level rea...
The .skel skeleton entity (namespace wowlib::formats::m2), 7.3+: a FIRST-CLASS entity with its own fi...
The external .skin file entity (namespace wowlib::formats::m2::skin), WotLK+: the 'SKIN' magic follow...
std::optional< std::string > path
The canonical client-internal path, if known.
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 ....
The conditional-base mechanism that gives a versioned chunked entity exactly the fields its client ve...