7#include "absl/strings/str_format.h"
57static constexpr uint8_t kSubtype1TileLengths[0xF8] = {
58 4, 8, 8, 8, 8, 8, 8, 4, 4, 5, 5, 5, 5, 5, 5, 5,
59 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
60 5, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6,
61 6, 1, 1, 16, 1, 1, 16, 16, 6, 8, 12, 12, 8, 8, 4, 3,
62 3, 3, 3, 3, 3, 3, 3, 15, 9, 8, 8, 4, 9, 16, 16, 16,
63 1, 18, 18, 4, 1, 8, 8, 1, 1, 1, 1, 18, 18, 15, 4, 3,
64 4, 8, 8, 8, 8, 8, 8, 4, 4, 3, 1, 1, 6, 6, 1, 1,
65 16, 1, 1, 16, 16, 8, 16, 16, 4, 1, 1, 4, 1, 4, 1, 8,
66 8, 12, 12, 12, 12, 18, 18, 8, 12, 4, 3, 3, 3, 1, 1, 4,
67 8, 8, 4, 4, 16, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1,
68 1, 1, 1, 1, 24, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
69 1, 1, 16, 3, 3, 8, 8, 8, 4, 4, 16, 4, 4, 4, 1, 1,
70 1, 68, 1, 1, 8, 8, 8, 8, 8, 8, 8, 1, 1, 24, 24, 1,
71 1, 8, 8, 0, 0, 0, 0, 1, 8, 8, 8, 8, 21, 16, 4, 8,
72 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1,
73 1, 1, 1, 1, 1, 1, 1, 1
77static inline bool IsSubtype1LogicOnlyZeroTileObject(
int object_id) {
78 int index = object_id & 0xFF;
79 return index >= 0xD3 && index <= 0xD6;
83static inline int GetSubtype1TileCount(
int object_id) {
84 int index = object_id & 0xFF;
86 int count = kSubtype1TileLengths[index];
90 if (IsSubtype1LogicOnlyZeroTileObject(index)) {
103 if (
rom_ ==
nullptr) {
104 return absl::InvalidArgumentError(
"ROM is null");
109 return absl::InvalidArgumentError(
110 absl::StrFormat(
"Invalid object ID: %d", object_id));
123 return absl::InvalidArgumentError(
124 absl::StrFormat(
"Invalid object subtype for ID: %#04x", object_id));
128absl::StatusOr<std::vector<ObjectTileReadRange>>
131 return absl::FailedPreconditionError(
132 "Object tile source resolution is already active");
135 std::vector<ObjectTileReadRange> ranges;
139 if (!parsed_or.ok()) {
140 return parsed_or.status();
143 std::sort(ranges.begin(), ranges.end(),
145 return std::tie(lhs.begin, lhs.end) <
146 std::tie(rhs.begin, rhs.end);
148 ranges.erase(std::unique(ranges.begin(), ranges.end()), ranges.end());
154 if (
rom_ ==
nullptr) {
155 return absl::InvalidArgumentError(
"ROM is null");
159 if (!subtype_info.ok()) {
160 return subtype_info.status();
164 routine_info.
routine_ptr = subtype_info->routine_ptr;
165 routine_info.
tile_ptr = subtype_info->subtype_ptr;
166 routine_info.
tile_count = subtype_info->max_tile_count;
180 int index = object_id & 0xFF;
188 int index = (object_id - 0x100) & 0x3F;
197 int index = (object_id - 0xF80) & 0x7F;
203 return absl::InvalidArgumentError(
204 absl::StrFormat(
"Invalid object subtype for ID: %#04x", object_id));
213 int16_t object_id, uint8_t size_byte) {
217 int size_x = size_byte & 0x03;
218 int size_y = (size_byte >> 2) & 0x03;
225 if (object_id >= 0x80 && object_id <= 0xFF) {
242 int index = object_id & 0xFF;
245 if (tile_ptr + 1 >= (
int)
rom_->
size()) {
246 return absl::OutOfRangeError(
247 absl::StrFormat(
"Tile pointer out of range: %#06x", tile_ptr));
251 uint8_t low =
rom_->
data()[tile_ptr];
252 uint8_t high =
rom_->
data()[tile_ptr + 1];
253 int16_t offset = (int16_t)((high << 8) | low);
257 bool is_debug_object = (object_id == 0x61 || object_id == 0x62 ||
258 object_id == 0xC0 || object_id == 0xC2);
259 static int debug_count = 0;
260 if (debug_count < 10 || is_debug_object) {
262 "ParseSubtype1: obj=0x%02X%s tile_ptr=0x%04X (SNES $01:%04X)",
263 object_id, is_debug_object ?
" (DEBUG)" :
"", tile_ptr, tile_ptr);
265 " ROM[0x%04X..0x%04X]=0x%02X 0x%02X offset=%d (0x%04X)",
266 tile_ptr, tile_ptr + 1, low, high, offset, (uint16_t)offset);
268 " tile_data_ptr=0x%04X+0x%04X=0x%04X (SNES $00:%04X)",
270 tile_data_ptr + 0x8000);
273 if (tile_data_ptr >= 0 && tile_data_ptr + 8 < (
int)
rom_->
size()) {
276 uint16_t tw1 =
rom_->
data()[tile_data_ptr + 2] |
277 (
rom_->
data()[tile_data_ptr + 3] << 8);
278 LOG_DEBUG(
"ObjectParser",
" First 2 tiles: $%04X(id=%d) $%04X(id=%d)",
279 tw0, tw0 & 0x3FF, tw1, tw1 & 0x3FF);
281 LOG_DEBUG(
"ObjectParser",
" Tile data at 0x%04X: <OUT OF BOUNDS>",
284 if (!is_debug_object)
289 int tile_count = GetSubtype1TileCount(object_id);
295 constexpr int kDamFloodGateOpenTileOffset = 0x13E8;
296 constexpr int kDamFloodGateTileCount = 40;
299 int index = (object_id - 0x100) & 0x3F;
302 if (tile_ptr + 1 >= (
int)
rom_->
size()) {
303 return absl::OutOfRangeError(
304 absl::StrFormat(
"Tile pointer out of range: %#06x", tile_ptr));
308 uint8_t low =
rom_->
data()[tile_ptr];
309 uint8_t high =
rom_->
data()[tile_ptr + 1];
316 bool is_corner = (object_id >= 0x100 && object_id <= 0x103);
319 "ParseSubtype2: CORNER obj=0x%03X index=%d tile_ptr=0x%04X",
320 object_id, index, tile_ptr);
322 " ROM[0x%04X..0x%04X]=0x%02X 0x%02X offset=0x%04X", tile_ptr,
323 tile_ptr + 1, low, high, (high << 8) | low);
325 "ObjectParser",
" tile_data_ptr=0x%04X+0x%04X=0x%04X (tile_count=%d)",
329 if (tile_data_ptr >= 0 && tile_data_ptr + 4 < (
int)
rom_->
size()) {
332 uint16_t tw1 =
rom_->
data()[tile_data_ptr + 2] |
333 (
rom_->
data()[tile_data_ptr + 3] << 8);
334 LOG_DEBUG(
"ObjectParser",
" First 2 tiles: $%04X(id=%d) $%04X(id=%d)",
335 tw0, tw0 & 0x3FF, tw1, tw1 & 0x3FF);
339 if (object_id == 0x137) {
340 auto closed_tiles =
ReadTileData(tile_data_ptr, kDamFloodGateTileCount);
341 if (!closed_tiles.ok()) {
342 return closed_tiles.status();
346 kDamFloodGateTileCount);
347 if (!open_tiles.ok()) {
348 return open_tiles.status();
350 closed_tiles->insert(closed_tiles->end(), open_tiles->begin(),
360 constexpr int kBombableFloorOpenTileOffset = 0x05BA;
361 constexpr int kBombableFloorStateTileCount = 16;
362 constexpr int kPrisonCellTileOffset = 0x1488;
363 constexpr int kPrisonCellTileCount = 6;
364 constexpr int kRupeeFloorTileOffset = 0x1DD6;
365 constexpr int kRupeeFloorTileCount = 2;
366 constexpr int kBigKeyLockTileOffset = 0x1494;
367 constexpr int kClosedChestTileOffset = 0x149C;
368 constexpr int kOpenChestTileOffset = 0x14A4;
369 constexpr int kChestStateTileCount = 4;
370 constexpr int kClosedBigChestTileOffset = 0x14AC;
371 constexpr int kOpenBigChestTileOffset = 0x14C4;
372 constexpr int kBigChestStateTileCount = 12;
373 constexpr int kBigGrayRockTileOffset = 0x0E62;
374 constexpr int kBigGrayRockTileCount = 16;
375 constexpr int kAgahnimsAltarTileOffset = 0x1B4A;
376 constexpr int kAgahnimsAltarTileCount = 84;
377 constexpr int kFortuneTellerRoomTileOffset = 0x202E;
378 constexpr int kFortuneTellerRoomTileCount = 26;
379 constexpr int kSmithyFurnaceTileOffset = 0x1F92;
380 constexpr int kSmithyFurnaceTileCount = 48;
384 int index = (object_id - 0xF80) & 0x7F;
387 if (tile_ptr + 1 >= (
int)
rom_->
size()) {
388 return absl::OutOfRangeError(
389 absl::StrFormat(
"Tile pointer out of range: %#06x", tile_ptr));
393 uint8_t low =
rom_->
data()[tile_ptr];
394 uint8_t high =
rom_->
data()[tile_ptr + 1];
403 if (object_id == 0xFC7) {
405 ReadTileData(tile_data_ptr, kBombableFloorStateTileCount);
406 if (!intact_tiles.ok()) {
407 return intact_tiles.status();
411 kBombableFloorStateTileCount);
412 if (!open_tiles.ok()) {
413 return open_tiles.status();
415 intact_tiles->insert(intact_tiles->end(), open_tiles->begin(),
424 if (object_id == 0xF8D || object_id == 0xF97) {
426 kPrisonCellTileCount);
433 if (object_id == 0xF92) {
435 kRupeeFloorTileCount);
441 if (object_id == 0xF98) {
443 kChestStateTileCount);
447 if (object_id == 0xF99) {
450 if (!closed_tiles.ok()) {
451 return closed_tiles.status();
455 if (!open_tiles.ok()) {
456 return open_tiles.status();
458 closed_tiles->insert(closed_tiles->end(), open_tiles->begin(),
465 if (object_id == 0xFB1) {
468 kBigChestStateTileCount);
469 if (!closed_tiles.ok()) {
470 return closed_tiles.status();
474 kBigChestStateTileCount);
475 if (!open_tiles.ok()) {
476 return open_tiles.status();
478 closed_tiles->insert(closed_tiles->end(), open_tiles->begin(),
486 if (object_id == 0xFAC) {
488 kBigGrayRockTileCount);
493 if (object_id == 0xFAD) {
495 kAgahnimsAltarTileCount);
500 if (object_id == 0xFD4) {
502 kFortuneTellerRoomTileCount);
508 if (object_id == 0xFCC) {
510 kSmithyFurnaceTileCount);
517 int address,
int tile_count) {
521 if (tile_count < 0) {
522 return absl::InvalidArgumentError(
523 absl::StrFormat(
"Invalid tile count: %d", tile_count));
525 if (tile_count == 0) {
526 return std::vector<gfx::TileInfo>{};
528 if (address < 0 || address + (tile_count * 2) >= (
int)
rom_->
size()) {
529 return absl::OutOfRangeError(
530 absl::StrFormat(
"Tile data address out of range: %#06x", address));
535 {
static_cast<uint32_t
>(address),
536 static_cast<uint32_t
>(address + tile_count * 2)});
539 std::vector<gfx::TileInfo> tiles;
540 tiles.reserve(tile_count);
543 static int debug_read_count = 0;
544 bool should_log = (debug_read_count < 3);
546 for (
int i = 0; i < tile_count; i++) {
547 int tile_offset = address + (i * 2);
554 tiles.push_back(tile_info);
557 if (should_log && i < 4) {
559 "ReadTile[%d]: addr=0x%06X word=0x%04X id=0x%03X pal=%d "
560 "mirror=(h:%d,v:%d)",
561 i, tile_offset, tile_word, tile_info.id_, tile_info.palette_,
562 tile_info.horizontal_mirror_, tile_info.vertical_mirror_);
568 "ReadTileData: addr=0x%06X count=%d loaded %zu tiles", address,
569 tile_count, tiles.size());
579 if (object_id >= 0x100 && object_id <= 0x10F) {
584 if (object_id >= 0x110 && object_id <= 0x117) {
588 if (object_id == 0x11F || object_id == 0x120) {
592 if (object_id == 0x11C || object_id == 0x124 || object_id == 0x125 ||
593 object_id == 0x129 || (object_id >= 0x12D && object_id <= 0x133)) {
597 if (object_id == 0x13C) {
602 if (object_id == 0x13F) {
606 if (object_id == 0x135 || object_id == 0x136) {
610 if (object_id == 0x137) {
614 if (object_id == 0x122 || object_id == 0x128) {
618 if (object_id == 0x123 || object_id == 0x13D) {
622 if (object_id == 0x12C || object_id == 0x13E) {
626 if (object_id >= 0x138 && object_id <= 0x13B) {
639 if (object_id == 0xF80) {
642 if (object_id == 0xF81) {
645 if (object_id == 0xF82) {
651 if (object_id == 0xF8D || object_id == 0xF97) {
657 if (object_id == 0xF92) {
663 if (object_id == 0xF96) {
670 if (object_id == 0xF98) {
676 if (object_id == 0xF99) {
679 if (object_id == 0xF9A) {
686 if (object_id == 0xFC7) {
692 if ((object_id >= 0xF83 && object_id <= 0xF8C) || object_id == 0xF8E ||
693 object_id == 0xF8F) {
699 if (object_id == 0xFB1) {
702 if (object_id == 0xFB2) {
706 if (object_id == 0xF94 || object_id == 0xFCE ||
707 (object_id >= 0xFE7 && object_id <= 0xFE8) ||
708 (object_id >= 0xFEC && object_id <= 0xFED)) {
713 if (object_id == 0xFC8 || object_id == 0xFE6 || object_id == 0xFEB ||
714 object_id == 0xFFA) {
718 if (object_id == 0xF95 || object_id == 0xFF2) {
723 if (object_id == 0xFFB) {
727 if ((object_id >= 0xF9B && object_id <= 0xF9D) || object_id == 0xFB3) {
731 if ((object_id >= 0xF9E && object_id <= 0xFA1) ||
732 (object_id >= 0xFA6 && object_id <= 0xFA9)) {
737 if (object_id == 0xFAC) {
742 if (object_id == 0xFAD) {
747 if (object_id == 0xFD4) {
754 if (object_id >= 0xFD6 && object_id <= 0xFD9) {
759 if (object_id == 0xFCC) {
763 if (object_id == 0xFAA || object_id == 0xFAE ||
764 (object_id >= 0xFB4 && object_id <= 0xFB9) || object_id == 0xFE2) {
769 if (object_id == 0xFCB || object_id == 0xFF6 || object_id == 0xFF7) {
781 if ((object_id >= 0xFBA && object_id <= 0xFBD) || object_id == 0xFDC) {
785 if (object_id == 0xFCD || object_id == 0xFDD) {
789 if (object_id == 0xFD5 || object_id == 0xFDB) {
793 if (object_id >= 0xFE0 && object_id <= 0xFE1) {
797 if (object_id == 0xFE9 || object_id == 0xFEA || object_id == 0xFEE ||
798 object_id == 0xFEF) {
804 if (object_id == 0xFF0) {
808 if (object_id == 0xFF1) {
813 if (object_id == 0xFF4) {
817 if (object_id == 0xFF8) {
821 if (object_id == 0xFF9) {
830 if (object_id >= 0xF80) {
832 }
else if (object_id >= 0x100) {
846 return GetSubtype1TileCount(object_id);
863 const bool is_vertical_band = (object_id >= 0x60 && object_id <= 0x6F);
874 registry.RefreshFeatureFlagMappings();
876 int registry_routine = registry.GetRoutineIdForObject(object_id);
877 if (registry_routine < 0) {
883 registry.GetRoutineInfo(registry_routine);
884 routine_info !=
nullptr) {
886 info.
both_layers = routine_info->draws_to_both_bgs;
888 switch (routine_info->category) {
static DrawRoutineRegistry & Get()
absl::StatusOr< ObjectSubtypeInfo > GetObjectSubtype(int16_t object_id)
Get object subtype information.
absl::StatusOr< ObjectRoutineInfo > ParseObjectRoutine(int16_t object_id)
Parse object routine data.
absl::StatusOr< ObjectSizeInfo > ParseObjectSize(int16_t object_id, uint8_t size_byte)
Parse object size and orientation.
absl::StatusOr< std::vector< ObjectTileReadRange > > ResolveTileReadRanges(int16_t object_id)
Resolve every tile-data range consumed by ParseObject.
int GetSubtype3TileCount(int16_t object_id) const
Get tile count for subtype 3 objects.
std::vector< ObjectTileReadRange > * tile_read_range_collector_
absl::StatusOr< std::vector< gfx::TileInfo > > ParseSubtype2(int16_t object_id)
Parse subtype 2 objects (0x100-0x1FF)
absl::StatusOr< std::vector< gfx::TileInfo > > ParseObject(int16_t object_id)
Parse object data directly from ROM.
int GetSubtype2TileCount(int16_t object_id) const
Get tile count for subtype 2 objects.
absl::StatusOr< std::vector< gfx::TileInfo > > ParseSubtype1(int16_t object_id)
Parse subtype 1 objects (0x00-0xFF)
absl::StatusOr< std::vector< gfx::TileInfo > > ParseSubtype3(int16_t object_id)
Parse subtype 3 objects (0x200+)
absl::StatusOr< std::vector< gfx::TileInfo > > ReadTileData(int address, int tile_count)
Read tile data from ROM.
ObjectDrawInfo GetObjectDrawInfo(int16_t object_id) const
Get draw routine information for an object.
int ResolveTileCountForObject(int16_t object_id) const
int DetermineSubtype(int16_t object_id) const
Determine object subtype from ID.
#define LOG_DEBUG(category, format,...)
TileInfo WordToTileInfo(uint16_t word)
constexpr int kRightwards1x1Solid_1to16_plus3
constexpr int kRoomObjectSubtype3
constexpr int kRoomObjectSubtype1
constexpr int kRoomObjectSubtype2
constexpr int kRoomObjectTileAddress
Metadata about a draw routine.
Draw routine information for object rendering.
Object routine information.
bool is_orientation_dependent
Object size and orientation information.
Object subtype information.
One half-open PC range consumed while parsing an object's tile data.