#pragma once // Minimal big/little-endian reader and writer for the native package formats, // ported from ReSkateStudio's sdk/io so the runtime can rebuild TOCs and // bundles without pulling in the editor's asset database. #include #include #include #include #include #include #include #include #include #include namespace dingosdk::frostbite { enum class Endian { little, big }; struct Guid { std::array bytes{}; auto operator<=>(const Guid&) const = default; [[nodiscard]] std::string string() const; }; struct Sha1 { std::array bytes{}; auto operator<=>(const Sha1&) const = default; }; class BinaryReader final { public: explicit BinaryReader(std::span data) : data_(data) {} [[nodiscard]] std::size_t position() const noexcept { return position_; } [[nodiscard]] std::size_t size() const noexcept { return data_.size(); } [[nodiscard]] std::size_t remaining() const noexcept { return data_.size() - position_; } void seek(std::size_t position) { if (position > data_.size()) throw std::out_of_range("Native reader seek is out of range"); position_ = position; } void skip(std::ptrdiff_t amount) { const auto target = static_cast(position_) + amount; if (target < 0) throw std::out_of_range("Native reader skipped before the start"); seek(static_cast(target)); } [[nodiscard]] std::uint8_t u8() { return std::to_integer(view(1)[0]); } [[nodiscard]] std::int8_t i8() { return static_cast(u8()); } [[nodiscard]] std::uint16_t u16(Endian endian = Endian::little) { return integer(endian); } [[nodiscard]] std::int16_t i16(Endian endian = Endian::little) { return static_cast(integer(endian)); } [[nodiscard]] std::uint32_t u32(Endian endian = Endian::little) { return integer(endian); } [[nodiscard]] std::int32_t i32(Endian endian = Endian::little) { return static_cast(integer(endian)); } [[nodiscard]] std::uint64_t u64(Endian endian = Endian::little) { return integer(endian); } [[nodiscard]] std::int64_t i64(Endian endian = Endian::little) { return static_cast(integer(endian)); } [[nodiscard]] float f32(Endian endian = Endian::little) { return std::bit_cast(integer(endian)); } [[nodiscard]] double f64(Endian endian = Endian::little) { return std::bit_cast(integer(endian)); } // Stops at the first NUL but always consumes the whole field. [[nodiscard]] std::string fixed_string(std::size_t length) { const auto raw = view(length); std::string value; for (const auto byte : raw) { const auto ch = std::to_integer(byte); if (!ch) break; value.push_back(static_cast(ch)); } return value; } [[nodiscard]] std::string c_string() { std::string value; while (true) { const auto ch = u8(); if (!ch) return value; value.push_back(static_cast(ch)); } } [[nodiscard]] Sha1 sha1() { Sha1 value; const auto raw = view(value.bytes.size()); std::copy(raw.begin(), raw.end(), value.bytes.begin()); return value; } [[nodiscard]] Guid guid(Endian endian = Endian::little) { Guid value; const auto raw = view(value.bytes.size()); // The native little-endian form stores the first three fields swapped. if (endian == Endian::big) std::reverse_copy(raw.begin(), raw.end(), value.bytes.begin()); else std::copy(raw.begin(), raw.end(), value.bytes.begin()); return value; } [[nodiscard]] std::span view(std::size_t length) { if (length > remaining()) throw std::out_of_range("Native reader ran past the end"); const auto result = data_.subspan(position_, length); position_ += length; return result; } [[nodiscard]] std::vector bytes(std::size_t length) { const auto raw = view(length); return {raw.begin(), raw.end()}; } private: template [[nodiscard]] Type integer(Endian endian) { const auto raw = view(sizeof(Type)); Type value{}; for (std::size_t index = 0; index < sizeof(Type); ++index) { const auto byte = static_cast(std::to_integer(raw[index])); value |= endian == Endian::big ? byte << ((sizeof(Type) - 1 - index) * 8) : byte << (index * 8); } return value; } std::span data_; std::size_t position_{}; }; class BinaryWriter final { public: [[nodiscard]] std::size_t position() const noexcept { return position_; } [[nodiscard]] const std::vector& data() const noexcept { return data_; } [[nodiscard]] std::vector take() { return std::move(data_); } void seek(std::size_t position) { if (position > data_.size()) data_.resize(position); position_ = position; } void u8(std::uint8_t value) { write(&value, 1); } void u16(std::uint16_t value, Endian endian = Endian::little) { integer(value, endian); } void u32(std::uint32_t value, Endian endian = Endian::little) { integer(value, endian); } void i32(std::int32_t value, Endian endian = Endian::little) { integer(static_cast(value), endian); } void u64(std::uint64_t value, Endian endian = Endian::little) { integer(value, endian); } void sha1(const Sha1& value) { bytes(value.bytes); } // Mirrors the reader so a value read back writes to the same bytes. void guid(const Guid& value, Endian endian = Endian::little) { if (endian == Endian::little) { bytes(value.bytes); return; } std::array raw{}; std::reverse_copy(value.bytes.begin(), value.bytes.end(), raw.begin()); bytes(raw); } void bytes(std::span value) { write(value.data(), value.size()); } private: template void integer(Type value, Endian endian) { std::array raw{}; for (std::size_t index = 0; index < sizeof(Type); ++index) { const auto shift = endian == Endian::big ? (sizeof(Type) - 1 - index) * 8 : index * 8; raw[index] = static_cast((value >> shift) & 0xFF); } write(raw.data(), raw.size()); } void write(const void* source, std::size_t length) { if (!length) return; if (position_ + length > data_.size()) data_.resize(position_ + length); std::memcpy(data_.data() + position_, source, length); position_ += length; } std::vector data_; std::size_t position_{}; }; } // namespace dingosdk::frostbite