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"
8#include "util/log.h"
9
10// ROM addresses for object data (PC addresses, not SNES)
11static constexpr int kRoomObjectSubtype1 = 0x0A8000;
12static constexpr int kRoomObjectSubtype2 = 0x0A9000;
13static constexpr int kRoomObjectSubtype3 = 0x0AA000;
14static constexpr int kRoomObjectTileAddress = 0x0AB000;
15
16namespace yaze {
17namespace zelda3 {
18
19absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseObject(int16_t object_id) {
20 if (rom_ == nullptr) {
21 return absl::InvalidArgumentError("ROM is null");
22 }
23
24 int subtype = DetermineSubtype(object_id);
25
26 switch (subtype) {
27 case 1:
28 return ParseSubtype1(object_id);
29 case 2:
30 return ParseSubtype2(object_id);
31 case 3:
32 return ParseSubtype3(object_id);
33 default:
34 return absl::InvalidArgumentError(
35 absl::StrFormat("Invalid object subtype for ID: %#04x", object_id));
36 }
37}
38
39absl::StatusOr<ObjectRoutineInfo> ObjectParser::ParseObjectRoutine(int16_t object_id) {
40 if (rom_ == nullptr) {
41 return absl::InvalidArgumentError("ROM is null");
42 }
43
44 auto subtype_info = GetObjectSubtype(object_id);
45 if (!subtype_info.ok()) {
46 return subtype_info.status();
47 }
48
49 ObjectRoutineInfo routine_info;
50 routine_info.routine_ptr = subtype_info->routine_ptr;
51 routine_info.tile_ptr = subtype_info->subtype_ptr;
52 routine_info.tile_count = subtype_info->max_tile_count;
53 routine_info.is_repeatable = true;
54 routine_info.is_orientation_dependent = true;
55
56 return routine_info;
57}
58
59absl::StatusOr<ObjectSubtypeInfo> ObjectParser::GetObjectSubtype(int16_t object_id) {
61 info.subtype = DetermineSubtype(object_id);
62
63 switch (info.subtype) {
64 case 1: {
65 int index = object_id & 0xFF;
66 info.subtype_ptr = kRoomObjectSubtype1 + (index * 2);
67 info.routine_ptr = kRoomObjectSubtype1 + 0x200 + (index * 2);
68 info.max_tile_count = 8; // Most subtype 1 objects use 8 tiles
69 break;
70 }
71 case 2: {
72 int index = object_id & 0x7F;
73 info.subtype_ptr = kRoomObjectSubtype2 + (index * 2);
74 info.routine_ptr = kRoomObjectSubtype2 + 0x80 + (index * 2);
75 info.max_tile_count = 8;
76 break;
77 }
78 case 3: {
79 int index = object_id & 0xFF;
80 info.subtype_ptr = kRoomObjectSubtype3 + (index * 2);
81 info.routine_ptr = kRoomObjectSubtype3 + 0x100 + (index * 2);
82 info.max_tile_count = 8;
83 break;
84 }
85 default:
86 return absl::InvalidArgumentError(
87 absl::StrFormat("Invalid object subtype for ID: %#04x", object_id));
88 }
89
90 return info;
91}
92
93absl::StatusOr<ObjectSizeInfo> ObjectParser::ParseObjectSize(int16_t object_id, uint8_t size_byte) {
94 ObjectSizeInfo info;
95
96 // Extract size bits (0-3 for X, 4-7 for Y)
97 int size_x = size_byte & 0x03;
98 int size_y = (size_byte >> 2) & 0x03;
99
100 info.width_tiles = (size_x + 1) * 2; // Convert to tile count
101 info.height_tiles = (size_y + 1) * 2;
102
103 // Determine orientation based on object ID and size
104 // This is a heuristic based on the object naming patterns
105 if (object_id >= 0x80 && object_id <= 0xFF) {
106 // Objects 0x80-0xFF are typically vertical
107 info.is_horizontal = false;
108 } else {
109 // Objects 0x00-0x7F are typically horizontal
110 info.is_horizontal = true;
111 }
112
113 // Determine if object is repeatable
114 info.is_repeatable = (size_byte != 0);
115 info.repeat_count = size_byte == 0 ? 32 : size_byte;
116
117 return info;
118}
119
120absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype1(int16_t object_id) {
121 int index = object_id & 0xFF;
122 int tile_ptr = kRoomObjectSubtype1 + (index * 2);
123
124 if (tile_ptr + 1 >= (int)rom_->size()) {
125 return absl::OutOfRangeError(
126 absl::StrFormat("Tile pointer out of range: %#06x", tile_ptr));
127 }
128
129 // Read tile data pointer
130 uint8_t low = rom_->data()[tile_ptr];
131 uint8_t high = rom_->data()[tile_ptr + 1];
132 int tile_data_ptr = kRoomObjectTileAddress + ((high << 8) | low);
133
134 // Read 8 tiles (most subtype 1 objects use 8 tiles)
135 return ReadTileData(tile_data_ptr, 8);
136}
137
138absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype2(int16_t object_id) {
139 int index = object_id & 0x7F;
140 int tile_ptr = kRoomObjectSubtype2 + (index * 2);
141
142 if (tile_ptr + 1 >= (int)rom_->size()) {
143 return absl::OutOfRangeError(
144 absl::StrFormat("Tile pointer out of range: %#06x", tile_ptr));
145 }
146
147 // Read tile data pointer
148 uint8_t low = rom_->data()[tile_ptr];
149 uint8_t high = rom_->data()[tile_ptr + 1];
150 int tile_data_ptr = kRoomObjectTileAddress + ((high << 8) | low);
151
152 // Read 8 tiles
153 return ReadTileData(tile_data_ptr, 8);
154}
155
156absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype3(int16_t object_id) {
157 int index = object_id & 0xFF;
158 int tile_ptr = kRoomObjectSubtype3 + (index * 2);
159
160 if (tile_ptr + 1 >= (int)rom_->size()) {
161 return absl::OutOfRangeError(
162 absl::StrFormat("Tile pointer out of range: %#06x", tile_ptr));
163 }
164
165 // Read tile data pointer
166 uint8_t low = rom_->data()[tile_ptr];
167 uint8_t high = rom_->data()[tile_ptr + 1];
168 int tile_data_ptr = kRoomObjectTileAddress + ((high << 8) | low);
169
170 // Read 8 tiles
171 return ReadTileData(tile_data_ptr, 8);
172}
173
174absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ReadTileData(int address, int tile_count) {
175 // Each tile is stored as a 16-bit word (2 bytes), not 8 bytes!
176 // ZScream: tiles.Add(new Tile(ROM.DATA[pos + ((i * 2))], ROM.DATA[pos + ((i * 2)) + 1]));
177 if (address < 0 || address + (tile_count * 2) >= (int)rom_->size()) {
178 return absl::OutOfRangeError(
179 absl::StrFormat("Tile data address out of range: %#06x", address));
180 }
181
182 std::vector<gfx::TileInfo> tiles;
183 tiles.reserve(tile_count);
184
185 // DEBUG: Log first tile read
186 static int debug_read_count = 0;
187 bool should_log = (debug_read_count < 3);
188
189 for (int i = 0; i < tile_count; i++) {
190 int tile_offset = address + (i * 2); // 2 bytes per tile word
191
192 // Read 1 word (2 bytes) per tile - this is the SNES tile format
193 uint16_t tile_word = rom_->data()[tile_offset] | (rom_->data()[tile_offset + 1] << 8);
194
195 auto tile_info = gfx::WordToTileInfo(tile_word);
196 tiles.push_back(tile_info);
197
198 // DEBUG: Log first few tiles
199 if (should_log && i < 4) {
200 printf("[ObjectParser] ReadTile[%d]: addr=0x%06X word=0x%04X → id=0x%03X pal=%d mirror=(h:%d,v:%d)\n",
201 i, tile_offset, tile_word, tile_info.id_, tile_info.palette_,
202 tile_info.horizontal_mirror_, tile_info.vertical_mirror_);
203 }
204 }
205
206 if (should_log) {
207 printf("[ObjectParser] ReadTileData: addr=0x%06X count=%d → loaded %zu tiles\n",
208 address, tile_count, tiles.size());
209 debug_read_count++;
210 }
211
212 return tiles;
213}
214
215int ObjectParser::DetermineSubtype(int16_t object_id) const {
216 if (object_id >= 0x200) {
217 return 3;
218 } else if (object_id >= 0x100) {
219 return 2;
220 } else {
221 return 1;
222 }
223}
224
226 ObjectDrawInfo info;
227
228 // Map object ID to draw routine based on ZScream's subtype1_routines table
229 // This is based on the DungeonObjectData.cs mapping from ZScream
230
231 if (object_id == 0x00) {
232 info.draw_routine_id = 0; // RoomDraw_Rightwards2x2_1to15or32
233 info.routine_name = "Rightwards2x2_1to15or32";
234 info.tile_count = 4;
235 info.is_horizontal = true;
236 }
237 else if (object_id >= 0x01 && object_id <= 0x02) {
238 info.draw_routine_id = 1; // RoomDraw_Rightwards2x4_1to15or26
239 info.routine_name = "Rightwards2x4_1to15or26";
240 info.tile_count = 8;
241 info.is_horizontal = true;
242 }
243 else if (object_id >= 0x03 && object_id <= 0x04) {
244 info.draw_routine_id = 2; // RoomDraw_Rightwards2x4spaced4_1to16
245 info.routine_name = "Rightwards2x4spaced4_1to16";
246 info.tile_count = 8;
247 info.is_horizontal = true;
248 }
249 else if (object_id >= 0x05 && object_id <= 0x06) {
250 info.draw_routine_id = 3; // RoomDraw_Rightwards2x4spaced4_1to16_BothBG
251 info.routine_name = "Rightwards2x4spaced4_1to16_BothBG";
252 info.tile_count = 8;
253 info.is_horizontal = true;
254 info.both_layers = true;
255 }
256 else if (object_id >= 0x07 && object_id <= 0x08) {
257 info.draw_routine_id = 4; // RoomDraw_Rightwards2x2_1to16
258 info.routine_name = "Rightwards2x2_1to16";
259 info.tile_count = 4;
260 info.is_horizontal = true;
261 }
262 else if (object_id == 0x09) {
263 info.draw_routine_id = 5; // RoomDraw_DiagonalAcute_1to16
264 info.routine_name = "DiagonalAcute_1to16";
265 info.tile_count = 5;
266 info.is_horizontal = false;
267 }
268 else if (object_id >= 0x0A && object_id <= 0x0B) {
269 info.draw_routine_id = 6; // RoomDraw_DiagonalGrave_1to16
270 info.routine_name = "DiagonalGrave_1to16";
271 info.tile_count = 5;
272 info.is_horizontal = false;
273 }
274 else if (object_id >= 0x15 && object_id <= 0x1F) {
275 info.draw_routine_id = 7; // RoomDraw_DiagonalAcute_1to16_BothBG
276 info.routine_name = "DiagonalAcute_1to16_BothBG";
277 info.tile_count = 5;
278 info.is_horizontal = false;
279 info.both_layers = true;
280 }
281 else if (object_id >= 0x16 && object_id <= 0x20) {
282 info.draw_routine_id = 8; // RoomDraw_DiagonalGrave_1to16_BothBG
283 info.routine_name = "DiagonalGrave_1to16_BothBG";
284 info.tile_count = 5;
285 info.is_horizontal = false;
286 info.both_layers = true;
287 }
288 else if (object_id == 0x21) {
289 info.draw_routine_id = 9; // RoomDraw_Rightwards1x2_1to16_plus2
290 info.routine_name = "Rightwards1x2_1to16_plus2";
291 info.tile_count = 2;
292 info.is_horizontal = true;
293 }
294 else if (object_id == 0x22) {
295 info.draw_routine_id = 10; // RoomDraw_RightwardsHasEdge1x1_1to16_plus3
296 info.routine_name = "RightwardsHasEdge1x1_1to16_plus3";
297 info.tile_count = 1;
298 info.is_horizontal = true;
299 }
300 else if (object_id >= 0x23 && object_id <= 0x2E) {
301 info.draw_routine_id = 11; // RoomDraw_RightwardsHasEdge1x1_1to16_plus2
302 info.routine_name = "RightwardsHasEdge1x1_1to16_plus2";
303 info.tile_count = 1;
304 info.is_horizontal = true;
305 }
306 else if (object_id == 0x2F) {
307 info.draw_routine_id = 12; // RoomDraw_RightwardsTopCorners1x2_1to16_plus13
308 info.routine_name = "RightwardsTopCorners1x2_1to16_plus13";
309 info.tile_count = 2;
310 info.is_horizontal = true;
311 }
312 else if (object_id == 0x30) {
313 info.draw_routine_id = 13; // RoomDraw_RightwardsBottomCorners1x2_1to16_plus13
314 info.routine_name = "RightwardsBottomCorners1x2_1to16_plus13";
315 info.tile_count = 2;
316 info.is_horizontal = true;
317 }
318 else if (object_id >= 0x31 && object_id <= 0x32) {
319 info.draw_routine_id = 14; // CustomDraw
320 info.routine_name = "CustomDraw";
321 info.tile_count = 1;
322 }
323 else if (object_id == 0x33) {
324 info.draw_routine_id = 15; // RoomDraw_Rightwards4x4_1to16
325 info.routine_name = "Rightwards4x4_1to16";
326 info.tile_count = 16;
327 info.is_horizontal = true;
328 }
329 else if (object_id == 0x34) {
330 info.draw_routine_id = 16; // RoomDraw_Rightwards1x1Solid_1to16_plus3
331 info.routine_name = "Rightwards1x1Solid_1to16_plus3";
332 info.tile_count = 1;
333 info.is_horizontal = true;
334 }
335 else if (object_id == 0x35) {
336 info.draw_routine_id = 17; // RoomDraw_DoorSwitcherer
337 info.routine_name = "DoorSwitcherer";
338 info.tile_count = 1;
339 }
340 else if (object_id >= 0x36 && object_id <= 0x37) {
341 info.draw_routine_id = 18; // RoomDraw_RightwardsDecor4x4spaced2_1to16
342 info.routine_name = "RightwardsDecor4x4spaced2_1to16";
343 info.tile_count = 16;
344 info.is_horizontal = true;
345 }
346 else if (object_id == 0x38) {
347 info.draw_routine_id = 19; // RoomDraw_RightwardsStatue2x3spaced2_1to16
348 info.routine_name = "RightwardsStatue2x3spaced2_1to16";
349 info.tile_count = 6;
350 info.is_horizontal = true;
351 }
352 else if (object_id == 0x39 || object_id == 0x3D) {
353 info.draw_routine_id = 20; // RoomDraw_RightwardsPillar2x4spaced4_1to16
354 info.routine_name = "RightwardsPillar2x4spaced4_1to16";
355 info.tile_count = 8;
356 info.is_horizontal = true;
357 }
358 else if (object_id >= 0x3A && object_id <= 0x3B) {
359 info.draw_routine_id = 21; // RoomDraw_RightwardsDecor4x3spaced4_1to16
360 info.routine_name = "RightwardsDecor4x3spaced4_1to16";
361 info.tile_count = 12;
362 info.is_horizontal = true;
363 }
364 else if (object_id == 0x3C) {
365 info.draw_routine_id = 22; // RoomDraw_RightwardsDoubled2x2spaced2_1to16
366 info.routine_name = "RightwardsDoubled2x2spaced2_1to16";
367 info.tile_count = 8;
368 info.is_horizontal = true;
369 }
370 else if (object_id == 0x3E) {
371 info.draw_routine_id = 23; // RoomDraw_RightwardsDecor2x2spaced12_1to16
372 info.routine_name = "RightwardsDecor2x2spaced12_1to16";
373 info.tile_count = 4;
374 info.is_horizontal = true;
375 }
376 else if (object_id >= 0x3F && object_id <= 0x40) {
377 info.draw_routine_id = 24; // RoomDraw_RightwardsHasEdge1x1_1to16_plus2 (variant)
378 info.routine_name = "RightwardsHasEdge1x1_1to16_plus2_variant";
379 info.tile_count = 1;
380 info.is_horizontal = true;
381 }
382 else {
383 // Default to simple 1x1 solid for unmapped objects
384 info.draw_routine_id = 16; // Use solid block routine
385 info.routine_name = "DefaultSolid";
386 info.tile_count = 1;
387 info.is_horizontal = true;
388 }
389
390 return info;
391}
392
393} // namespace zelda3
394} // namespace yaze
auto data() const
Definition rom.h:203
auto size() const
Definition rom.h:202
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< 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.
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 DetermineSubtype(int16_t object_id) const
Determine object subtype from ID.
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:308
constexpr int kRoomObjectSubtype3
Definition room_object.h:45
constexpr int kRoomObjectSubtype1
Definition room_object.h:43
constexpr int kRoomObjectSubtype2
Definition room_object.h:44
constexpr int kRoomObjectTileAddress
Definition room_object.h:46
Main namespace for the application.
Draw routine information for object rendering.
Object routine information.
Object size and orientation information.
Object subtype information.