53#include <welder/vocabulary.hpp>
67 [[=welder::mark::exclude]]
80 std::function<std::span<const std::byte>(std::size_t i)>
sequenceBase;
115 template <
typename M>
concept InlineRecordMember = std::is_class_v<M> && !std::is_trivially_copyable_v<M> && !
133 template <
typename T, ClientVersion V>
136 else if constexpr (std::is_trivially_copyable_v<T>)
return sizeof(T);
139 std::size_t total = 0;
141 template for (
constexpr auto m : Members) {
143 "gated_by members are only supported at the entity top level");
145 using MT = [:std::meta::type_of(m):];
165 template <
typename Derived>
167 [[=welder::doc(
"Deserialize file bytes into this entity, replacing its "
168 "contents. Offsets resolve against the given buffer; "
169 "sequence-gated data is read inline.")]]
171 read(std::span<const std::byte> data [[=welder::doc(
"the file (or containing-chunk payload) bytes")]]) {
172 std::size_t cursor = 0;
173 auto& self =
static_cast<Derived&
>(*this);
182 [[=welder::mark::exclude]]
184 std::size_t cursor = 0;
185 auto& self =
static_cast<Derived&
>(*this);
190 [[=welder::doc(
"Serialize this entity in wowlib's canonical layout (an "
191 "offset format has no byte-perfect round-trip guarantee; "
192 "a written entity re-reads equal instead)."),
193 =welder::returns(
"the file bytes")]]
202 [[=welder::mark::exclude]]
205 if (
auto r =
_writeImage(
static_cast<const Derived&
>(*
this), out, ctx); !r)
return std::unexpected{r.error()};
214 [[=welder::mark::exclude]]
223 [[=welder::mark::exclude]]
230 Check the logical integrity contracts this file must satisfy to LOAD in
231 the client — companion-array counts, lookup ranges — which write()
232 deliberately never enforces. Call it before writing when you want to
233 know the result will load. A file read from a client and left
234 unmodified reports no errors; warnings mark states real client files
236 =welder::returns("every violated contract, in member order")]]
244 [[=welder::doc(
"Validate and raise on the first error instead of returning "
245 "a report — the assert-style face of validate()."),
246 =welder::returns(
"nothing; raises when validate() finds any error")]]
256 [[=welder::mark::exclude]]
257 std::size_t imageSize()
const {
258 const auto& self =
static_cast<const Derived&
>(*this);
260 std::size_t total = 0;
261 template for (
constexpr auto m : Members) {
263 if (_memberPresent<m>(self)) {
264 using MT = [:std::meta::type_of(m):];
282 [[=welder::mark::exclude]]
283 static consteval std::size_t memberOffset(std::string_view name) {
286 static constexpr auto Order = _memberOrder<Derived>();
289 template for (
constexpr std::size_t idx : Order) {
290 constexpr auto m = Members[idx];
292 if (!found && std::meta::identifier_of(m) == name) found =
true;
295 "member_offset: a gated_by member precedes the target";
296 using MT = [:std::meta::type_of(m):];
301 if (!found)
throw "member_offset: the entity has no such member";
305 [[=welder::mark::exclude]]
306 bool operator==(
const M2OffsetBlock&)
const =
default;
312 std::uint32_t count = 0;
313 std::uint32_t offset = 0;
316 static_assert(
sizeof(M2ArrayRef) == 8 && std::is_trivially_copyable_v<M2ArrayRef>);
323 static consteval std::optional<formats::detail::OffsetAfterSpec>
324 _offsetAnchorOf(std::meta::info member) {
326 if (anns.empty())
return std::nullopt;
327 return std::meta::extract<formats::detail::OffsetAfterSpec>(anns[0]);
344 template <
typename T>
347 const auto own = std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked());
348 const auto isOwn = [&](std::meta::info m) {
350 if (o == m)
return true;
353 for (std::size_t i = 0; i < members.size(); ++i)
355 throw "an offset entity's trait-base member needs =offset_after(\"own member\")";
356 std::vector<std::size_t> order;
358 for (std::size_t i = 0; i < members.size(); ++i)
359 if (members[i] == o) order.push_back(i);
360 if (!std::meta::has_identifier(o))
continue;
361 for (std::size_t i = 0; i < members.size(); ++i)
362 if (!isOwn(members[i]))
363 if (
auto a =
_offsetAnchorOf(members[i]); a && a->view() == std::meta::identifier_of(o)) order.
366 if (order.size() != members.size())
throw "an offset_after anchor names no own member of the entity";
367 return std::define_static_array(order);
379 template <std::meta::info Mem,
typename T>
380 static bool _memberPresent(
const T& rec) {
383 static_assert(
requires { rec.globalFlags; },
"gated_by members need a global_flags member to gate on");
384 return (
static_cast<std::uint32_t
>(rec.globalFlags) & gate->mask) != 0;
397 template <
bool IsSequenceData,
typename T>
398 static bool _resolvesExternally(
const T& rec) {
399 if constexpr (!IsSequenceData)
return false;
400 else if constexpr (
requires { rec.globalSequence; })
return rec.globalSequence == 0xFFFF;
418 template <
typename T>
420 const std::span<const std::byte> image,
422 const std::span<const std::byte> base,
426 std::optional<Error> failed;
427 template for (
constexpr std::size_t idx : Order) {
428 constexpr auto m = Members[idx];
430 constexpr std::string_view name = std::meta::identifier_of(m);
437 if (failed)
return std::unexpected{*failed};
452 template <
typename M>
454 const std::span<const std::byte> image,
456 const std::span<const std::byte> base,
457 const OffsetReadContext& ctx,
459 const std::string_view what) {
460 if constexpr (OffsetArrayMember<M>)
476 template <
typename M>
478 const std::span<const std::byte> image,
480 const std::string_view what) {
481 if (image.size() <
sizeof(M) || image.size() -
sizeof(M) < cursor)
483 std::format(
"{}-byte value at image offset {:#x} overruns the image",
sizeof(M), cursor));
484 std::memcpy(&dst, image.data() + cursor,
sizeof(M));
497 const std::span<const std::byte> image,
499 const std::string_view what) {
500 if (cursor > image.size() || image.size() - cursor <
sizeof(M2ArrayRef))
502 std::memcpy(&ref, image.data() + cursor,
sizeof ref);
503 cursor +=
sizeof ref;
518 template <
typename M>
520 const std::span<const std::byte> image,
522 const std::span<const std::byte> base,
523 const OffsetReadContext& ctx,
525 const std::string_view what) {
527 if (
auto r =
_readArrayRef(ref, image, cursor, what); !r)
return r;
541 const M2ArrayRef ref,
542 const std::span<const std::byte> base,
543 const std::string_view what) {
545 if (ref.count == 0)
return {};
546 if (std::uint64_t{ref.offset} + ref.count > base.size())
548 std::format(
"string [{} at {:#x}] overruns the {}-byte buffer", ref.count, ref.offset,
550 const auto* chars =
reinterpret_cast<const char*
>(base.data() + ref.offset);
551 const auto* nul =
static_cast<const char*
>(std::memchr(chars, 0, ref.count));
552 dst.assign(chars, nul ?
static_cast<std::size_t
>(nul - chars) : ref.count);
569 template <
typename M>
571 const M2ArrayRef ref,
572 const std::span<const std::byte> base,
575 const std::string_view what) {
576 using U =
typename M::value_type;
578 if (ref.count == 0)
return {};
581 if (base.empty())
return {};
583 if (std::uint64_t{ref.offset} + std::uint64_t{ref.count} * elem > base.size())
585 std::format(
"array [{} x {} at {:#x}] overruns the {}-byte buffer", ref.count, elem,
586 ref.offset, base.size()));
587 dst.resize(ref.count);
588 if constexpr (std::is_trivially_copyable_v<U>) {
589 std::memcpy(dst.data(), base.data() + ref.offset, std::size_t{ref.count} * elem);
607 template <
typename M>
609 const M2ArrayRef ref,
610 const std::span<const std::byte> base,
613 const std::string_view what) {
614 using U =
typename M::value_type;
616 for (std::size_t i = 0; i < dst.size(); ++i) {
617 const auto elementImage = base.subspan(ref.offset + i * elem, elem);
619 std::size_t elementCursor = 0;
620 if (
auto r =
_readMember(dst[i], elementImage, elementCursor, elementBase, ctx,
false, what); !r)
return r;
636 const std::size_t imagePos,
639 const std::size_t n) {
640 std::memcpy(image.data() + imagePos + cursor, bytes, n);
653 target.resize((target.size() + 15) / 16 * 16);
654 const std::size_t at = target.size();
655 target.resize(target.size() + bytes);
669 buffer.resize(self.imageSize());
670 std::size_t cursor = 0;
671 if (
auto r = _writeMembers(self, buffer, 0, cursor, buffer, ctx); !r)
return r;
672 out.insert(out.end(), buffer.begin(), buffer.end());
687 template <
typename T>
690 const std::size_t imagePos,
693 const OffsetWriteContext& ctx) {
695 static constexpr auto Order = _memberOrder<T>();
696 std::optional<Error> failed;
697 template for (
constexpr std::size_t idx : Order) {
698 constexpr auto m = Members[idx];
700 constexpr std::string_view name = std::meta::identifier_of(m);
702 if (!failed && _memberPresent<m>(src))
703 if (
auto r = _writeMember(src.[:m:], image, imagePos, cursor, blocks, ctx,
704 _resolvesExternally<isSeq>(src), name); !r) failed = r.error();
707 if (failed)
return std::unexpected{*failed};
723 template <
typename M>
726 const std::size_t imagePos,
729 const OffsetWriteContext& ctx,
731 const std::string_view what) {
732 if constexpr (OffsetArrayMember<M>)
733 return _writeArrayMember(src, image, imagePos, cursor, blocks, ctx, external, what);
734 else if constexpr (InlineRecordMember<M>)
return _writeMembers(src, image, imagePos, cursor, blocks, ctx);
736 static_assert(InlineScalarMember<M>,
"offset members are arrays, records, or scalars");
737 _putBytes(image, imagePos, cursor, &src,
sizeof(M));
754 template <
typename M>
757 const std::size_t imagePos,
760 const OffsetWriteContext& ctx,
762 const std::string_view what) {
765 return _writeVectorBlock(src, image, imagePos, cursor, blocks, ctx, external, what);
777 static Result<void> _writeStringBlock(
const std::string& src,
779 const std::size_t imagePos,
782 const std::size_t at = _allocBlock(blocks, src.size() + 1);
783 std::memcpy(blocks.data() + at, src.data(), src.size());
784 blocks[at + src.size()] = std::byte{0};
785 const M2ArrayRef ref{
static_cast<std::uint32_t
>(src.size() + 1),
static_cast<std::uint32_t
>(at)};
786 _putBytes(image, imagePos, cursor, &ref,
sizeof ref);
804 template <
typename M>
807 const std::size_t imagePos,
812 const std::string_view what) {
813 using U =
typename M::value_type;
820 const std::size_t at =
_allocBlock(blocks, src.size() * elem);
821 const M2ArrayRef ref{
static_cast<std::uint32_t
>(src.size()),
static_cast<std::uint32_t
>(at)};
822 _putBytes(image, imagePos, cursor, &ref,
sizeof ref);
823 if constexpr (std::is_trivially_copyable_v<U>) {
824 std::memcpy(blocks.data() + at, src.data(), src.size() * elem);
844 template <
typename M>
846 const std::size_t blockAt,
850 const std::string_view what) {
851 using U =
typename M::value_type;
853 for (std::size_t i = 0; i < src.size(); ++i) {
855 std::size_t elementCursor = 0;
856 if (
auto r =
_writeMember(src[i], blocks, blockAt + i * elem, elementCursor, sink ? *sink : blocks, ctx,
857 false, what); !r)
return r;
869 static std::unexpected<Error> _offsetError(
const ErrorCode code,
870 const std::string_view member,
871 const std::string_view what) {
872 return makeError(code, std::format(
"offset member '{}': {}", member, what));
878 template <
typename E>
concept OffsetEntity = std::derived_from<E, M2OffsetBase> &&
requires {
879 { E::Version } -> std::convertible_to<ClientVersion>;
The owning byte buffer file contents are read into.
The chunk framework, vocabulary and engine in one header.
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...
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.
ErrorCode
Machine-readable failure category carried by every Error.
@ OffsetOutOfBounds
An offset array (M2Array) points outside its base buffer.
std::vector< std::byte > FileBuffer
Owning byte buffer for file contents read out of a client storage.
std::string error
The failure diagnostic; empty on success and on skips.
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 ...