yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
object_parser.cc
Go to the documentation of this file.
1#include "object_parser.h"
2
3#include <algorithm>
4#include <cstring>
5
6#include "absl/strings/str_format.h"
7#include "util/log.h"
9
10// ROM addresses for object data are defined in room_object.h:
11// - kRoomObjectSubtype1 = 0x8000 (SNES $01:8000)
12// - kRoomObjectSubtype2 = 0x83F0 (SNES $01:83F0)
13// - kRoomObjectSubtype3 = 0x84F0 (SNES $01:84F0)
14// - kRoomObjectTileAddress = 0x1B52 (SNES $00:9B52)
15
16// Subtype 1 tile count lookup table (from ZScream's DungeonObjectData.cs)
17// Each entry specifies how many tiles to read for that object ID (0x00-0xF7)
18// Index directly by (object_id & 0xFF) for subtype 1 objects
19// clang-format off
20static constexpr uint8_t kSubtype1TileLengths[0xF8] = {
21 4, 8, 8, 8, 8, 8, 8, 4, 4, 5, 5, 5, 5, 5, 5, 5, // 0x00-0x0F
22 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 0x10-0x1F
23 5, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, // 0x20-0x2F
24 6, 1, 1, 16, 1, 1, 16, 16, 6, 8, 12, 12, 4, 8, 4, 3, // 0x30-0x3F
25 3, 3, 3, 3, 3, 3, 3, 0, 0, 8, 8, 4, 9, 16, 16, 16, // 0x40-0x4F
26 1, 18, 18, 4, 1, 8, 8, 1, 1, 1, 1, 18, 18, 15, 4, 3, // 0x50-0x5F
27 4, 8, 8, 8, 8, 8, 8, 4, 4, 3, 1, 1, 6, 6, 1, 1, // 0x60-0x6F
28 16, 1, 1, 16, 16, 8, 16, 16, 4, 1, 1, 4, 1, 4, 1, 8, // 0x70-0x7F
29 8, 12, 12, 12, 12, 18, 18, 8, 12, 4, 3, 3, 3, 1, 1, 6, // 0x80-0x8F
30 8, 8, 4, 4, 16, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90-0x9F
31 1, 1, 1, 1, 24, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xA0-0xAF
32 1, 1, 16, 3, 3, 8, 8, 8, 4, 4, 16, 4, 4, 4, 1, 1, // 0xB0-0xBF
33 1, 68, 1, 1, 8, 8, 8, 8, 8, 8, 8, 1, 1, 28, 28, 1, // 0xC0-0xCF
34 1, 8, 8, 0, 0, 0, 0, 1, 8, 8, 8, 8, 21, 16, 4, 8, // 0xD0-0xDF
35 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, // 0xE0-0xEF
36 1, 1, 1, 1, 1, 1, 1, 1 // 0xF0-0xF7
37};
38// clang-format on
39
40// Helper function to get tile count for Subtype 1 objects
41static inline int GetSubtype1TileCount(int object_id) {
42 int index = object_id & 0xFF;
43 if (index < 0xF8) {
44 int count = kSubtype1TileLengths[index];
45 return (count > 0) ? count : 8; // Default to 8 if table has 0
46 }
47 return 8; // Default for IDs >= 0xF8
48}
49
50namespace yaze {
51namespace zelda3 {
52
53absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseObject(
54 int16_t object_id) {
55 if (rom_ == nullptr) {
56 return absl::InvalidArgumentError("ROM is null");
57 }
58
59 // Validate object ID is non-negative
60 if (object_id < 0) {
61 return absl::InvalidArgumentError(
62 absl::StrFormat("Invalid object ID: %d", object_id));
63 }
64
65 int subtype = DetermineSubtype(object_id);
66
67 switch (subtype) {
68 case 1:
69 return ParseSubtype1(object_id);
70 case 2:
71 return ParseSubtype2(object_id);
72 case 3:
73 return ParseSubtype3(object_id);
74 default:
75 return absl::InvalidArgumentError(
76 absl::StrFormat("Invalid object subtype for ID: %#04x", object_id));
77 }
78}
79
80absl::StatusOr<ObjectRoutineInfo> ObjectParser::ParseObjectRoutine(
81 int16_t object_id) {
82 if (rom_ == nullptr) {
83 return absl::InvalidArgumentError("ROM is null");
84 }
85
86 auto subtype_info = GetObjectSubtype(object_id);
87 if (!subtype_info.ok()) {
88 return subtype_info.status();
89 }
90
91 ObjectRoutineInfo routine_info;
92 routine_info.routine_ptr = subtype_info->routine_ptr;
93 routine_info.tile_ptr = subtype_info->subtype_ptr;
94 routine_info.tile_count = subtype_info->max_tile_count;
95 routine_info.is_repeatable = true;
96 routine_info.is_orientation_dependent = true;
97
98 return routine_info;
99}
100
101absl::StatusOr<ObjectSubtypeInfo> ObjectParser::GetObjectSubtype(
102 int16_t object_id) {
104 info.subtype = DetermineSubtype(object_id);
105
106 switch (info.subtype) {
107 case 1: {
108 int index = object_id & 0xFF;
109 info.subtype_ptr = kRoomObjectSubtype1 + (index * 2);
110 info.routine_ptr = kRoomObjectSubtype1 + 0x200 + (index * 2);
111 break;
112 }
113 case 2: {
114 // Type 2 objects: 0x100-0x13F (64 objects only)
115 // Index mask 0x3F ensures we stay within 64-entry table bounds
116 int index = (object_id - 0x100) & 0x3F;
117 info.subtype_ptr = kRoomObjectSubtype2 + (index * 2);
118 // Routine table starts 128 bytes (64 entries * 2 bytes) after data table
119 info.routine_ptr = kRoomObjectSubtype2 + 0x80 + (index * 2);
120 break;
121 }
122 case 3: {
123 // Type 3 object IDs are 0xF80-0xFFF (128 objects)
124 // Table index should be 0-127
125 int index = (object_id - 0xF80) & 0x7F;
126 info.subtype_ptr = kRoomObjectSubtype3 + (index * 2);
127 info.routine_ptr = kRoomObjectSubtype3 + 0x100 + (index * 2);
128 break;
129 }
130 default:
131 return absl::InvalidArgumentError(
132 absl::StrFormat("Invalid object subtype for ID: %#04x", object_id));
133 }
134
136
137 return info;
138}
139
140absl::StatusOr<ObjectSizeInfo> ObjectParser::ParseObjectSize(
141 int16_t object_id, uint8_t size_byte) {
142 ObjectSizeInfo info;
143
144 // Extract size bits (0-3 for X, 4-7 for Y)
145 int size_x = size_byte & 0x03;
146 int size_y = (size_byte >> 2) & 0x03;
147
148 info.width_tiles = (size_x + 1) * 2; // Convert to tile count
149 info.height_tiles = (size_y + 1) * 2;
150
151 // Determine orientation based on object ID and size
152 // This is a heuristic based on the object naming patterns
153 if (object_id >= 0x80 && object_id <= 0xFF) {
154 // Objects 0x80-0xFF are typically vertical
155 info.is_horizontal = false;
156 } else {
157 // Objects 0x00-0x7F are typically horizontal
158 info.is_horizontal = true;
159 }
160
161 // Determine if object is repeatable
162 info.is_repeatable = (size_byte != 0);
163 info.repeat_count = size_byte == 0 ? 32 : size_byte;
164
165 return info;
166}
167
168absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype1(
169 int16_t object_id) {
170 int index = object_id & 0xFF;
171 int tile_ptr = kRoomObjectSubtype1 + (index * 2);
172
173 if (tile_ptr + 1 >= (int)rom_->size()) {
174 return absl::OutOfRangeError(
175 absl::StrFormat("Tile pointer out of range: %#06x", tile_ptr));
176 }
177
178 // Read tile data pointer (16-bit little-endian offset)
179 uint8_t low = rom_->data()[tile_ptr];
180 uint8_t high = rom_->data()[tile_ptr + 1];
181 int16_t offset = (int16_t)((high << 8) | low); // Signed offset!
182 int tile_data_ptr = kRoomObjectTileAddress + offset;
183
184 // DEBUG: Log wall objects 0x61/0x62 and ceiling 0xC0 to verify tile data
185 bool is_debug_object = (object_id == 0x61 || object_id == 0x62 ||
186 object_id == 0xC0 || object_id == 0xC2);
187 static int debug_count = 0;
188 if (debug_count < 10 || is_debug_object) {
189 LOG_DEBUG("ObjectParser",
190 "ParseSubtype1: obj=0x%02X%s tile_ptr=0x%04X (SNES $01:%04X)",
191 object_id, is_debug_object ? " (DEBUG)" : "", tile_ptr, tile_ptr);
192 LOG_DEBUG("ObjectParser",
193 " ROM[0x%04X..0x%04X]=0x%02X 0x%02X offset=%d (0x%04X)",
194 tile_ptr, tile_ptr + 1, low, high, offset, (uint16_t)offset);
195 LOG_DEBUG("ObjectParser",
196 " tile_data_ptr=0x%04X+0x%04X=0x%04X (SNES $00:%04X)",
197 kRoomObjectTileAddress, (uint16_t)offset, tile_data_ptr,
198 tile_data_ptr + 0x8000);
199
200 // Fix: Check for negative tile_data_ptr to prevent SIGSEGV on corrupted ROMs
201 if (tile_data_ptr >= 0 && tile_data_ptr + 8 < (int)rom_->size()) {
202 uint16_t tw0 =
203 rom_->data()[tile_data_ptr] | (rom_->data()[tile_data_ptr + 1] << 8);
204 uint16_t tw1 = rom_->data()[tile_data_ptr + 2] |
205 (rom_->data()[tile_data_ptr + 3] << 8);
206 LOG_DEBUG("ObjectParser", " First 2 tiles: $%04X(id=%d) $%04X(id=%d)",
207 tw0, tw0 & 0x3FF, tw1, tw1 & 0x3FF);
208 } else {
209 LOG_DEBUG("ObjectParser", " Tile data at 0x%04X: <OUT OF BOUNDS>",
210 tile_data_ptr);
211 }
212 if (!is_debug_object)
213 debug_count++;
214 }
215
216 // Use lookup table for correct tile count per object ID
217 int tile_count = GetSubtype1TileCount(object_id);
218 return ReadTileData(tile_data_ptr, tile_count);
219}
220
221absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype2(
222 int16_t object_id) {
223 // Type 2 objects: 0x100-0x13F (64 objects only)
224 int index = (object_id - 0x100) & 0x3F;
225 int tile_ptr = kRoomObjectSubtype2 + (index * 2);
226
227 if (tile_ptr + 1 >= (int)rom_->size()) {
228 return absl::OutOfRangeError(
229 absl::StrFormat("Tile pointer out of range: %#06x", tile_ptr));
230 }
231
232 // Read tile data pointer
233 uint8_t low = rom_->data()[tile_ptr];
234 uint8_t high = rom_->data()[tile_ptr + 1];
235 int tile_data_ptr = kRoomObjectTileAddress + ((high << 8) | low);
236
237 // Determine tile count based on object ID
238 int tile_count = GetSubtype2TileCount(object_id);
239
240 // DEBUG: Log corner tile loading (0x100-0x103)
241 bool is_corner = (object_id >= 0x100 && object_id <= 0x103);
242 if (is_corner) {
243 LOG_DEBUG("ObjectParser",
244 "ParseSubtype2: CORNER obj=0x%03X index=%d tile_ptr=0x%04X",
245 object_id, index, tile_ptr);
246 LOG_DEBUG("ObjectParser",
247 " ROM[0x%04X..0x%04X]=0x%02X 0x%02X offset=0x%04X", tile_ptr,
248 tile_ptr + 1, low, high, (high << 8) | low);
249 LOG_DEBUG(
250 "ObjectParser", " tile_data_ptr=0x%04X+0x%04X=0x%04X (tile_count=%d)",
251 kRoomObjectTileAddress, (high << 8) | low, tile_data_ptr, tile_count);
252
253 // Show first 2 tile words
254 if (tile_data_ptr >= 0 && tile_data_ptr + 4 < (int)rom_->size()) {
255 uint16_t tw0 =
256 rom_->data()[tile_data_ptr] | (rom_->data()[tile_data_ptr + 1] << 8);
257 uint16_t tw1 = rom_->data()[tile_data_ptr + 2] |
258 (rom_->data()[tile_data_ptr + 3] << 8);
259 LOG_DEBUG("ObjectParser", " First 2 tiles: $%04X(id=%d) $%04X(id=%d)",
260 tw0, tw0 & 0x3FF, tw1, tw1 & 0x3FF);
261 }
262 }
263
264 return ReadTileData(tile_data_ptr, tile_count);
265}
266
267absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype3(
268 int16_t object_id) {
269 // Type 3 object IDs are 0xF80-0xFFF (128 objects)
270 // Table index should be 0-127, calculated by subtracting base offset 0xF80
271 int index = (object_id - 0xF80) & 0x7F;
272 int tile_ptr = kRoomObjectSubtype3 + (index * 2);
273
274 if (tile_ptr + 1 >= (int)rom_->size()) {
275 return absl::OutOfRangeError(
276 absl::StrFormat("Tile pointer out of range: %#06x", tile_ptr));
277 }
278
279 // Read tile data pointer
280 uint8_t low = rom_->data()[tile_ptr];
281 uint8_t high = rom_->data()[tile_ptr + 1];
282 int tile_data_ptr = kRoomObjectTileAddress + ((high << 8) | low);
283
284 // Determine tile count based on object ID
285 int tile_count = GetSubtype3TileCount(object_id);
286 return ReadTileData(tile_data_ptr, tile_count);
287}
288
289absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ReadTileData(
290 int address, int tile_count) {
291 // Each tile is stored as a 16-bit word (2 bytes), not 8 bytes!
292 // ZScream: tiles.Add(new Tile(ROM.DATA[pos + ((i * 2))], ROM.DATA[pos + ((i *
293 // 2)) + 1]));
294 if (address < 0 || address + (tile_count * 2) >= (int)rom_->size()) {
295 return absl::OutOfRangeError(
296 absl::StrFormat("Tile data address out of range: %#06x", address));
297 }
298
299 std::vector<gfx::TileInfo> tiles;
300 tiles.reserve(tile_count);
301
302 // DEBUG: Log first tile read
303 static int debug_read_count = 0;
304 bool should_log = (debug_read_count < 3);
305
306 for (int i = 0; i < tile_count; i++) {
307 int tile_offset = address + (i * 2); // 2 bytes per tile word
308
309 // Read 1 word (2 bytes) per tile - this is the SNES tile format
310 uint16_t tile_word =
311 rom_->data()[tile_offset] | (rom_->data()[tile_offset + 1] << 8);
312
313 auto tile_info = gfx::WordToTileInfo(tile_word);
314 tiles.push_back(tile_info);
315
316 // DEBUG: Log first few tiles
317 if (should_log && i < 4) {
318 LOG_DEBUG("ObjectParser",
319 "ReadTile[%d]: addr=0x%06X word=0x%04X id=0x%03X pal=%d "
320 "mirror=(h:%d,v:%d)",
321 i, tile_offset, tile_word, tile_info.id_, tile_info.palette_,
322 tile_info.horizontal_mirror_, tile_info.vertical_mirror_);
323 }
324 }
325
326 if (should_log) {
327 LOG_DEBUG("ObjectParser",
328 "ReadTileData: addr=0x%06X count=%d loaded %zu tiles", address,
329 tile_count, tiles.size());
330 debug_read_count++;
331 }
332
333 return tiles;
334}
335
336int ObjectParser::GetSubtype2TileCount(int16_t object_id) const {
337 // 4x4 corners (0x100-0x10F): 16 tiles (32 bytes)
338 // These are RoomDraw_4x4 and RoomDraw_4x4Corner_BothBG routines
339 if (object_id >= 0x100 && object_id <= 0x10F) {
340 return 16;
341 }
342 // Weird corners (0x110-0x117): 12 tiles (24 bytes)
343 // These are RoomDraw_WeirdCornerBottom_BothBG and RoomDraw_WeirdCornerTop_BothBG
344 if (object_id >= 0x110 && object_id <= 0x117) {
345 return 12;
346 }
347 // 4x4 fixed patterns (stairs/altars/walls)
348 if (object_id == 0x11C || object_id == 0x124 || object_id == 0x125 ||
349 object_id == 0x129 || (object_id >= 0x12D && object_id <= 0x133) ||
350 (object_id >= 0x135 && object_id <= 0x137) || object_id == 0x13C ||
351 object_id == 0x13F) {
352 return 16;
353 }
354 // Beds (4x5)
355 if (object_id == 0x122 || object_id == 0x128) {
356 return 20;
357 }
358 // Tables (4x3)
359 if (object_id == 0x123 || object_id == 0x13D) {
360 return 12;
361 }
362 // 3x6 and 6x3 utility patterns
363 if (object_id == 0x12C || object_id == 0x13E) {
364 return 18;
365 }
366 // Spiral stairs (4x3)
367 if (object_id >= 0x138 && object_id <= 0x13B) {
368 return 12;
369 }
370 // Default: 8 tiles for other subtype 2 objects
371 return 8;
372}
373
374int ObjectParser::GetSubtype3TileCount(int16_t object_id) const {
375 // Water face variants:
376 // - 0xF80 (Empty) may branch to the 0xF81 tile block at runtime, so we load
377 // both spans to preserve parity for state-dependent rendering.
378 // - 0xF81 (Spitting): 4x5
379 // - 0xF82 (Drenching): 4x7
380 if (object_id == 0xF80) {
381 return 32;
382 }
383 if (object_id == 0xF81) {
384 return 20;
385 }
386 if (object_id == 0xF82) {
387 return 28;
388 }
389
390 // BigChest (0xFB1 = ASM 0x231) and OpenBigChest (0xFB2 = ASM 0x232): 12 tiles
391 // These use RoomDraw_1x3N_rightwards with N=4 (4 columns × 3 rows)
392 if (object_id == 0xFB1 || object_id == 0xFB2) {
393 return 12;
394 }
395 // TableRock4x3 variants (0xF94, 0xFCE, 0xFE7-0xFE8, 0xFEC-0xFED): 12 tiles
396 if (object_id == 0xF94 || object_id == 0xFCE ||
397 (object_id >= 0xFE7 && object_id <= 0xFE8) ||
398 (object_id >= 0xFEC && object_id <= 0xFED)) {
399 return 12;
400 }
401 // 4x4 pattern objects: 16 tiles
402 // (0xFC8 = 0x248, 0xFE6 = 0x266, 0xFEB = 0x26B, 0xFFA = 0x27A)
403 if (object_id == 0xFC8 || object_id == 0xFE6 || object_id == 0xFEB ||
404 object_id == 0xFFA) {
405 return 16;
406 }
407 // Boss shells (4x4)
408 if (object_id == 0xF95 || object_id == 0xFF2 || object_id == 0xFFB) {
409 return 16;
410 }
411 // Auto stairs (4x4)
412 if ((object_id >= 0xF9B && object_id <= 0xF9D) || object_id == 0xFB3) {
413 return 16;
414 }
415 // Straight inter-room stairs (4x4)
416 if ((object_id >= 0xF9E && object_id <= 0xFA1) ||
417 (object_id >= 0xFA6 && object_id <= 0xFA9)) {
418 return 16;
419 }
420 // 4x4 single-pattern objects
421 if (object_id == 0xFAA || object_id == 0xFAD || object_id == 0xFAE ||
422 (object_id >= 0xFB4 && object_id <= 0xFB9) || object_id == 0xFCB ||
423 object_id == 0xFCC || object_id == 0xFD4 || object_id == 0xFE2 ||
424 object_id == 0xFF4 || object_id == 0xFF6 || object_id == 0xFF7) {
425 return 16;
426 }
427 // Utility 6x3 (18 tiles)
428 if (object_id == 0xFCD || object_id == 0xFDD) {
429 return 18;
430 }
431 // Utility 3x5 (15 tiles)
432 if (object_id == 0xFD5 || object_id == 0xFDB) {
433 return 15;
434 }
435 // Archery game target door (3x6, 18 tiles)
436 if (object_id >= 0xFE0 && object_id <= 0xFE1) {
437 return 18;
438 }
439 // Solid wall decor 3x4 (12 tiles)
440 if (object_id == 0xFE9 || object_id == 0xFEA || object_id == 0xFEE ||
441 object_id == 0xFEF) {
442 return 12;
443 }
444 // Light beams
445 if (object_id == 0xFF0) {
446 return 16;
447 }
448 if (object_id == 0xFF1) {
449 return 36;
450 }
451 // Ganon Triforce floor decor (two 4x4 blocks -> 32 tiles)
452 if (object_id == 0xFF8) {
453 return 32;
454 }
455 // Table rock 4x3
456 if (object_id == 0xFF9) {
457 return 12;
458 }
459 // Default: 8 tiles for most subtype 3 objects
460 return 8;
461}
462
463int ObjectParser::DetermineSubtype(int16_t object_id) const {
464 // Type 3 IDs from decoding are 0xF80-0xFFF (b3 0xF8-0xFF shifted).
465 if (object_id >= 0xF80) {
466 return 3;
467 } else if (object_id >= 0x100) {
468 return 2;
469 } else {
470 return 1;
471 }
472}
473
474int ObjectParser::ResolveTileCountForObject(int16_t object_id) const {
475 if (object_id < 0) {
476 return 1;
477 }
478
479 switch (DetermineSubtype(object_id)) {
480 case 1:
481 return GetSubtype1TileCount(object_id);
482 case 2:
483 return GetSubtype2TileCount(object_id);
484 case 3:
485 return GetSubtype3TileCount(object_id);
486 default:
487 return 1;
488 }
489}
490
492 ObjectDrawInfo info;
493
494 // Map object ID to draw routine based on ZScream's subtype1_routines table
495 // This is based on the DungeonObjectData.cs mapping from ZScream
496
497 if (object_id == 0x00) {
498 info.draw_routine_id = 0; // RoomDraw_Rightwards2x2_1to15or32
499 info.routine_name = "Rightwards2x2_1to15or32";
500 info.tile_count = 4;
501 info.is_horizontal = true;
502 } else if (object_id >= 0x01 && object_id <= 0x02) {
503 info.draw_routine_id = 1; // RoomDraw_Rightwards2x4_1to15or26
504 info.routine_name = "Rightwards2x4_1to15or26";
505 info.tile_count = 8;
506 info.is_horizontal = true;
507 } else if (object_id >= 0x03 && object_id <= 0x04) {
508 info.draw_routine_id = 2; // RoomDraw_Rightwards2x4spaced4_1to16
509 info.routine_name = "Rightwards2x4spaced4_1to16";
510 info.tile_count = 8;
511 info.is_horizontal = true;
512 } else if (object_id >= 0x05 && object_id <= 0x06) {
513 info.draw_routine_id = 3; // RoomDraw_Rightwards2x4spaced4_1to16_BothBG
514 info.routine_name = "Rightwards2x4spaced4_1to16_BothBG";
515 info.tile_count = 8;
516 info.is_horizontal = true;
517 info.both_layers = true;
518 } else if (object_id >= 0x07 && object_id <= 0x08) {
519 info.draw_routine_id = 4; // RoomDraw_Rightwards2x2_1to16
520 info.routine_name = "Rightwards2x2_1to16";
521 info.tile_count = 4;
522 info.is_horizontal = true;
523 } else if (object_id == 0x09) {
524 info.draw_routine_id = 5; // RoomDraw_DiagonalAcute_1to16
525 info.routine_name = "DiagonalAcute_1to16";
526 info.tile_count = 5;
527 info.is_horizontal = false;
528 } else if (object_id >= 0x0A && object_id <= 0x0B) {
529 info.draw_routine_id = 6; // RoomDraw_DiagonalGrave_1to16
530 info.routine_name = "DiagonalGrave_1to16";
531 info.tile_count = 5;
532 info.is_horizontal = false;
533 } else if (object_id >= 0x0C && object_id <= 0x0D) {
534 info.draw_routine_id = 17; // RoomDraw_DiagonalAcute_1to16_BothBG
535 info.routine_name = "DiagonalAcute_1to16_BothBG";
536 info.tile_count = 5;
537 info.is_horizontal = false;
538 info.both_layers = true;
539 } else if (object_id >= 0x0E && object_id <= 0x0F) {
540 info.draw_routine_id = 18; // RoomDraw_DiagonalGrave_1to16_BothBG
541 info.routine_name = "DiagonalGrave_1to16_BothBG";
542 info.tile_count = 5;
543 info.is_horizontal = false;
544 info.both_layers = true;
545 } else if (object_id >= 0x10 && object_id <= 0x11) {
546 info.draw_routine_id = 17; // RoomDraw_DiagonalAcute_1to16_BothBG
547 info.routine_name = "DiagonalAcute_1to16_BothBG";
548 info.tile_count = 5;
549 info.is_horizontal = false;
550 info.both_layers = true;
551 } else if (object_id >= 0x12 && object_id <= 0x13) {
552 info.draw_routine_id = 18; // RoomDraw_DiagonalGrave_1to16_BothBG
553 info.routine_name = "DiagonalGrave_1to16_BothBG";
554 info.tile_count = 5;
555 info.is_horizontal = false;
556 info.both_layers = true;
557 } else if (object_id >= 0x14 && object_id <= 0x15) {
558 info.draw_routine_id = 17; // RoomDraw_DiagonalAcute_1to16_BothBG
559 info.routine_name = "DiagonalAcute_1to16_BothBG";
560 info.tile_count = 5;
561 info.is_horizontal = false;
562 info.both_layers = true;
563 } else if (object_id >= 0x16 && object_id <= 0x17) {
564 info.draw_routine_id = 18; // RoomDraw_DiagonalGrave_1to16_BothBG
565 info.routine_name = "DiagonalGrave_1to16_BothBG";
566 info.tile_count = 5;
567 info.is_horizontal = false;
568 info.both_layers = true;
569 } else if (object_id >= 0x18 && object_id <= 0x19) {
570 info.draw_routine_id = 17; // RoomDraw_DiagonalAcute_1to16_BothBG
571 info.routine_name = "DiagonalAcute_1to16_BothBG";
572 info.tile_count = 5;
573 info.is_horizontal = false;
574 info.both_layers = true;
575 } else if (object_id >= 0x1A && object_id <= 0x1B) {
576 info.draw_routine_id = 18; // RoomDraw_DiagonalGrave_1to16_BothBG
577 info.routine_name = "DiagonalGrave_1to16_BothBG";
578 info.tile_count = 5;
579 info.is_horizontal = false;
580 info.both_layers = true;
581 } else if (object_id >= 0x1C && object_id <= 0x1D) {
582 info.draw_routine_id = 17; // RoomDraw_DiagonalAcute_1to16_BothBG
583 info.routine_name = "DiagonalAcute_1to16_BothBG";
584 info.tile_count = 5;
585 info.is_horizontal = false;
586 info.both_layers = true;
587 } else if (object_id >= 0x1E && object_id <= 0x1F) {
588 info.draw_routine_id = 18; // RoomDraw_DiagonalGrave_1to16_BothBG
589 info.routine_name = "DiagonalGrave_1to16_BothBG";
590 info.tile_count = 5;
591 info.is_horizontal = false;
592 info.both_layers = true;
593 } else if (object_id == 0x20) {
594 info.draw_routine_id = 17; // RoomDraw_DiagonalAcute_1to16_BothBG
595 info.routine_name = "DiagonalAcute_1to16_BothBG";
596 info.tile_count = 5;
597 info.is_horizontal = false;
598 info.both_layers = true;
599 } else if (object_id == 0x21) {
600 info.draw_routine_id = 20; // RoomDraw_Rightwards1x2_1to16_plus2
601 info.routine_name = "Rightwards1x2_1to16_plus2";
602 info.tile_count = 2;
603 info.is_horizontal = true;
604 } else if (object_id == 0x22) {
605 info.draw_routine_id = 20; // RoomDraw_Rightwards1x2_1to16_plus2
606 info.routine_name = "Rightwards1x2_1to16_plus2";
607 info.tile_count = 2;
608 info.is_horizontal = true;
609 } else if (object_id >= 0x23 && object_id <= 0x24) {
610 info.draw_routine_id = 21; // RoomDraw_RightwardsHasEdge1x1_1to16_plus3
611 info.routine_name = "RightwardsHasEdge1x1_1to16_plus3";
612 info.tile_count = 1;
613 info.is_horizontal = true;
614 } else if (object_id >= 0x25 && object_id <= 0x26) {
615 info.draw_routine_id = 22; // RoomDraw_RightwardsHasEdge1x1_1to16_plus2
616 info.routine_name = "RightwardsHasEdge1x1_1to16_plus2";
617 info.tile_count = 1;
618 info.is_horizontal = true;
619 } else if (object_id == 0x27) {
620 info.draw_routine_id = 23; // RoomDraw_RightwardsTopCorners1x2_1to16_plus13
621 info.routine_name = "RightwardsTopCorners1x2_1to16_plus13";
622 info.tile_count = 2;
623 info.is_horizontal = true;
624 } else if (object_id == 0x28) {
625 info.draw_routine_id =
626 24; // RoomDraw_RightwardsBottomCorners1x2_1to16_plus13
627 info.routine_name = "RightwardsBottomCorners1x2_1to16_plus13";
628 info.tile_count = 2;
629 info.is_horizontal = true;
630 } else if (object_id >= 0x29 && object_id <= 0x2E) {
631 info.draw_routine_id = 22; // RoomDraw_RightwardsHasEdge1x1_1to16_plus2
632 info.routine_name = "RightwardsHasEdge1x1_1to16_plus2";
633 info.tile_count = 1;
634 info.is_horizontal = true;
635 } else if (object_id == 0x2F) {
636 info.draw_routine_id = 23; // RoomDraw_RightwardsTopCorners1x2_1to16_plus13
637 info.routine_name = "RightwardsTopCorners1x2_1to16_plus13";
638 info.tile_count = 2;
639 info.is_horizontal = true;
640 } else if (object_id == 0x30) {
641 info.draw_routine_id =
642 24; // RoomDraw_RightwardsBottomCorners1x2_1to16_plus13
643 info.routine_name = "RightwardsBottomCorners1x2_1to16_plus13";
644 info.tile_count = 2;
645 info.is_horizontal = true;
646 } else if (object_id >= 0x31 && object_id <= 0x32) {
647 info.draw_routine_id = 14; // CustomDraw
648 info.routine_name = "CustomDraw";
649 info.tile_count = 1;
650 } else if (object_id == 0x33) {
651 info.draw_routine_id = 16; // RoomDraw_Rightwards4x4_1to16
652 info.routine_name = "Rightwards4x4_1to16";
653 info.tile_count = 16;
654 info.is_horizontal = true;
655 } else if (object_id == 0x34) {
656 info.draw_routine_id = 25; // RoomDraw_Rightwards1x1Solid_1to16_plus3
657 info.routine_name = "Rightwards1x1Solid_1to16_plus3";
658 info.tile_count = 1;
659 info.is_horizontal = true;
660 } else if (object_id == 0x35) {
661 info.draw_routine_id = 26; // RoomDraw_DoorSwitcherer
662 info.routine_name = "DoorSwitcherer";
663 info.tile_count = 1;
664 } else if (object_id >= 0x36 && object_id <= 0x37) {
665 info.draw_routine_id = 27; // RoomDraw_RightwardsDecor4x4spaced2_1to16
666 info.routine_name = "RightwardsDecor4x4spaced2_1to16";
667 info.tile_count = 16;
668 info.is_horizontal = true;
669 } else if (object_id == 0x38) {
670 info.draw_routine_id = 28; // RoomDraw_RightwardsStatue2x3spaced2_1to16
671 info.routine_name = "RightwardsStatue2x3spaced2_1to16";
672 info.tile_count = 6;
673 info.is_horizontal = true;
674 } else if (object_id == 0x39 || object_id == 0x3D) {
675 info.draw_routine_id = 29; // RoomDraw_RightwardsPillar2x4spaced4_1to16
676 info.routine_name = "RightwardsPillar2x4spaced4_1to16";
677 info.tile_count = 8;
678 info.is_horizontal = true;
679 } else if (object_id >= 0x3A && object_id <= 0x3B) {
680 info.draw_routine_id = 30; // RoomDraw_RightwardsDecor4x3spaced4_1to16
681 info.routine_name = "RightwardsDecor4x3spaced4_1to16";
682 info.tile_count = 12;
683 info.is_horizontal = true;
684 } else if (object_id == 0x3C) {
685 info.draw_routine_id = 31; // RoomDraw_RightwardsDoubled2x2spaced2_1to16
686 info.routine_name = "RightwardsDoubled2x2spaced2_1to16";
687 info.tile_count = 8;
688 info.is_horizontal = true;
689 } else if (object_id == 0x3E) {
690 info.draw_routine_id = 32; // RoomDraw_RightwardsDecor2x2spaced12_1to16
691 info.routine_name = "RightwardsDecor2x2spaced12_1to16";
692 info.tile_count = 4;
693 info.is_horizontal = true;
694 } else if (object_id >= 0x3F && object_id <= 0x40) {
695 info.draw_routine_id =
696 22; // RoomDraw_RightwardsHasEdge1x1_1to16_plus2 (variant)
697 info.routine_name = "RightwardsHasEdge1x1_1to16_plus2";
698 info.tile_count = 1;
699 info.is_horizontal = true;
700 } else if (object_id >= 0x40 && object_id <= 0x4F) {
701 info.draw_routine_id = 19; // RoomDraw_Corner4x4 (Type 2 corners)
702 info.routine_name = "Corner4x4";
703 info.tile_count = 16;
704 info.is_horizontal = true;
705 } else if (object_id >= 0x60 && object_id <= 0x6F) {
706 // Vertical objects (Subtype 1)
707 if (object_id == 0x60) {
708 info.draw_routine_id = 7; // RoomDraw_Downwards2x2_1to15or32
709 info.routine_name = "Downwards2x2_1to15or32";
710 info.tile_count = 4;
711 info.is_horizontal = false;
712 } else if (object_id >= 0x61 && object_id <= 0x62) {
713 info.draw_routine_id = 8; // RoomDraw_Downwards4x2_1to15or26
714 info.routine_name = "Downwards4x2_1to15or26";
715 info.tile_count = 8;
716 info.is_horizontal = false;
717 } else if (object_id >= 0x63 && object_id <= 0x64) {
718 info.draw_routine_id = 9; // RoomDraw_Downwards4x2_1to16_BothBG
719 info.routine_name = "Downwards4x2_1to16_BothBG";
720 info.tile_count = 8;
721 info.is_horizontal = false;
722 info.both_layers = true;
723 } else if (object_id >= 0x65 && object_id <= 0x66) {
724 info.draw_routine_id = 10; // RoomDraw_DownwardsDecor4x2spaced4_1to16
725 info.routine_name = "DownwardsDecor4x2spaced4_1to16";
726 info.tile_count = 8;
727 info.is_horizontal = false;
728 } else if (object_id >= 0x67 && object_id <= 0x68) {
729 info.draw_routine_id = 11; // RoomDraw_Downwards2x2_1to16
730 info.routine_name = "Downwards2x2_1to16";
731 info.tile_count = 4;
732 info.is_horizontal = false;
733 } else if (object_id == 0x69) {
734 info.draw_routine_id = 12; // RoomDraw_DownwardsHasEdge1x1_1to16_plus3
735 info.routine_name = "DownwardsHasEdge1x1_1to16_plus3";
736 info.tile_count = 1;
737 info.is_horizontal = false;
738 } else if (object_id >= 0x6A && object_id <= 0x6B) {
739 info.draw_routine_id = 13; // RoomDraw_DownwardsEdge1x1_1to16
740 info.routine_name = "DownwardsEdge1x1_1to16";
741 info.tile_count = 1;
742 info.is_horizontal = false;
743 } else if (object_id == 0x6C) {
744 info.draw_routine_id =
745 14; // RoomDraw_DownwardsLeftCorners2x1_1to16_plus12
746 info.routine_name = "DownwardsLeftCorners2x1_1to16_plus12";
747 info.tile_count = 2;
748 info.is_horizontal = false;
749 } else if (object_id == 0x6D) {
750 info.draw_routine_id =
751 15; // RoomDraw_DownwardsRightCorners2x1_1to16_plus12
752 info.routine_name = "DownwardsRightCorners2x1_1to16_plus12";
753 info.tile_count = 2;
754 info.is_horizontal = false;
755 } else {
756 info.draw_routine_id = 16; // Default
757 info.routine_name = "DefaultSolid";
758 info.tile_count = 1;
759 }
760 } else if (object_id >= 0x100 && object_id <= 0x10F) {
761 info.draw_routine_id = 16; // RoomDraw_Rightwards4x4_1to16 (Type 2)
762 info.routine_name = "Rightwards4x4_1to16";
763 info.tile_count = 16;
764 info.is_horizontal = true;
765 } else {
766 // Default to simple 1x1 solid for unmapped objects
767 info.draw_routine_id = 25; // Use solid block routine (0x34)
768 info.routine_name = "DefaultSolid";
769 info.tile_count = 1;
770 info.is_horizontal = true;
771 }
772
773 // Tile count should always come from subtype lookup tables to avoid stale
774 // hardcoded counts in the draw-routine mapping above.
775 info.tile_count = std::max(1, ResolveTileCountForObject(object_id));
776
777 return info;
778}
779
780} // namespace zelda3
781} // namespace yaze
auto data() const
Definition rom.h:139
auto size() const
Definition rom.h:138
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.
int GetSubtype3TileCount(int16_t object_id) const
Get tile count for subtype 3 objects.
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,...)
Definition log.h:103
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:378
constexpr int kRoomObjectSubtype3
Definition room_object.h:46
constexpr int kRoomObjectSubtype1
Definition room_object.h:44
constexpr int kRoomObjectSubtype2
Definition room_object.h:45
constexpr int kRoomObjectTileAddress
Definition room_object.h:47
Draw routine information for object rendering.
Object routine information.
Object size and orientation information.
Object subtype information.