6#include <unordered_map>
7#include <unordered_set>
14 constexpr std::uint32_t
Flag = 0x8000'0000U;
15 constexpr std::uint32_t
EndOfChain = 0xFFFF'FFFFU;
20 return { code,
offset, std::move(detail), std::nullopt };
30 std::uint32_t
u32(std::span<const std::byte>
bytes,
31 std::size_t
offset)
noexcept
33 std::uint32_t result = 0;
34 for (
unsigned i = 0;
i < 4; ++
i)
35 result |= std::to_integer<std::uint32_t>(
bytes[
offset +
i])
40 std::uint16_t
u16(std::span<const std::byte>
bytes,
41 std::size_t
offset)
noexcept
43 return static_cast<std::uint16_t
>(
45 (std::to_integer<std::uint16_t>(
bytes[
offset + 1]) << 8U));
48 std::uint16_t
texturePage8(std::uint16_t
x, std::uint16_t
y)
noexcept
51 return static_cast<std::uint16_t
>(
52 (1U << 7U) | ((
y & 0x100U) >> 4U) |
53 ((
x & 0x3FFU) >> 6U) | ((
y & 0x200U) << 2U));
56 std::expected<std::size_t, MeshDataError>
wordOffset(
57 std::span<const std::byte>
bytes, std::uint32_t
value,
64 fieldOffset,
"HMD word offset leaves the owning payload"));
65 return static_cast<std::size_t
>(
value) * 4;
69 std::size_t
offset)
noexcept
74 std::bit_cast<std::int16_t>(
static_cast<std::uint16_t
>(
a)),
75 std::bit_cast<std::int16_t>(
static_cast<std::uint16_t
>(
a >> 16)),
76 std::bit_cast<std::int16_t>(
static_cast<std::uint16_t
>(
b)),
77 std::bit_cast<std::int16_t>(
static_cast<std::uint16_t
>(
b >> 16))
109 "HMD requires an owning DAT payload"));
110 const std::span<const std::byte>
data(*owner);
113 "HMD header requires four little-endian DWORDs"));
116 "Relocated process pointers are not an HMD disk payload"));
119 result.owner_ = std::move(owner);
121 result.versionId_ =
u32(
data, 0);
125 12,
"HMD block table is truncated"));
128 12,
"HMD block table exceeds configured record budget"));
139 *
headerSection,
"HMD header count exceeds configured budget"));
148 cursor,
"Missing HMD primitive header field count"));
155 header.offset,
"HMD primitive header fields are truncated"));
158 header.offset,
"HMD header fields exceed configured budget"));
171 result.headers_.push_back(std::move(
header));
182 result.blockRoots_.push_back(std::nullopt);
186 if (!
root)
return std::unexpected(
root.error());
187 result.blockRoots_.push_back(*
root);
188 std::optional<std::size_t>
next = *
root;
192 const auto position = *
next;
195 position,
"Cycle in HMD primitive next chain"));
200 position,
"HMD primitives exceed configured budget"));
212 u32(
data, position + 4), position + 4, 4);
213 if (!headerOffset)
return std::unexpected(headerOffset.error());
217 position + 4,
"Primitive does not reference a declared header"));
225 position + 8,
"HMD data section count exceeds available bytes"));
228 position + 8,
"HMD sections exceed configured budget"));
234 cursor,
"HMD data section header is truncated"));
239 cursor + 4,
"HMD section cannot be shorter than its header"));
242 cursor + 4,
"HMD data section extends beyond payload"));
244 static_cast<std::uint16_t
>((
countSize >> 16) & 0x7FFFU),
250 result.primitives_.push_back(std::move(
primitive));
258 {
return blockRoots_; }
262 {
return primitives_; }
264 {
return owner_ ? std::span<const std::byte>(*owner_) : std::span<const std::byte>{}; }
273 "HMD primitive/section index is out of range"));
276 static_cast<std::size_t
>(
section.sizeWords) * 4);
287 const auto type =
section.type;
290 if (((type >> 24) & 0xFU) != 0 || ((type >> 3) & 7U) != 1 ||
291 (type & (0x40U | 0x100U)) != 0)
293 section.offset,
"Section is not a supported normal-bearing triangle"));
296 section.offset,
"Triangle index exceeds section polygon count"));
299 section.offset,
"Triangle section requires a polygon word offset"));
301 if (
header.fields.size() < 3)
303 header.offset,
"Triangle header needs polygon, vertex and normal fields"));
304 std::array<std::size_t, 3>
bases{};
305 for (std::size_t
i = 0;
i <
bases.size(); ++
i)
307 if (!
header.fields[
i].isOffset())
309 header.offset + 4 +
i * 4,
"Triangle field is a literal, not an offset"));
312 const bool textured = (type & 1U) != 0;
314 const bool gouraud = (type & 4U) != 0;
315 const bool tiled = (type & 0x200U) != 0;
325 section.offset + 8,
"Polygon offset leaves owning HMD payload"));
329 polygonStart,
"Triangle count extends beyond owning HMD payload"));
333 if (colours == 0) result.
colours.fill(0x00FF'FFFFU);
336 for (std::size_t
i = 0;
i < colours; ++
i,
cursor += 4)
342 for (std::size_t
i = 0;
i < 3; ++
i,
cursor += 4)
346 static_cast<std::uint8_t
>(
value >> 8) };
352 for (std::size_t
i = 0;
i < 3; ++
i,
cursor += 4)
364 static_cast<std::uint16_t
>(second),
static_cast<std::uint16_t
>(second >> 16) };
367 for (std::size_t
i = 0;
i < 3; ++
i)
374 polygonStart,
"Triangle references an out-of-payload SVECTOR"));
396 -> std::expected<std::size_t, MeshDataError>
400 fieldOffset,
"MIMe word offset leaves the diff section"));
410 -> std::expected<void, MeshDataError>
415 if (
header.fields.size() < 4)
417 header.offset,
"MIMe header needs a diff-block base field"));
418 if (!
header.fields[3].isOffset())
421 "MIMe diff-block base must be a word offset"));
425 return std::unexpected(
blockBase.error());
428 section.offset,
"MIMe record offsets exceed section size"));
431 for (std::size_t item = 0; item <
section.elementCount; ++item)
437 return std::unexpected(current.error());
440 ?
static_cast<std::size_t
>(
counts & 0xFFFFU)
441 :
static_cast<std::size_t
>(
counts >> 16U);
444 *current,
"MIMe current-block diff offsets are truncated"));
449 return std::unexpected(
error(
451 "MIMe diff-block budget exceeded"));
461 return std::unexpected(
error(
463 *
blockOffset + 4U,
"MIMe SVECTOR budget exceeded"));
466 *
blockOffset,
"MIMe diff SVECTOR run is truncated"));
484 for (
const auto&
root : blockRoots_)
488 std::vector<std::size_t>
chain;
489 std::optional<std::size_t> current = *
root;
494 return std::unexpected(
error(
496 "MIMe chain references an unknown primitive"));
498 current = primitives_[
found->second].nextOffset;
507 const auto type =
section.type & 0xFF7F'FFFFU;
508 if (type == 0x0401'0020U)
512 return std::unexpected(
decoded.error());
514 else if (type == 0x0401'0021U)
518 return std::unexpected(
decoded.error());
525 std::vector<HmdMimePose>
poses(poseCount);
540 std::vector<HmdTextureImage> result;
541 for (
const auto&
primitive : primitives_)
548 if (((
section.type >> 24U) & 0xFU) != 2U ||
551 if (
header.fields.size() < 2)
553 header.offset,
"HMD image header needs IMAGE TOP and CLUT TOP fields"));
554 if (!
header.fields[0].isOffset() || !
header.fields[1].isOffset())
556 header.offset + 4,
"HMD image header bases must be word offsets"));
557 const auto imageBase =
static_cast<std::size_t
>(
559 const auto clutBase =
static_cast<std::size_t
>(
566 static_cast<std::size_t
>(
section.elementCount) * 6U;
569 section.offset,
"GsUIMG1 records exceed HMD image section size"));
571 static_cast<std::size_t
>(
section.sizeWords) * 4U);
573 for (std::size_t
i = 0;
i <
section.elementCount; ++
i)
575 const auto record = 2U +
i * 6U;
576 const auto xy =
u32(
raw, record * 4U);
577 const auto wh =
u32(
raw, (record + 1U) * 4U);
580 const auto rawX =
static_cast<std::uint16_t
>(
xy);
581 const auto rawY =
static_cast<std::uint16_t
>(
xy >> 16U);
583 const auto height16 =
static_cast<std::uint16_t
>(
wh >> 16U);
584 const std::uint32_t
width =
588 width > std::numeric_limits<std::size_t>::max() /
height)
591 "HMD 8-bit texture has invalid dimensions"));
594 if (
pixelCount > std::numeric_limits<std::size_t>::max() / 4U)
597 "HMD texture RGBA size overflows host range"));
601 section.offset + (record + 2U) * 4U,
602 "HMD image index leaves IMAGE TOP section"));
606 section.offset + (record + 5U) * 4U,
607 "HMD CLUT index leaves CLUT TOP section"));
611 static_cast<std::size_t
>(
clutIndex) * 4U;
615 section.offset,
"HMD image or 256-entry CLUT is truncated"));
621 image.logicalX =
static_cast<std::int32_t
>((rawX & 0x3FU) << 1U);
622 image.logicalY =
static_cast<std::int32_t
>(rawY & 0xFFU);
628 const auto duplicate = std::find_if(result.begin(), result.end(),
631 return existing.texturePage == image.texturePage &&
632 existing.rawX == rawX && existing.rawY == rawY;
645 return static_cast<std::uint8_t
>(
646 (
static_cast<std::uint32_t
>(
value) * 255U) / 31U);
653 result.push_back(std::move(
image));
666 0,
"Cannot inspect HMD DAT item",
metadata.error() });
669 0,
"DAT item is not an unmapped HMD payload"));
670 const auto payload =
source.load(
id);
673 0,
"Cannot load HMD DAT item", payload.error() });
std::vector< std::uint8_t > data
LegacyBitmapMetadata metadata
std::vector< std::byte > bytes
std::shared_ptr< const data::HmdTextureImage > source
std::span< const HmdPrimitive > primitives() const noexcept
std::expected< std::vector< HmdTextureImage >, MeshDataError > textureImages() const
std::expected< std::span< const std::byte >, MeshDataError > sectionBytes(std::size_t primitiveIndex, std::size_t sectionIndex) const
std::span< const std::optional< std::size_t > > blockRoots() const noexcept
std::span< const HmdPrimitiveHeader > headers() const noexcept
std::expected< std::vector< HmdMimePose >, MeshDataError > mimePoses(HmdMimeLimits limits={}) const
std::uint32_t versionId() const noexcept
std::expected< HmdTriangle, MeshDataError > triangle(std::size_t primitiveIndex, std::size_t sectionIndex, std::size_t triangleIndex) const
std::span< const std::byte > bytes() const noexcept
static std::expected< LegacyMeshData, MeshDataError > parse(SharedDataBytes bytes, MeshParseLimits limits={})
std::expected< LegacyMeshData, MeshDataError > openLegacyMeshData(const DataSource &source, DataId id, MeshParseLimits limits)
std::shared_ptr< const DataBytes > SharedDataBytes
std::string_view meshDataErrorCodeName(MeshDataErrorCode code) noexcept
std::size_t maximumVectors
std::size_t maximumDiffBlocks
std::uint16_t texturePage
std::array< HmdTexturePoint, 3 > texturePoints
std::array< std::uint16_t, 3 > normalIndices
std::array< HmdShortVector, 3 > normals
std::array< std::uint32_t, 3 > colours
std::array< HmdShortVector, 3 > vertices
std::uint16_t texturePage
std::array< std::uint16_t, 3 > vertexIndices
std::size_t maximumHeaderFields
std::size_t maximumSections
std::size_t maximumPrimitives