212 if (image ==
nullptr || size <= kDescriptorPc) {
215 const uint8_t* d = image + kDescriptorPc;
216 const size_t available = std::min(kDescriptorSize, size - kDescriptorPc);
217 const bool signature_prefix =
218 available >= 5 && std::memcmp(d,
"OOSOW", 5) == 0;
221 const bool directory_fingerprint =
222 available >= kUsedSize && U16(d + 0x0A) == kHeaderSize &&
223 U16(d + 0x0C) == kUsedSize && U16(d + 0x0E) == 192 &&
224 U16(d + 0x10) == 160 && U16(d + 0x12) == kEntryCount &&
225 U16(d + kHeaderSize) == 1 && U16(d + kHeaderSize + 2) == 1;
226 if (!signature_prefix && !directory_fingerprint) {
229 if (available != kDescriptorSize) {
230 return Invalid(
"Truncated Lyra descriptor region");
232 if (std::memcmp(d, kMagic, 8) != 0) {
233 return Invalid(
"Unknown Lyra descriptor magic");
236 const uint16_t revision = U16(d + 8);
237 const uint16_t capacity = U16(d + 0x0E);
238 const uint16_t active = U16(d + 0x10);
239 auto bad = [&](std::string error) {
240 return Invalid(std::move(error), revision, capacity, active);
243 return bad(
"Unsupported Lyra ROM-format revision");
244 if (U16(d + 0x0A) != kHeaderSize || U16(d + 0x0C) != kUsedSize ||
245 U16(d + 0x12) != kEntryCount) {
246 return bad(
"Invalid Lyra descriptor length or directory count");
248 if (capacity != 192 || active != 160) {
249 return bad(
"Invalid Lyra capacity or active count");
251 if (d[0x14] != 0x3C || d[0x15] != 1 || U16(d + 0x16) != 384) {
252 return bad(
"Unsupported Lyra sprite bank, flags or phase stride");
254 if (U32(d + 0x18) != size) {
255 return bad(
"Lyra image length mismatch");
257 if (image[0x140145] != 3 || image[0x1423FF] == 0xEA) {
258 return bad(
"Conflicting ZS version or older $EA tail identity");
260 std::array<uint8_t, kDescriptorSize> checked;
261 std::copy_n(d, kDescriptorSize, checked.begin());
262 std::fill_n(checked.begin() + 0x1C, 4, 0);
263 if (Crc32(checked.data(), checked.size()) != U32(d + 0x1C)) {
264 return bad(
"Lyra descriptor CRC mismatch");
266 if (std::any_of(d + kUsedSize, d + kDescriptorSize,
267 [](uint8_t b) {
return b != 0; })) {
268 return bad(
"Nonzero Lyra descriptor padding");
277 result.
entries.reserve(kEntryCount);
278 for (
size_t i = 0; i < kEntryCount; ++i) {
279 const uint8_t* p = d + kHeaderSize + i * kEntrySize;
280 const auto& spec = kSpecs[i];
282 U16(p + 8), U16(p + 10), U32(p + 12)};
283 if (e.id != spec.id || e.kind != spec.kind || e.count != spec.count ||
284 e.stride != spec.stride) {
285 return bad(
"Invalid Lyra directory entry");
287 if (!ToPc(e.snes_address, &e.pc_address)) {
288 return bad(
"Invalid Lyra directory bank or address");
290 const uint32_t length =
static_cast<uint32_t
>(e.count) * e.stride;
291 if ((e.snes_address & 0xFFFF) + length > 0x10000) {
292 return bad(
"Lyra table crosses bank");
294 if (e.pc_address > size || length > size - e.pc_address) {
295 return bad(
"Lyra directory outside image extent");
298 if (i == 29 && e.snes_address != 0x02F88D) {
299 return bad(
"Invalid Lyra area-size table address");
301 if (i == 30 && e.snes_address != 0x09C635) {
302 return bad(
"Invalid Lyra palette-set table address");
305 (e.pc_address < 0x144000 || e.pc_address + length > 0x146840)) {
306 return bad(
"Lyra table outside owned bank-28 reservation");
308 if (i < 19 && (e.snes_address >> 16) != 0x28) {
309 return bad(
"Lyra Pool table bank mismatch");
311 if (i > 0 && i < 19 &&
312 result.
entries.back().pc_address +
315 return bad(
"Lyra Pool table overlap or gap");
317 }
else if (i == 31) {
318 if (e.pc_address != 0x1E4000) {
319 return bad(
"Lyra sprite list region changed");
321 }
else if (e.pc_address != kStreamWindows[i - 32].first) {
322 return bad(
"Lyra stream region changed");
324 for (
const auto& prior : result.
entries) {
325 const uint32_t prior_end = prior.pc_address + prior.count * prior.stride;
326 if (e.pc_address < prior_end &&
327 prior.pc_address < e.pc_address + length) {
328 return bad(
"Lyra directory overlap");
333 if (result.
entries[16].pc_address - result.
entries[15].pc_address != 384 ||
334 result.
entries[17].pc_address - result.
entries[16].pc_address != 384) {
335 return bad(
"Lyra sprite phase stride mismatch");
337 for (
const auto& e : result.
entries) {
338 const size_t length =
static_cast<size_t>(e.count) * e.stride;
339 if (Crc32(image + e.pc_address, length) != e.crc32) {
340 return bad(
"Lyra data CRC mismatch");
343 for (
size_t map = 0; map < 192; ++map) {
344 std::array<uint8_t, 256> high{};
345 std::array<uint8_t, 256> low{};
346 for (
size_t table : {19U, 20U}) {
347 const auto& e = result.
entries[table];
348 uint32_t stream_pc = 0;
349 if (!StreamPointerPc(size, U24(image + e.pc_address + map * 3),
351 return bad(
"Lyra map pointer outside owned stream regions");
353 if (!DecodeStream(image, size, stream_pc, table == 19 ? &high : &low)) {
354 return bad(
"Lyra compressed map stream invalid");
357 for (
size_t tile = 0; tile < high.size(); ++tile) {
358 if ((
static_cast<uint16_t
>(high[tile]) << 8 | low[tile]) >= 17728) {
359 return bad(
"Lyra Tile32 reference exceeds existing capacity");
362 for (
size_t table : {15U, 16U, 17U}) {
363 const auto& e = result.
entries[table];
364 if (!CheckSpritePointer(image, size,
365 U16(image + e.pc_address + map * 2))) {
366 return bad(
"Lyra sprite pointer outside list region or unterminated");
370 const auto& parent = result.
entries[6];
371 const auto& role = result.
entries[28];
372 const auto& overlay = result.
entries[4];
373 const auto& area_size = result.
entries[29];
374 if (image[parent.pc_address + map] != map ||
375 image[role.pc_address + map] != 3 ||
376 U16(image + overlay.pc_address + map * 2) != 0x00FF ||
377 image[area_size.pc_address + map] != 0) {
378 return bad(
"Lyra inactive tail role or parent is invalid");
412 if (!high_record || !low_record || high_record->size() != 3 ||
413 low_record->size() != 3) {
416 std::array<uint8_t, 256> high{};
417 std::array<uint8_t, 256> low{};
418 uint32_t high_pc = 0;
420 if (!StreamPointerPc(size, U24(high_record->data()), &high_pc) ||
421 !StreamPointerPc(size, U24(low_record->data()), &low_pc) ||
422 !DecodeStream(image, size, high_pc, &high) ||
423 !DecodeStream(image, size, low_pc, &low)) {
426 std::array<uint16_t, 256> tiles{};
427 for (
size_t i = 0; i < tiles.size(); ++i) {
428 tiles[i] =
static_cast<uint16_t
>((high[i] << 8) | low[i]);
429 if (tiles[i] >= 17728) {