yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
room.cc
Go to the documentation of this file.
1#include "room.h"
2
3#include <yaze.h>
4
5#include <algorithm>
6#include <cstdint>
7#include <vector>
8
9#include "absl/strings/str_cat.h"
13#include "rom/rom.h"
14#include "rom/snes.h"
15#include "util/log.h"
23
24namespace yaze {
25namespace zelda3 {
26
27// Define room effect names in a single translation unit to avoid SIOF
28const std::string RoomEffect[8] = {
29 "Nothing",
30 "Nothing",
31 "Moving Floor",
32 "Moving Water",
33 "Trinexx Shell",
34 "Red Flashes",
35 "Light Torch to See Floor",
36 "Ganon's Darkness"
37};
38
39// Define room tag names in a single translation unit to avoid SIOF
40const std::string RoomTag[65] = {
41 "Nothing",
42 "NW Kill Enemy to Open",
43 "NE Kill Enemy to Open",
44 "SW Kill Enemy to Open",
45 "SE Kill Enemy to Open",
46 "W Kill Enemy to Open",
47 "E Kill Enemy to Open",
48 "N Kill Enemy to Open",
49 "S Kill Enemy to Open",
50 "Clear Quadrant to Open",
51 "Clear Full Tile to Open",
52 "NW Push Block to Open",
53 "NE Push Block to Open",
54 "SW Push Block to Open",
55 "SE Push Block to Open",
56 "W Push Block to Open",
57 "E Push Block to Open",
58 "N Push Block to Open",
59 "S Push Block to Open",
60 "Push Block to Open",
61 "Pull Lever to Open",
62 "Collect Prize to Open",
63 "Hold Switch Open Door",
64 "Toggle Switch to Open Door",
65 "Turn off Water",
66 "Turn on Water",
67 "Water Gate",
68 "Water Twin",
69 "Moving Wall Right",
70 "Moving Wall Left",
71 "Crash",
72 "Crash",
73 "Push Switch Exploding Wall",
74 "Holes 0",
75 "Open Chest (Holes 0)",
76 "Holes 1",
77 "Holes 2",
78 "Defeat Boss for Dungeon Prize",
79 "SE Kill Enemy to Push Block",
80 "Trigger Switch Chest",
81 "Pull Lever Exploding Wall",
82 "NW Kill Enemy for Chest",
83 "NE Kill Enemy for Chest",
84 "SW Kill Enemy for Chest",
85 "SE Kill Enemy for Chest",
86 "W Kill Enemy for Chest",
87 "E Kill Enemy for Chest",
88 "N Kill Enemy for Chest",
89 "S Kill Enemy for Chest",
90 "Clear Quadrant for Chest",
91 "Clear Full Tile for Chest",
92 "Light Torches to Open",
93 "Holes 3",
94 "Holes 4",
95 "Holes 5",
96 "Holes 6",
97 "Agahnim Room",
98 "Holes 7",
99 "Holes 8",
100 "Open Chest for Holes 8",
101 "Push Block for Chest",
102 "Clear Room for Triforce Door",
103 "Light Torches for Chest",
104 "Kill Boss Again"
105};
106
107RoomSize CalculateRoomSize(Rom* rom, int room_id) {
108 // Calculate the size of the room based on how many objects are used per room
109 // Some notes from hacker Zarby89
110 // vanilla rooms are using in average ~0x80 bytes
111 // a "normal" person who wants more details than vanilla will use around 0x100
112 // bytes per rooms you could fit 128 rooms like that in 1 bank
113 // F8000 I don't remember if that's PC or snes tho
114 // Check last rooms
115 // F8000+(roomid*3)
116 // So we want to search the rom() object at this addressed based on the room
117 // ID since it's the roomid * 3 we will by pulling 3 bytes at a time We can do
118 // this with the rom()->ReadByteVector(addr, size)
119 // Existing room size address calculation...
120 RoomSize room_size;
121 room_size.room_size_pointer = 0;
122 room_size.room_size = 0;
123
124 if (!rom || !rom->is_loaded() || rom->size() == 0) {
125 return room_size;
126 }
127
128 auto room_size_address = 0xF8000 + (room_id * 3);
129
130 // Bounds check
131 if (room_size_address < 0 || room_size_address + 2 >= static_cast<int>(rom->size())) {
132 return room_size;
133 }
134
135 // Reading bytes for long address construction
136 uint8_t low = rom->data()[room_size_address];
137 uint8_t high = rom->data()[room_size_address + 1];
138 uint8_t bank = rom->data()[room_size_address + 2];
139
140 // Constructing the long address
141 int long_address = (bank << 16) | (high << 8) | low;
142 room_size.room_size_pointer = long_address;
143
144 if (long_address == 0x0A8000) {
145 // Blank room disregard in size calculation
146 room_size.room_size = 0;
147 } else {
148 // use the long address to calculate the size of the room
149 // we will use the room_id_ to calculate the next room's address
150 // and subtract the two to get the size of the room
151
152 int next_room_address = 0xF8000 + ((room_id + 1) * 3);
153
154 // Bounds check for next room address
155 if (next_room_address < 0 || next_room_address + 2 >= static_cast<int>(rom->size())) {
156 return room_size;
157 }
158
159 // Reading bytes for long address construction
160 uint8_t next_low = rom->data()[next_room_address];
161 uint8_t next_high = rom->data()[next_room_address + 1];
162 uint8_t next_bank = rom->data()[next_room_address + 2];
163
164 // Constructing the long address
165 int next_long_address = (next_bank << 16) | (next_high << 8) | next_low;
166
167 // Calculate the size of the room
168 int actual_room_size = next_long_address - long_address;
169 room_size.room_size = actual_room_size;
170 }
171
172 return room_size;
173}
174
175// Loads a room from the ROM.
176// ASM: Bank 01, Underworld_LoadRoom ($01873A)
177Room LoadRoomFromRom(Rom* rom, int room_id) {
178 // Use the header loader to get the base room with properties
179 // ASM: JSR Underworld_LoadHeader ($01873A)
180 Room room = LoadRoomHeaderFromRom(rom, room_id);
181
182 // Load room objects
183 // ASM: RoomData_ObjectDataPointers ($018742 - reads pointer table)
184 int object_pointer = SnesToPc(room_object_pointer);
185 int room_address = object_pointer + (room_id * 3);
186 int objects_location = SnesToPc(room_address);
187
188 // Load sprites
189 // ASM: Bank 09 logic (referenced via long pointers in Bank 01)
190 int spr_ptr = 0x040000 | rooms_sprite_pointer;
191 int sprite_address = SnesToPc(dungeon_spr_ptrs | spr_ptr + (room_id * 2));
192
193 // Load additional room features
194 room.LoadDoors();
195 room.LoadPotItems();
196 room.LoadTorches();
197 room.LoadTorches();
198 room.LoadTorches();
199 room.LoadBlocks();
200 room.LoadPits();
201
202 room.SetLoaded(true);
203 return room;
204}
205
206Room LoadRoomHeaderFromRom(Rom* rom, int room_id) {
207 Room room(room_id, rom);
208
209 if (!rom || !rom->is_loaded() || rom->size() == 0) {
210 return room;
211 }
212
213 // Validate kRoomHeaderPointer access
214 if (kRoomHeaderPointer < 0 || kRoomHeaderPointer + 2 >= static_cast<int>(rom->size())) {
215 return room;
216 }
217
218 // ASM: RoomHeader_RoomToPointer table lookup
219 int header_pointer = (rom->data()[kRoomHeaderPointer + 2] << 16) +
220 (rom->data()[kRoomHeaderPointer + 1] << 8) +
221 (rom->data()[kRoomHeaderPointer]);
222 header_pointer = SnesToPc(header_pointer);
223
224 // Validate kRoomHeaderPointerBank access
225 if (kRoomHeaderPointerBank < 0 || kRoomHeaderPointerBank >= static_cast<int>(rom->size())) {
226 return room;
227 }
228
229 // Validate header_pointer table access
230 int table_offset = (header_pointer) + (room_id * 2);
231 if (table_offset < 0 || table_offset + 1 >= static_cast<int>(rom->size())) {
232 return room;
233 }
234
235 int address = (rom->data()[kRoomHeaderPointerBank] << 16) +
236 (rom->data()[table_offset + 1] << 8) +
237 rom->data()[table_offset];
238
239 auto header_location = SnesToPc(address);
240
241 // Validate header_location access (we read up to +13 bytes)
242 if (header_location < 0 || header_location + 13 >= static_cast<int>(rom->size())) {
243 return room;
244 }
245
246 room.SetBg2((background2)((rom->data()[header_location] >> 5) & 0x07));
247 room.SetCollision((CollisionKey)((rom->data()[header_location] >> 2) & 0x07));
248 room.SetIsLight(((rom->data()[header_location]) & 0x01) == 1);
249
250 if (room.IsLight()) {
251 room.SetBg2(background2::DarkRoom);
252 }
253
254 room.SetPalette(((rom->data()[header_location + 1] & 0x3F)));
255 room.SetBlockset((rom->data()[header_location + 2]));
256 room.SetSpriteset((rom->data()[header_location + 3]));
257 room.SetEffect((EffectKey)((rom->data()[header_location + 4])));
258 room.SetTag1((TagKey)((rom->data()[header_location + 5])));
259 room.SetTag2((TagKey)((rom->data()[header_location + 6])));
260
261 room.SetStaircasePlane(0, ((rom->data()[header_location + 7] >> 2) & 0x03));
262 room.SetStaircasePlane(1, ((rom->data()[header_location + 7] >> 4) & 0x03));
263 room.SetStaircasePlane(2, ((rom->data()[header_location + 7] >> 6) & 0x03));
264 room.SetStaircasePlane(3, ((rom->data()[header_location + 8]) & 0x03));
265
266 room.SetHolewarp((rom->data()[header_location + 9]));
267 room.SetStaircaseRoom(0, (rom->data()[header_location + 10]));
268 room.SetStaircaseRoom(1, (rom->data()[header_location + 11]));
269 room.SetStaircaseRoom(2, (rom->data()[header_location + 12]));
270 room.SetStaircaseRoom(3, (rom->data()[header_location + 13]));
271
272 // =====
273
274 // Validate kRoomHeaderPointer access (again, just in case)
275 if (kRoomHeaderPointer < 0 || kRoomHeaderPointer + 2 >= static_cast<int>(rom->size())) {
276 return room;
277 }
278
279 int header_pointer_2 = (rom->data()[kRoomHeaderPointer + 2] << 16) +
280 (rom->data()[kRoomHeaderPointer + 1] << 8) +
281 (rom->data()[kRoomHeaderPointer]);
282 header_pointer_2 = SnesToPc(header_pointer_2);
283
284 // Validate kRoomHeaderPointerBank access
285 if (kRoomHeaderPointerBank < 0 || kRoomHeaderPointerBank >= static_cast<int>(rom->size())) {
286 return room;
287 }
288
289 // Validate header_pointer_2 table access
290 int table_offset_2 = (header_pointer_2) + (room_id * 2);
291 if (table_offset_2 < 0 || table_offset_2 + 1 >= static_cast<int>(rom->size())) {
292 return room;
293 }
294
295 int address_2 = (rom->data()[kRoomHeaderPointerBank] << 16) +
296 (rom->data()[table_offset_2 + 1] << 8) +
297 rom->data()[table_offset_2];
298
299 room.SetMessageIdDirect(messages_id_dungeon + (room_id * 2));
300
301 auto hpos = SnesToPc(address_2);
302
303 // Validate hpos access (we read sequentially)
304 // We read about 14 bytes (hpos++ calls)
305 if (hpos < 0 || hpos + 14 >= static_cast<int>(rom->size())) {
306 return room;
307 }
308
309 hpos++;
310 uint8_t b = rom->data()[hpos];
311
312 room.SetLayer2Mode((b >> 5));
313 room.SetLayerMerging(kLayerMergeTypeList[(b & 0x0C) >> 2]);
314
315 room.SetIsDark((b & 0x01) == 0x01);
316 hpos++;
317 room.SetPaletteDirect(rom->data()[hpos]);
318 hpos++;
319
320 room.SetBackgroundTileset(rom->data()[hpos]);
321 hpos++;
322
323 room.SetSpriteTileset(rom->data()[hpos]);
324 hpos++;
325
326 room.SetLayer2Behavior(rom->data()[hpos]);
327 hpos++;
328
329 room.SetTag1Direct((TagKey)rom->data()[hpos]);
330 hpos++;
331
332 room.SetTag2Direct((TagKey)rom->data()[hpos]);
333 hpos++;
334
335 b = rom->data()[hpos];
336
337 room.SetPitsTargetLayer((uint8_t)(b & 0x03));
338 room.SetStair1TargetLayer((uint8_t)((b >> 2) & 0x03));
339 room.SetStair2TargetLayer((uint8_t)((b >> 4) & 0x03));
340 room.SetStair3TargetLayer((uint8_t)((b >> 6) & 0x03));
341 hpos++;
342 room.SetStair4TargetLayer((uint8_t)(rom->data()[hpos] & 0x03));
343 hpos++;
344
345 room.SetPitsTarget(rom->data()[hpos]);
346 hpos++;
347 room.SetStair1Target(rom->data()[hpos]);
348 hpos++;
349 room.SetStair2Target(rom->data()[hpos]);
350 hpos++;
351 room.SetStair3Target(rom->data()[hpos]);
352 hpos++;
353 room.SetStair4Target(rom->data()[hpos]);
354
355 // Note: We do NOT set is_loaded_ to true here, as this is just the header
356 return room;
357}
358
359Room::Room(int room_id, Rom* rom, GameData* game_data)
360 : room_id_(room_id),
361 rom_(rom),
362 game_data_(game_data),
363 dungeon_state_(std::make_unique<EditorDungeonState>(rom, game_data)) {}
364
365Room::Room() = default;
366Room::~Room() = default;
367Room::Room(Room&&) = default;
368Room& Room::operator=(Room&&) = default;
369
370void Room::LoadRoomGraphics(uint8_t entrance_blockset) {
371 if (!game_data_) {
372 printf("[LoadRoomGraphics] GameData not set for room %d\n", room_id_);
373 return;
374 }
375
376 const auto& room_gfx = game_data_->room_blockset_ids;
377 const auto& sprite_gfx = game_data_->spriteset_ids;
378
379 // DEBUG: Log blockset being used
380 printf("[LoadRoomGraphics] Room %d: blockset=%d, spriteset=%d, palette=%d\n",
382
383 for (int i = 0; i < 8; i++) {
385 // Block 6 can be overridden by entrance-specific room graphics (index 3)
386 // Note: The "3-6" comment was misleading - only block 6 uses room_gfx
387 if (i == 6) {
388 if (entrance_blockset != 0xFF &&
389 room_gfx[entrance_blockset][3] != 0) {
390 blocks_[i] = room_gfx[entrance_blockset][3];
391 }
392 }
393 }
394
395 blocks_[8] = 115 + 0; // Static Sprites Blocksets (fairy,pot,ect...)
396 blocks_[9] = 115 + 10;
397 blocks_[10] = 115 + 6;
398 blocks_[11] = 115 + 7;
399 for (int i = 0; i < 4; i++) {
400 blocks_[12 + i] = (uint8_t)(sprite_gfx[spriteset + 64][i] + 115);
401 } // 12-16 sprites
402
403 // DEBUG: Log sheet IDs being used for this room
404 printf("[LoadRoomGraphics] Sheet IDs for blocks 0-15:\n");
405 printf(" BG blocks 0-7: ");
406 for (int i = 0; i < 8; i++) printf("%3d ", blocks_[i]);
407 printf("\n Sprite blocks 8-15: ");
408 for (int i = 8; i < 16; i++) printf("%3d ", blocks_[i]);
409 printf("\n");
410}
411
412constexpr int kGfxBufferOffset = 92 * 2048;
413constexpr int kGfxBufferStride = 1024;
414constexpr int kGfxBufferAnimatedFrameOffset = 7 * 4096;
415constexpr int kGfxBufferAnimatedFrameStride = 1024;
416constexpr int kGfxBufferRoomOffset = 4096;
417constexpr int kGfxBufferRoomSpriteOffset = 1024;
418constexpr int kGfxBufferRoomSpriteStride = 4096;
420
422 if (!rom_ || !rom_->is_loaded()) {
423 printf("[CopyRoomGraphicsToBuffer] ROM not loaded\n");
424 return;
425 }
426
427 if (!game_data_) {
428 printf("[CopyRoomGraphicsToBuffer] GameData not set\n");
429 return;
430 }
431 auto* gfx_buffer_data = &game_data_->graphics_buffer;
432 if (gfx_buffer_data->empty()) {
433 printf("[CopyRoomGraphicsToBuffer] Graphics buffer is empty\n");
434 return;
435 }
436
437
438
439 printf("[CopyRoomGraphicsToBuffer] Room %d: Copying 8BPP graphics (Linear)\n", room_id_);
440 printf("[CopyRoomGraphicsToBuffer] Buffer size: %zu bytes\n", gfx_buffer_data->size());
441
442 // Clear destination buffer
443 std::fill(current_gfx16_.begin(), current_gfx16_.end(), 0);
444
445 // Process each of the 16 graphics blocks
446 for (int block = 0; block < 16; block++) {
447 int sheet_id = blocks_[block];
448
449 // Validate block index
450 if (sheet_id >= 223) { // kNumGfxSheets
451 LOG_WARN("Room", "Invalid sheet index %d for block %d", sheet_id, block);
452 continue;
453 }
454
455 // Source offset in ROM graphics buffer (now 8BPP format)
456 // Each 8BPP sheet is 4096 bytes (128x32 pixels)
457 int src_sheet_offset = sheet_id * 4096;
458
459 // Validate source bounds
460 if (src_sheet_offset + 4096 > gfx_buffer_data->size()) {
461 LOG_ERROR("Room", "Graphics offset out of bounds: %d (size: %zu)",
462 src_sheet_offset, gfx_buffer_data->size());
463 continue;
464 }
465
466 // DEBUG: Log the first few bytes of the sheet to verify content
467 if (block < 4) { // Only log first few blocks to avoid spam
468 printf("[CopyRoomGraphicsToBuffer] Block %d (Sheet %d): Offset %d\n",
469 block, sheet_id, src_sheet_offset);
470 printf(" Bytes: %02X %02X %02X %02X %02X %02X %02X %02X\n",
471 (*gfx_buffer_data)[src_sheet_offset],
472 (*gfx_buffer_data)[src_sheet_offset+1],
473 (*gfx_buffer_data)[src_sheet_offset+2],
474 (*gfx_buffer_data)[src_sheet_offset+3],
475 (*gfx_buffer_data)[src_sheet_offset+4],
476 (*gfx_buffer_data)[src_sheet_offset+5],
477 (*gfx_buffer_data)[src_sheet_offset+6],
478 (*gfx_buffer_data)[src_sheet_offset+7]);
479 }
480
481 // Copy 4096 bytes for the 8BPP sheet
482 int dest_index_base = block * 4096;
483 if (dest_index_base + 4096 <= current_gfx16_.size()) {
484 memcpy(current_gfx16_.data() + dest_index_base,
485 gfx_buffer_data->data() + src_sheet_offset, 4096);
486 }
487 }
488 printf("[CopyRoomGraphicsToBuffer] Room %d: Copied graphics blocks\n",
489 room_id_);
490
491 // DEBUG: Print first few bytes of converted graphics
492 printf("[CopyRoomGraphicsToBuffer] First 16 bytes of current_gfx16_:\n");
493 for (int i = 0; i < 16; i++) {
494 printf("%02X ", current_gfx16_[i]);
495 }
496 printf("\n");
497
498 // DEBUG: Count how many zeros are in the buffer (should be many for transparent pixels)
499 int zero_count = 0;
500 for (size_t i = 0; i < current_gfx16_.size(); i++) {
501 if (current_gfx16_[i] == 0) zero_count++;
502 }
503 printf("[CopyRoomGraphicsToBuffer] Zero bytes (transparent): %d / %zu (%.1f%%)\n",
504 zero_count, current_gfx16_.size(),
505 100.0f * zero_count / current_gfx16_.size());
506
508}
509
511 if (composite_dirty_) {
512 layer_mgr.CompositeToOutput(*this, composite_bitmap_);
513 composite_dirty_ = false;
514 }
515 return composite_bitmap_;
516}
517
519 // PERFORMANCE OPTIMIZATION: Check if room properties have changed
520 bool properties_changed = false;
521
522 // Check if graphics properties changed
533 graphics_dirty_ = true;
534 properties_changed = true;
535 }
536
537 // Check if effect/tags changed
538 if (cached_effect_ != static_cast<uint8_t>(effect_) ||
540 cached_effect_ = static_cast<uint8_t>(effect_);
543 objects_dirty_ = true;
544 properties_changed = true;
545 }
546
547 // If nothing changed and textures exist, skip rendering
548 if (!properties_changed && !graphics_dirty_ && !objects_dirty_ &&
550 auto& bg1_bmp = bg1_buffer_.bitmap();
551 auto& bg2_bmp = bg2_buffer_.bitmap();
552 if (bg1_bmp.texture() && bg2_bmp.texture()) {
553 LOG_DEBUG("[RenderRoomGraphics]",
554 "Room %d: No changes detected, skipping render", room_id_);
555 return;
556 }
557 }
558
559 LOG_DEBUG("[RenderRoomGraphics]",
560 "Room %d: Rendering graphics (dirty_flags: g=%d o=%d l=%d t=%d)",
563
564 // Capture dirty state BEFORE clearing flags (needed for floor/bg draw logic)
565 bool was_graphics_dirty = graphics_dirty_;
566 bool was_layout_dirty = layout_dirty_;
567
568 // STEP 0: Load graphics if needed
569 if (graphics_dirty_) {
570 // Ensure blocks_[] array is properly initialized before copying graphics
571 // LoadRoomGraphics sets up which sheets go into which blocks
574 graphics_dirty_ = false;
575 }
576
577 // Debug: Log floor graphics values
578 LOG_DEBUG("[RenderRoomGraphics]",
579 "Room %d: floor1=%d, floor2=%d, blocks_size=%zu", room_id_,
581
582 // STEP 1: Draw floor tiles to bitmaps (base layer) - if graphics changed OR
583 // bitmaps not created yet
584 bool need_floor_draw = was_graphics_dirty;
585 auto& bg1_bmp = bg1_buffer_.bitmap();
586 auto& bg2_bmp = bg2_buffer_.bitmap();
587
588 // Always draw floor if bitmaps don't exist yet (first time rendering)
589 if (!bg1_bmp.is_active() || bg1_bmp.width() == 0 || !bg2_bmp.is_active() ||
590 bg2_bmp.width() == 0) {
591 need_floor_draw = true;
592 LOG_DEBUG("[RenderRoomGraphics]",
593 "Room %d: Bitmaps not created yet, forcing floor draw", room_id_);
594 }
595
596 if (need_floor_draw) {
601 }
602
603 // STEP 2: Draw background tiles (floor pattern) to bitmap
604 // This converts the floor tile buffer to pixels
605 bool need_bg_draw = was_graphics_dirty || need_floor_draw;
606 if (need_bg_draw) {
607 bg1_buffer_.DrawBackground(std::span<uint8_t>(current_gfx16_));
608 bg2_buffer_.DrawBackground(std::span<uint8_t>(current_gfx16_));
609 }
610
611 // STEP 3: Draw layout objects ON TOP of floor
612 // Layout objects (walls, corners) are drawn after floor so they appear over it
613 // TODO(zelda3-hacking-expert): Mirror the SNES four-pass pipeline from
614 // assets/asm/usdasm/bank_01.asm (documented in
615 // docs/internal/agents/dungeon-object-rendering-spec.md): layout list,
616 // main list, BG2 overlay list, BG1 overlay list, with BothBG routines
617 // writing simultaneously. Today we only emit one layout pass + one object
618 // list, so BG overlays and dual-layer draws can end up wrong (layout objects
619 // rendering above later passes or BG merge treated as exclusive).
620 if (was_layout_dirty || need_floor_draw) {
622 layout_dirty_ = false;
623 }
624
625 // Get and apply palette BEFORE rendering objects (so objects use correct colors)
626 if (!game_data_) return;
627 auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
628 int num_palettes = dungeon_pal_group.size();
629 if (num_palettes == 0) return;
630
631 // Look up dungeon palette ID using the two-level paletteset_ids table.
632 // paletteset_ids[palette][0] contains a BYTE OFFSET into the palette pointer
633 // table at kDungeonPalettePointerTable. The word at that offset, divided by
634 // 180 (bytes per palette), gives the actual palette index (0-19).
635 int palette_id = palette;
636 if (palette < game_data_->paletteset_ids.size() &&
637 !game_data_->paletteset_ids[palette].empty()) {
638 auto dungeon_palette_ptr = game_data_->paletteset_ids[palette][0];
639 auto palette_word =
640 rom()->ReadWord(kDungeonPalettePointerTable + dungeon_palette_ptr);
641 if (palette_word.ok()) {
642 palette_id = palette_word.value() / 180;
643 }
644 }
645 if (palette_id < 0 || palette_id >= num_palettes) {
646 palette_id = 0;
647 }
648
649 auto bg1_palette = dungeon_pal_group[palette_id];
650
651 // DEBUG: Log palette loading
653 "Room::RenderRoomGraphics", palette_id, bg1_palette);
654
655 printf("[RenderRoomGraphics] Palette ID: %d, Size: %zu\n", palette_id, bg1_palette.size());
656 if (!bg1_palette.empty()) {
657 printf("[RenderRoomGraphics] First color: R=%d G=%d B=%d\n",
658 bg1_palette[0].rom_color().red, bg1_palette[0].rom_color().green, bg1_palette[0].rom_color().blue);
659 }
660
661 // Store current palette and bitmap for pixel inspector debugging
664
665 if (bg1_palette.size() > 0) {
666 // Apply FULL 90-color dungeon palette
667 // IMPORTANT: Palette colors must NOT be shifted! The drawing code uses:
668 // final_color = (pixel - 1) + palette_offset
669 // Where pixel 0 is never drawn (transparent), and pixel 1 maps to index 0.
670 // So palette[0] must be the FIRST actual color, not transparent.
671 auto set_dungeon_palette = [](gfx::Bitmap& bmp, const gfx::SnesPalette& pal) {
672 std::vector<SDL_Color> colors(256);
673
674 // Map palette colors directly to indices 0..N-1 (NO SHIFT!)
675 // This matches the drawing formula: (pixel - 1) + palette_offset
676 // pixel=1, pal=0 → index 0 = first color
677 for (size_t i = 0; i < pal.size() && i < 256; ++i) {
678 ImVec4 rgb = pal[i].rgb();
679 colors[i] = {
680 static_cast<Uint8>(rgb.x),
681 static_cast<Uint8>(rgb.y),
682 static_cast<Uint8>(rgb.z),
683 255 // Opaque
684 };
685 }
686
687 // Use index 255 as the transparent color key (never used in 90-color palette)
688 // Transparent pixels (pixel=0) are never written, so this is safe
689 colors[255] = {0, 0, 0, 0};
690 // TODO(zelda3-hacking-expert): Rebuild this per chunk (16-color banks),
691 // not linear “90-color”; chunk n goes to [n*16..n*16+15] with 0 as
692 // transparent/colorkey. See docs/internal/agents/dungeon-palette-fix-plan.md.
693
694 bmp.SetPalette(colors);
695 if (bmp.surface()) {
696 // Set color key to 255 (unused index) for proper alpha blending
697 SDL_SetColorKey(bmp.surface(), SDL_TRUE, 255);
698 SDL_SetSurfaceBlendMode(bmp.surface(), SDL_BLENDMODE_BLEND);
699 }
700 };
701
702 set_dungeon_palette(bg1_bmp, bg1_palette);
703 set_dungeon_palette(bg2_bmp, bg1_palette);
704 set_dungeon_palette(object_bg1_buffer_.bitmap(), bg1_palette);
705 set_dungeon_palette(object_bg2_buffer_.bitmap(), bg1_palette);
706
707 // DEBUG: Verify palette was applied to SDL surface
708 auto* surface = bg1_bmp.surface();
709 if (surface) {
710 SDL_Palette* palette = platform::GetSurfacePalette(surface);
711 if (palette) {
713 "Room::RenderRoomGraphics (BG1)", palette_id, true);
714
715 // Log surface state for detailed debugging
717 "Room::RenderRoomGraphics (after SetPalette)", surface);
718
719 // Sample color 56 (pal=7, offset 0)
720 if (palette->ncolors > 56) {
721 auto& c = palette->colors[56];
722 if (c.r != 156 || c.g != 107 || c.b != 107) {
724 "Room::RenderRoomGraphics", palette_id, false,
725 absl::StrFormat("Color 56 mismatch: got R=%d G=%d B=%d", c.r,
726 c.g, c.b));
727 }
728 }
729 } else {
731 "Room::RenderRoomGraphics", palette_id, false,
732 "SDL surface has no palette!");
733 }
734 }
735
736 // Apply Layer Merge effects (Transparency/Blending) to BG2
737 // NOTE: These SDL blend settings are for direct SDL rendering paths.
738 // RoomLayerManager::CompositeToOutput uses manual pixel compositing and
739 // handles blend modes separately via its layer_blend_mode_ array.
740 // TODO(scawful): Consolidate blend handling - either implement proper
741 // blending in CompositeLayer or ensure SDL path uses RoomLayerManager.
743 // Set alpha mod for translucency (50%)
744 if (bg2_bmp.surface()) {
745 SDL_SetSurfaceAlphaMod(bg2_bmp.surface(), 128);
746 }
747 if (object_bg2_buffer_.bitmap().surface()) {
748 SDL_SetSurfaceAlphaMod(object_bg2_buffer_.bitmap().surface(), 128);
749 }
750
751 // Check for Addition mode (ID 0x05)
752 if (layer_merging_.ID == 0x05) {
753 if (bg2_bmp.surface()) {
754 SDL_SetSurfaceBlendMode(bg2_bmp.surface(), SDL_BLENDMODE_ADD);
755 }
756 if (object_bg2_buffer_.bitmap().surface()) {
757 SDL_SetSurfaceBlendMode(object_bg2_buffer_.bitmap().surface(),
758 SDL_BLENDMODE_ADD);
759 }
760 }
761 }
762 }
763
764 // Render objects ON TOP of background tiles (AFTER palette is set)
765 // ObjectDrawer will write indexed pixel data that uses the palette we just
766 // set
768
769 // PERFORMANCE OPTIMIZATION: Queue texture commands but DON'T process
770 // immediately. This allows multiple rooms to batch their texture updates
771 // together. Processing happens in DrawDungeonCanvas() once per frame.
772 //
773 // IMPORTANT: Check each buffer INDIVIDUALLY for existing texture.
774 // Layout and object buffers may have different states (e.g., layout rendered
775 // but objects added later need CREATE, not UPDATE).
776 auto queue_texture = [](gfx::Bitmap* bitmap, const char* name) {
777 if (bitmap->texture()) {
778 LOG_DEBUG("[RenderRoomGraphics]", "Queueing UPDATE for %s", name);
781 } else {
782 LOG_DEBUG("[RenderRoomGraphics]", "Queueing CREATE for %s", name);
785 }
786 };
787
788 queue_texture(&bg1_bmp, "bg1_buffer");
789 queue_texture(&bg2_bmp, "bg2_buffer");
790 queue_texture(&object_bg1_buffer_.bitmap(), "object_bg1_buffer");
791 queue_texture(&object_bg2_buffer_.bitmap(), "object_bg2_buffer");
792
793 // Mark textures as clean after successful queuing
794 textures_dirty_ = false;
795
796 // IMPORTANT: Mark composite as dirty after any render work
797 // This ensures GetCompositeBitmap() regenerates the merged output
798 composite_dirty_ = true;
799
800 // REMOVED: Don't process texture queue here - let it be batched!
801 // Processing happens once per frame in DrawDungeonCanvas()
802 // This dramatically improves performance when multiple rooms are open
803 // gfx::Arena::Get().ProcessTextureQueue(nullptr); // OLD: Caused slowdown!
804 LOG_DEBUG("[RenderRoomGraphics]",
805 "Texture commands queued for batch processing");
806}
807
808
809
811 // DEBUG: Use printf for visibility
812 printf("[LoadLayoutTilesToBuffer] START for room %d, layout=%d\n", room_id_, layout);
813 fflush(stdout); // Ensure output is visible
814 LOG_DEBUG("RenderRoomGraphics", "LoadLayoutTilesToBuffer START for room %d",
815 room_id_);
816
817 if (!rom_ || !rom_->is_loaded()) {
818 printf("[LoadLayoutTilesToBuffer] ROM not loaded, aborting\n");
819 LOG_DEBUG("RenderRoomGraphics", "ROM not loaded, aborting");
820 return;
821 }
822
823 // Load layout tiles from ROM if not already loaded
825 auto layout_status = layout_.LoadLayout(layout);
826 if (!layout_status.ok()) {
827 printf("[LoadLayoutTilesToBuffer] Failed to load layout %d: %s\n",
828 layout, std::string(layout_status.message()).c_str());
829 LOG_DEBUG("RenderRoomGraphics", "Failed to load layout %d: %s",
830 layout, layout_status.message().data());
831 return;
832 }
833
834 const auto& layout_objects = layout_.GetObjects();
835 printf("[LoadLayoutTilesToBuffer] Layout %d has %zu objects\n", layout, layout_objects.size());
836 fflush(stdout); // Ensure output is visible
837 LOG_DEBUG("RenderRoomGraphics", "Layout has %zu objects",
838 layout_objects.size());
839 if (layout_objects.empty()) {
840 return;
841 }
842
843 // Use ObjectDrawer to render layout objects properly
844 // Layout objects are the same format as room objects and need draw routines
845 // to render correctly (walls, corners, etc.)
846 if (!game_data_) {
847 LOG_DEBUG("RenderRoomGraphics", "GameData not set, cannot render layout");
848 return;
849 }
850
851 // Get palette for layout rendering
852 // Get palette for layout rendering using two-level lookup
853 auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
854 int num_palettes = dungeon_pal_group.size();
855 if (num_palettes == 0) return;
856 int palette_id = palette;
857 if (palette < game_data_->paletteset_ids.size() &&
858 !game_data_->paletteset_ids[palette].empty()) {
859 auto dungeon_palette_ptr = game_data_->paletteset_ids[palette][0];
860 auto palette_word =
861 rom()->ReadWord(kDungeonPalettePointerTable + dungeon_palette_ptr);
862 if (palette_word.ok()) {
863 palette_id = palette_word.value() / 180;
864 }
865 }
866 if (palette_id < 0 || palette_id >= num_palettes) {
867 palette_id = 0;
868 }
869
870 auto room_palette = dungeon_pal_group[palette_id];
871 gfx::PaletteGroup palette_group;
872 palette_group.AddPalette(room_palette);
873 // TODO(zelda3-hacking-expert): Align palette chunking with 16-color banks
874 // per docs/internal/agents/dungeon-palette-fix-plan.md. SDL palette should
875 // map each subpalette to indices [n*16..n*16+15] with index 0 transparent,
876 // using the same palette for bg1/bg2/object buffers. Add assertions/logging
877 // for palette_id/pointer mismatch against usdasm ($0DEC4B pointers).
878
879 // Draw layout objects using proper draw routines via RoomLayout
880 auto status = layout_.Draw(room_id_, current_gfx16_.data(), bg1_buffer_,
881 bg2_buffer_, palette_group, dungeon_state_.get());
882
883 if (!status.ok()) {
884 LOG_DEBUG("RenderRoomGraphics", "Layout Draw failed: %s",
885 std::string(status.message().data(), status.message().size())
886 .c_str());
887 } else {
888 LOG_DEBUG("RenderRoomGraphics", "Layout rendered with %zu objects",
889 layout_objects.size());
890 }
891}
892
894 LOG_DEBUG("[RenderObjectsToBackground]",
895 "Starting object rendering for room %d", room_id_);
896
897 if (!rom_ || !rom_->is_loaded()) {
898 LOG_DEBUG("[RenderObjectsToBackground]", "ROM not loaded, aborting");
899 return;
900 }
901
902 // PERFORMANCE OPTIMIZATION: Only render objects if they have changed or if
903 // graphics changed Also render if bitmaps were just created (need_floor_draw
904 // was true in RenderRoomGraphics)
905 auto& bg1_bmp = bg1_buffer_.bitmap();
906 auto& bg2_bmp = bg2_buffer_.bitmap();
907 bool bitmaps_exist = bg1_bmp.is_active() && bg1_bmp.width() > 0 &&
908 bg2_bmp.is_active() && bg2_bmp.width() > 0;
909
910 if (!objects_dirty_ && !graphics_dirty_ && bitmaps_exist) {
911 LOG_DEBUG("[RenderObjectsToBackground]",
912 "Room %d: Objects not dirty, skipping render", room_id_);
913 return;
914 }
915
916 // Handle rendering based on mode (currently using emulator-based rendering)
917 // Emulator or Hybrid mode (use ObjectDrawer)
918 LOG_DEBUG("[RenderObjectsToBackground]",
919 "Room %d: Emulator rendering objects", room_id_);
920 // Get palette group for object rendering (use SAME lookup as
921 // RenderRoomGraphics)
922 if (!game_data_) return;
923 auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
924 int num_palettes = dungeon_pal_group.size();
925
926 // Look up dungeon palette ID using the two-level paletteset_ids table.
927 // (same lookup as RenderRoomGraphics and LoadLayoutTilesToBuffer)
928 int palette_id = palette;
929 if (palette < game_data_->paletteset_ids.size() &&
930 !game_data_->paletteset_ids[palette].empty()) {
931 auto dungeon_palette_ptr = game_data_->paletteset_ids[palette][0];
932 auto palette_word =
933 rom()->ReadWord(kDungeonPalettePointerTable + dungeon_palette_ptr);
934 if (palette_word.ok()) {
935 palette_id = palette_word.value() / 180;
936 }
937 }
938 if (palette_id < 0 || palette_id >= num_palettes) {
939 palette_id = 0;
940 }
941
942 auto room_palette = dungeon_pal_group[palette_id];
943 // Dungeon palettes are 90-color palettes for 3BPP graphics (8-color strides)
944 // Pass the full palette to ObjectDrawer so it can handle all palette indices
945 gfx::PaletteGroup palette_group;
946 palette_group.AddPalette(room_palette);
947
948 // Use ObjectDrawer for pattern-based object rendering
949 // This provides proper wall/object drawing patterns
950 // Pass the room-specific graphics buffer (current_gfx16_) so objects use
951 // correct tiles
953 // TODO(zelda3-hacking-expert): When we split the object stream into the
954 // four ASM layers, ensure DrawObjectList is invoked per stream with proper
955 // target buffers so BothBG routines (ceiling corners, merged stairs, etc.)
956 // land on both BG1/BG2 as in bank_01.asm. See
957 // docs/internal/agents/dungeon-object-rendering-spec.md for the expected
958 // order and dual-layer handling.
959
960 // Clear object buffers before rendering
961 // IMPORTANT: Fill with 255 (transparent color key) so objects overlay correctly
962 // on the floor. We use index 255 as transparent since palette has 90 colors (0-89).
965 object_bg1_buffer_.bitmap().Fill(255);
966 object_bg2_buffer_.bitmap().Fill(255);
967
968 // IMPORTANT: Clear priority buffers when clearing object buffers
969 // Otherwise, old priority values persist and cause incorrect Z-ordering
972
973 // Render objects to appropriate buffers
974 // BG1 = Floor/Main (Layer 0, 2)
975 // BG2 = Overlay (Layer 1)
976 // Pass bg1_buffer_ for BG2 object masking - this creates "holes" in the floor
977 // so BG2 overlay content (platforms, statues) shows through BG1 floor tiles
979 palette_group, dungeon_state_.get(),
980 &bg1_buffer_);
981
982 // Render doors using DoorDef struct with enum types
983 // Doors are drawn to the OBJECT buffer for layer visibility control
984 // This allows doors to remain visible when toggling BG1_Layout off
985 for (int i = 0; i < static_cast<int>(doors_.size()); ++i) {
986 const auto& door = doors_[i];
987 ObjectDrawer::DoorDef door_def;
988 door_def.type = door.type;
989 door_def.direction = door.direction;
990 door_def.position = door.position;
991 // Draw doors to object buffers (not layout buffers) so they remain visible
992 // when BG1_Layout is hidden. Doors are objects, not layout tiles.
994 dungeon_state_.get());
995 }
996 // Mark object buffer as modified so texture gets updated
997 if (!doors_.empty()) {
998 object_bg1_buffer_.bitmap().set_modified(true);
999 }
1000
1001 // Render pot items
1002 // Pot items now have their own position from ROM data
1003 // No need to match to objects - each item has exact coordinates
1004 for (const auto& pot_item : pot_items_) {
1005 if (pot_item.item != 0) { // Skip "Nothing" items
1006 // PotItem provides pixel coordinates, convert to tile coords
1007 int tile_x = pot_item.GetTileX();
1008 int tile_y = pot_item.GetTileY();
1009 drawer.DrawPotItem(pot_item.item, tile_x, tile_y, object_bg1_buffer_);
1010 }
1011 }
1012
1013 // Render sprites (for key drops)
1014 // We don't have full sprite rendering yet, but we can visualize key drops
1015 for (const auto& sprite : sprites_) {
1016 if (sprite.key_drop() > 0) {
1017 // Draw key drop visualization
1018 // Use a special item ID or just draw a key icon
1019 // We can reuse DrawPotItem with a special ID for key
1020 // Or add DrawKeyDrop to ObjectDrawer
1021 // For now, let's use DrawPotItem with ID 0xFD (Small Key) or 0xFE (Big Key)
1022 uint8_t key_item = (sprite.key_drop() == 1) ? 0xFD : 0xFE;
1023 drawer.DrawPotItem(key_item, sprite.x(), sprite.y(), object_bg1_buffer_);
1024 }
1025 }
1026
1027 // Log only failures, not successes
1028 if (!status.ok()) {
1029 LOG_DEBUG("[RenderObjectsToBackground]", "ObjectDrawer failed: %s - FALLING BACK TO MANUAL",
1030 std::string(status.message().data(), status.message().size()).c_str());
1031
1032 LOG_DEBUG("[RenderObjectsToBackground]",
1033 "Room %d: Manual rendering objects (fallback)", room_id_);
1034 auto& bg1_bmp = bg1_buffer_.bitmap();
1035 // Simple manual rendering: draw a colored rectangle for each object
1036 for (const auto& obj : tile_objects_) {
1037 int x = obj.x() * 8;
1038 int y = obj.y() * 8;
1039 int width = 16; // Default size for manual draw
1040 int height = 16;
1041
1042 // Basic layer-based coloring for manual mode
1043 uint8_t color_idx = 0; // Default transparent
1044 if (obj.GetLayerValue() == 0) {
1045 color_idx = 5; // Example: Reddish for Layer 0
1046 } else if (obj.GetLayerValue() == 1) {
1047 color_idx = 10; // Example: Greenish for Layer 1
1048 } else {
1049 color_idx = 15; // Example: Bluish for Layer 2
1050 }
1051 // Draw simple rectangle using WriteToBGRSurface
1052 for (int py = y; py < y + height && py < bg1_bmp.height(); ++py) {
1053 for (int px = x; px < x + width && px < bg1_bmp.width(); ++px) {
1054 int pixel_offset = (py * bg1_bmp.width()) + px;
1055 bg1_bmp.WriteToPixel(pixel_offset, color_idx);
1056 }
1057 }
1058 }
1059 objects_dirty_ = false; // Mark as clean after manual draw
1060 } else {
1061 // Mark objects as clean after successful render
1062 objects_dirty_ = false;
1063 LOG_DEBUG("[RenderObjectsToBackground]",
1064 "Room %d: Objects rendered successfully", room_id_);
1065 }
1066}
1067
1068// LoadGraphicsSheetsIntoArena() removed - using per-room graphics instead
1069// Room rendering no longer depends on Arena graphics sheets
1070
1072 if (!rom_ || !rom_->is_loaded()) {
1073 return;
1074 }
1075
1076 if (!game_data_) {
1077 return;
1078 }
1079 auto* gfx_buffer_data = &game_data_->graphics_buffer;
1080 if (gfx_buffer_data->empty()) {
1081 return;
1082 }
1083
1084 auto rom_data = rom()->vector();
1085 if (rom_data.empty()) {
1086 return;
1087 }
1088
1089 // Validate animated_frame_ bounds
1090 if (animated_frame_ < 0 || animated_frame_ > 10) {
1091 return;
1092 }
1093
1094 // Validate background_tileset_ bounds
1095 if (background_tileset_ < 0 || background_tileset_ > 255) {
1096 return;
1097 }
1098
1099 int gfx_ptr = SnesToPc(version_constants().kGfxAnimatedPointer);
1100 if (gfx_ptr < 0 || gfx_ptr >= static_cast<int>(rom_data.size())) {
1101 return;
1102 }
1103
1104 int data = 0;
1105 while (data < 1024) {
1106 // Validate buffer access for first operation
1107 // 92 * 4096 = 376832. 1024 * 10 = 10240. Total ~387KB.
1108 int first_offset = data + (92 * 4096) + (1024 * animated_frame_);
1109 if (first_offset >= 0 &&
1110 first_offset < static_cast<int>(gfx_buffer_data->size())) {
1111 uint8_t map_byte = (*gfx_buffer_data)[first_offset];
1112
1113 // Validate current_gfx16_ access
1114 int gfx_offset = data + (7 * 4096);
1115 if (gfx_offset >= 0 &&
1116 gfx_offset < static_cast<int>(current_gfx16_.size())) {
1117 current_gfx16_[gfx_offset] = map_byte;
1118 }
1119 }
1120
1121 // Validate buffer access for second operation
1122 int tileset_index = rom_data[gfx_ptr + background_tileset_];
1123 int second_offset = data + (tileset_index * 4096) + (1024 * animated_frame_);
1124 if (second_offset >= 0 &&
1125 second_offset < static_cast<int>(gfx_buffer_data->size())) {
1126 uint8_t map_byte = (*gfx_buffer_data)[second_offset];
1127
1128 // Validate current_gfx16_ access
1129 int gfx_offset = data + (7 * 4096) - 1024;
1130 if (gfx_offset >= 0 &&
1131 gfx_offset < static_cast<int>(current_gfx16_.size())) {
1132 current_gfx16_[gfx_offset] = map_byte;
1133 }
1134 }
1135
1136 data++;
1137 }
1138}
1139
1141 LOG_DEBUG("[LoadObjects]", "Starting LoadObjects for room %d", room_id_);
1142 auto rom_data = rom()->vector();
1143
1144 // Enhanced object loading with comprehensive validation
1145 int object_pointer = (rom_data[room_object_pointer + 2] << 16) +
1146 (rom_data[room_object_pointer + 1] << 8) +
1147 (rom_data[room_object_pointer]);
1148 object_pointer = SnesToPc(object_pointer);
1149
1150 // Enhanced bounds checking for object pointer
1151 if (object_pointer < 0 || object_pointer >= (int)rom_->size()) {
1152 return;
1153 }
1154
1155 int room_address = object_pointer + (room_id_ * 3);
1156
1157 // Enhanced bounds checking for room address
1158 if (room_address < 0 || room_address + 2 >= (int)rom_->size()) {
1159 return;
1160 }
1161
1162 int tile_address = (rom_data[room_address + 2] << 16) +
1163 (rom_data[room_address + 1] << 8) + rom_data[room_address];
1164
1165 int objects_location = SnesToPc(tile_address);
1166
1167 // Enhanced bounds checking for objects location
1168 if (objects_location < 0 || objects_location >= (int)rom_->size()) {
1169 return;
1170 }
1171
1172 // Parse floor graphics and layout with validation
1173 if (objects_location + 1 < (int)rom_->size()) {
1174 if (is_floor_) {
1176 static_cast<uint8_t>(rom_data[objects_location] & 0x0F);
1178 static_cast<uint8_t>((rom_data[objects_location] >> 4) & 0x0F);
1179 LOG_DEBUG("[LoadObjects]",
1180 "Room %d: Set floor1_graphics_=%d, floor2_graphics_=%d",
1182 }
1183
1184 layout = static_cast<uint8_t>((rom_data[objects_location + 1] >> 2) & 0x07);
1185 }
1186
1187 LoadChests();
1188
1189 // Parse objects with enhanced error handling
1190 ParseObjectsFromLocation(objects_location + 2);
1191}
1192
1193void Room::ParseObjectsFromLocation(int objects_location) {
1194 auto rom_data = rom()->vector();
1195
1196 // Clear existing objects before parsing to prevent accumulation on reload
1197 tile_objects_.clear();
1198 doors_.clear();
1199 z3_staircases_.clear();
1200 int nbr_of_staircase = 0;
1201
1202 int pos = objects_location;
1203 uint8_t b1 = 0;
1204 uint8_t b2 = 0;
1205 uint8_t b3 = 0;
1206 int layer = 0;
1207 bool door = false;
1208 bool end_read = false;
1209
1210 // Enhanced parsing loop with bounds checking
1211 // ASM: Main object loop logic (implicit in structure)
1212 while (!end_read && pos < (int)rom_->size()) {
1213 // Check if we have enough bytes to read
1214 if (pos + 1 >= (int)rom_->size()) {
1215 break;
1216 }
1217
1218 b1 = rom_data[pos];
1219 b2 = rom_data[pos + 1];
1220
1221 // ASM Marker: 0xFF 0xFF - End of Layer
1222 // Signals transition between object layers:
1223 // Layer 0 -> BG1 buffer (main floor/walls)
1224 // Layer 1 -> BG2 buffer (overlay layer)
1225 // Layer 2 -> BG1 buffer (priority objects, still on BG1)
1226 if (b1 == 0xFF && b2 == 0xFF) {
1227 pos += 2; // Jump to next layer
1228 layer++;
1229 door = false;
1230 if (layer == 3) {
1231 break;
1232 }
1233 continue;
1234 }
1235
1236 // ASM Marker: 0xF0 0xFF - Start of Door List
1237 // See RoomDraw_DoorObject ($018916) logic
1238 if (b1 == 0xF0 && b2 == 0xFF) {
1239 pos += 2; // Jump to door section
1240 door = true;
1241 continue;
1242 }
1243
1244 // Check if we have enough bytes for object data
1245 if (pos + 2 >= (int)rom_->size()) {
1246 break;
1247 }
1248
1249 b3 = rom_data[pos + 2];
1250 if (door) {
1251 pos += 2;
1252 } else {
1253 pos += 3;
1254 }
1255
1256 if (!door) {
1257 // ASM: RoomDraw_RoomObject ($01893C)
1258 // Handles Subtype 1, 2, 3 parsing based on byte values
1260 b1, b2, b3, static_cast<uint8_t>(layer));
1261
1262 // Validate object ID before adding to the room
1263 // Object IDs can be up to 12-bit (0xFFF) to support Type 3 objects
1264 if (r.id_ >= 0 && r.id_ <= 0xFFF) {
1265 r.SetRom(rom_);
1266 tile_objects_.push_back(r);
1267
1268 // Handle special object types (staircases, chests, etc.)
1269 HandleSpecialObjects(r.id_, r.x(), r.y(), nbr_of_staircase);
1270 }
1271 } else {
1272 // Handle door objects
1273 // ASM format (from RoomDraw_DoorObject):
1274 // b1: bits 4-7 = position index, bits 0-1 = direction
1275 // b2: door type (full byte)
1276 auto door = Door::FromRomBytes(b1, b2);
1277 printf("[ParseDoor] room=%d b1=0x%02X b2=0x%02X -> pos=%d dir=%d type=%d\n",
1278 room_id_, b1, b2, door.position, static_cast<int>(door.direction),
1279 static_cast<int>(door.type));
1280 doors_.push_back(door);
1281 }
1282 }
1283}
1284
1285// ============================================================================
1286// Object Saving Implementation (Phase 1, Task 1.3)
1287// ============================================================================
1288
1289std::vector<uint8_t> Room::EncodeObjects() const {
1290 std::vector<uint8_t> bytes;
1291
1292 // Organize objects by layer
1293 std::vector<RoomObject> layer0_objects;
1294 std::vector<RoomObject> layer1_objects;
1295 std::vector<RoomObject> layer2_objects;
1296
1297 for (const auto& obj : tile_objects_) {
1298 switch (obj.GetLayerValue()) {
1299 case 0:
1300 layer0_objects.push_back(obj);
1301 break;
1302 case 1:
1303 layer1_objects.push_back(obj);
1304 break;
1305 case 2:
1306 layer2_objects.push_back(obj);
1307 break;
1308 }
1309 }
1310
1311 // Encode Layer 1 (BG2)
1312 for (const auto& obj : layer0_objects) {
1313 auto encoded = obj.EncodeObjectToBytes();
1314 bytes.push_back(encoded.b1);
1315 bytes.push_back(encoded.b2);
1316 bytes.push_back(encoded.b3);
1317 }
1318 bytes.push_back(0xFF);
1319 bytes.push_back(0xFF);
1320
1321 // Encode Layer 2 (BG1)
1322 for (const auto& obj : layer1_objects) {
1323 auto encoded = obj.EncodeObjectToBytes();
1324 bytes.push_back(encoded.b1);
1325 bytes.push_back(encoded.b2);
1326 bytes.push_back(encoded.b3);
1327 }
1328 bytes.push_back(0xFF);
1329 bytes.push_back(0xFF);
1330
1331 // Encode Layer 3
1332 for (const auto& obj : layer2_objects) {
1333 auto encoded = obj.EncodeObjectToBytes();
1334 bytes.push_back(encoded.b1);
1335 bytes.push_back(encoded.b2);
1336 bytes.push_back(encoded.b3);
1337 }
1338
1339 // Final terminator
1340 bytes.push_back(0xFF);
1341 bytes.push_back(0xFF);
1342
1343 return bytes;
1344}
1345
1346std::vector<uint8_t> Room::EncodeSprites() const {
1347 std::vector<uint8_t> bytes;
1348
1349 for (const auto& sprite : sprites_) {
1350 uint8_t b1, b2, b3;
1351
1352 // b3 is simply the ID
1353 b3 = sprite.id();
1354
1355 // b2 = (X & 0x1F) | ((Flags & 0x07) << 5)
1356 // Flags 0-2 come from b2 5-7
1357 b2 = (sprite.x() & 0x1F) | ((sprite.subtype() & 0x07) << 5);
1358
1359 // b1 = (Y & 0x1F) | ((Flags & 0x18) << 2) | ((Layer & 1) << 7)
1360 // Flags 3-4 come from b1 5-6. (0x18 is 00011000)
1361 // Layer bit 0 comes from b1 7
1362 b1 = (sprite.y() & 0x1F) | ((sprite.subtype() & 0x18) << 2) |
1363 ((sprite.layer() & 0x01) << 7);
1364
1365 bytes.push_back(b1);
1366 bytes.push_back(b2);
1367 bytes.push_back(b3);
1368 }
1369
1370 // Terminator
1371 bytes.push_back(0xFF);
1372
1373 return bytes;
1374}
1375
1376absl::Status Room::SaveObjects() {
1377 if (rom_ == nullptr) {
1378 return absl::InvalidArgumentError("ROM pointer is null");
1379 }
1380
1381 auto rom_data = rom()->vector();
1382
1383 // Get object pointer
1384 int object_pointer = (rom_data[room_object_pointer + 2] << 16) +
1385 (rom_data[room_object_pointer + 1] << 8) +
1386 (rom_data[room_object_pointer]);
1387 object_pointer = SnesToPc(object_pointer);
1388
1389 if (object_pointer < 0 || object_pointer >= (int)rom_->size()) {
1390 return absl::OutOfRangeError("Object pointer out of range");
1391 }
1392
1393 int room_address = object_pointer + (room_id_ * 3);
1394
1395 if (room_address < 0 || room_address + 2 >= (int)rom_->size()) {
1396 return absl::OutOfRangeError("Room address out of range");
1397 }
1398
1399 int tile_address = (rom_data[room_address + 2] << 16) +
1400 (rom_data[room_address + 1] << 8) + rom_data[room_address];
1401
1402 int objects_location = SnesToPc(tile_address);
1403
1404 if (objects_location < 0 || objects_location >= (int)rom_->size()) {
1405 return absl::OutOfRangeError("Objects location out of range");
1406 }
1407
1408 // Calculate available space
1409 RoomSize room_size_info = CalculateRoomSize(rom_, room_id_);
1410 int available_size = room_size_info.room_size;
1411
1412 // Skip graphics/layout header (2 bytes)
1413 int write_pos = objects_location + 2;
1414
1415 // Encode all objects
1416 auto encoded_bytes = EncodeObjects();
1417
1418 // VALIDATION: Check if new data fits in available space
1419 // We subtract 2 bytes for the header which is not part of encoded_bytes
1420 if (encoded_bytes.size() > available_size - 2) {
1421 return absl::OutOfRangeError(absl::StrFormat(
1422 "Room %d object data too large! Size: %d, Available: %d", room_id_,
1423 encoded_bytes.size(), available_size - 2));
1424 }
1425
1426 // Write encoded bytes to ROM using WriteVector
1427 return rom_->WriteVector(write_pos, encoded_bytes);
1428}
1429
1430absl::Status Room::SaveSprites() {
1431 if (rom_ == nullptr) {
1432 return absl::InvalidArgumentError("ROM pointer is null");
1433 }
1434
1435 auto rom_data = rom()->vector();
1436
1437 // Calculate sprite pointer table location
1438 // Bank 09 + rooms_sprite_pointer (was incorrectly 0x04)
1439 int sprite_pointer = (0x09 << 16) +
1440 (rom_data[rooms_sprite_pointer + 1] << 8) +
1441 (rom_data[rooms_sprite_pointer]);
1442 sprite_pointer = SnesToPc(sprite_pointer);
1443
1444 if (sprite_pointer < 0 || sprite_pointer + (room_id_ * 2) + 1 >= (int)rom_->size()) {
1445 return absl::OutOfRangeError("Sprite table pointer out of range");
1446 }
1447
1448 // Read room sprite address from table
1449 int sprite_address_snes =
1450 (0x09 << 16) + (rom_data[sprite_pointer + (room_id_ * 2) + 1] << 8) +
1451 rom_data[sprite_pointer + (room_id_ * 2)];
1452
1453 int sprite_address = SnesToPc(sprite_address_snes);
1454
1455 if (sprite_address < 0 || sprite_address >= (int)rom_->size()) {
1456 return absl::OutOfRangeError("Sprite address out of range");
1457 }
1458
1459 // Calculate available space for sprites
1460 // Check next room's sprite pointer
1461 int next_sprite_address_snes =
1462 (0x09 << 16) + (rom_data[sprite_pointer + ((room_id_ + 1) * 2) + 1] << 8) +
1463 rom_data[sprite_pointer + ((room_id_ + 1) * 2)];
1464
1465 int next_sprite_address = SnesToPc(next_sprite_address_snes);
1466
1467 // Handle wrap-around or end of bank if needed, but usually sequential
1468 int available_size = next_sprite_address - sprite_address;
1469
1470 // If calculation seems wrong (negative or too large), fallback to a safe limit or error
1471 if (available_size <= 0 || available_size > 0x1000) {
1472 // Fallback: Assume standard max or just warn.
1473 // For now, let's be strict but allow a reasonable max if calculation fails (e.g. last room)
1474 if (room_id_ == NumberOfRooms - 1) {
1475 available_size = 0x100; // Arbitrary safe limit for last room
1476 } else {
1477 // If negative, it means pointers are not sequential.
1478 // This happens in some ROMs. We can't easily validate size then without a free space map.
1479 // We'll log a warning and proceed with caution? No, prompt says "Free space validation".
1480 // Let's error out if we can't determine size.
1481 return absl::InternalError(absl::StrFormat("Cannot determine available sprite space for room %d", room_id_));
1482 }
1483 }
1484
1485 // Handle sortsprites byte (skip if present)
1486 bool has_sort_sprite = false;
1487 if (rom_data[sprite_address] == 1) {
1488 has_sort_sprite = true;
1489 sprite_address += 1;
1490 available_size -= 1;
1491 }
1492
1493 auto encoded_bytes = EncodeSprites();
1494
1495 // VALIDATION
1496 if (encoded_bytes.size() > available_size) {
1497 return absl::OutOfRangeError(absl::StrFormat(
1498 "Room %d sprite data too large! Size: %d, Available: %d", room_id_,
1499 encoded_bytes.size(), available_size));
1500 }
1501
1502 return rom_->WriteVector(sprite_address, encoded_bytes);
1503}
1504
1505// ============================================================================
1506// Object Manipulation Methods (Phase 3)
1507// ============================================================================
1508
1509absl::Status Room::AddObject(const RoomObject& object) {
1510 // Validate object
1511 if (!ValidateObject(object)) {
1512 return absl::InvalidArgumentError("Invalid object parameters");
1513 }
1514
1515 // Add to internal list
1516 tile_objects_.push_back(object);
1518
1519 return absl::OkStatus();
1520}
1521
1522absl::Status Room::RemoveObject(size_t index) {
1523 if (index >= tile_objects_.size()) {
1524 return absl::OutOfRangeError("Object index out of range");
1525 }
1526
1527 tile_objects_.erase(tile_objects_.begin() + index);
1529
1530 return absl::OkStatus();
1531}
1532
1533absl::Status Room::UpdateObject(size_t index, const RoomObject& object) {
1534 if (index >= tile_objects_.size()) {
1535 return absl::OutOfRangeError("Object index out of range");
1536 }
1537
1538 if (!ValidateObject(object)) {
1539 return absl::InvalidArgumentError("Invalid object parameters");
1540 }
1541
1542 tile_objects_[index] = object;
1544
1545 return absl::OkStatus();
1546}
1547
1548absl::StatusOr<size_t> Room::FindObjectAt(int x, int y, int layer) const {
1549 for (size_t i = 0; i < tile_objects_.size(); i++) {
1550 const auto& obj = tile_objects_[i];
1551 if (obj.x() == x && obj.y() == y && obj.GetLayerValue() == layer) {
1552 return i;
1553 }
1554 }
1555 return absl::NotFoundError("No object found at position");
1556}
1557
1558bool Room::ValidateObject(const RoomObject& object) const {
1559 // Validate position (0-63 for both X and Y)
1560 if (object.x() < 0 || object.x() > 63)
1561 return false;
1562 if (object.y() < 0 || object.y() > 63)
1563 return false;
1564
1565 // Validate layer (0-2)
1566 if (object.GetLayerValue() < 0 || object.GetLayerValue() > 2)
1567 return false;
1568
1569 // Validate object ID range
1570 if (object.id_ < 0 || object.id_ > 0xFFF)
1571 return false;
1572
1573 // Validate size for Type 1 objects
1574 if (object.id_ < 0x100 && object.size() > 15)
1575 return false;
1576
1577 return true;
1578}
1579
1580void Room::HandleSpecialObjects(short oid, uint8_t posX, uint8_t posY,
1581 int& nbr_of_staircase) {
1582 // Handle staircase objects
1583 for (short stair : stairsObjects) {
1584 if (stair == oid) {
1585 if (nbr_of_staircase < 4) {
1586 tile_objects_.back().set_options(ObjectOption::Stairs |
1587 tile_objects_.back().options());
1588 z3_staircases_.push_back(
1589 {posX, posY,
1590 absl::StrCat("To ", staircase_rooms_[nbr_of_staircase]).data()});
1591 nbr_of_staircase++;
1592 } else {
1593 tile_objects_.back().set_options(ObjectOption::Stairs |
1594 tile_objects_.back().options());
1595 z3_staircases_.push_back({posX, posY, "To ???"});
1596 }
1597 break;
1598 }
1599 }
1600
1601 // Handle chest objects
1602 if (oid == 0xF99) {
1603 if (chests_in_room_.size() > 0) {
1604 tile_objects_.back().set_options(ObjectOption::Chest |
1605 tile_objects_.back().options());
1606 chests_in_room_.erase(chests_in_room_.begin());
1607 }
1608 } else if (oid == 0xFB1) {
1609 if (chests_in_room_.size() > 0) {
1610 tile_objects_.back().set_options(ObjectOption::Chest |
1611 tile_objects_.back().options());
1612 chests_in_room_.erase(chests_in_room_.begin());
1613 }
1614 }
1615}
1616
1618 auto rom_data = rom()->vector();
1619 int sprite_pointer = (0x04 << 16) +
1620 (rom_data[rooms_sprite_pointer + 1] << 8) +
1621 (rom_data[rooms_sprite_pointer]);
1622 int sprite_address_snes =
1623 (0x09 << 16) + (rom_data[sprite_pointer + (room_id_ * 2) + 1] << 8) +
1624 rom_data[sprite_pointer + (room_id_ * 2)];
1625
1626 int sprite_address = SnesToPc(sprite_address_snes);
1627 if (rom_data[sprite_address] == 1) {
1628 // sortsprites is unused
1629 }
1630 sprite_address += 1;
1631
1632 while (true) {
1633 uint8_t b1 = rom_data[sprite_address];
1634 uint8_t b2 = rom_data[sprite_address + 1];
1635 uint8_t b3 = rom_data[sprite_address + 2];
1636
1637 if (b1 == 0xFF) {
1638 break;
1639 }
1640
1641 sprites_.emplace_back(b3, (b2 & 0x1F), (b1 & 0x1F),
1642 ((b2 & 0xE0) >> 5) + ((b1 & 0x60) >> 2),
1643 (b1 & 0x80) >> 7);
1644
1645 if (sprites_.size() > 1) {
1646 Sprite& spr = sprites_.back();
1647 Sprite& prevSprite = sprites_[sprites_.size() - 2];
1648
1649 if (spr.id() == 0xE4 && spr.x() == 0x00 && spr.y() == 0x1E &&
1650 spr.layer() == 1 && spr.subtype() == 0x18) {
1651 prevSprite.set_key_drop(1);
1652 sprites_.pop_back();
1653 }
1654
1655 if (spr.id() == 0xE4 && spr.x() == 0x00 && spr.y() == 0x1D &&
1656 spr.layer() == 1 && spr.subtype() == 0x18) {
1657 prevSprite.set_key_drop(2);
1658 sprites_.pop_back();
1659 }
1660 }
1661
1662 sprite_address += 3;
1663 }
1664}
1665
1667 auto rom_data = rom()->vector();
1668 uint32_t cpos = SnesToPc((rom_data[chests_data_pointer1 + 2] << 16) +
1669 (rom_data[chests_data_pointer1 + 1] << 8) +
1670 (rom_data[chests_data_pointer1]));
1671 size_t clength = (rom_data[chests_length_pointer + 1] << 8) +
1672 (rom_data[chests_length_pointer]);
1673
1674 for (size_t i = 0; i < clength; i++) {
1675 if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
1676 0x7FFF) == room_id_) {
1677 // There's a chest in that room !
1678 bool big = false;
1679 if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
1680 0x8000) == 0x8000) {
1681 big = true;
1682 }
1683
1684 chests_in_room_.emplace_back(
1685 chest_data{rom_data[cpos + (i * 3) + 2], big});
1686 }
1687 }
1688}
1689
1691 auto rom_data = rom()->vector();
1692
1693 // Doors are loaded as part of the object stream in LoadObjects()
1694 // When the parser encounters 0xF0 0xFF, it enters door mode
1695 // Door objects have format: b1 (position/direction), b2 (type)
1696 // Door encoding: b1 = (door_pos << 4) | (door_dir & 0x03)
1697 // position in bits 4-7, direction in bits 0-1
1698 // b2 = door_type (full byte, values 0x00, 0x02, 0x04, etc.)
1699 // This is already handled in ParseObjectsFromLocation()
1700
1701 LOG_DEBUG("Room",
1702 "LoadDoors for room %d - doors are loaded via object stream",
1703 room_id_);
1704}
1705
1707 auto rom_data = rom()->vector();
1708
1709 // Read torch data length
1710 int bytes_count = (rom_data[torches_length_pointer + 1] << 8) |
1711 rom_data[torches_length_pointer];
1712
1713 LOG_DEBUG("Room", "LoadTorches: room_id=%d, bytes_count=%d", room_id_,
1714 bytes_count);
1715
1716 // Iterate through torch data to find torches for this room
1717 for (int i = 0; i < bytes_count; i += 2) {
1718 if (i + 1 >= bytes_count)
1719 break;
1720
1721 uint8_t b1 = rom_data[torch_data + i];
1722 uint8_t b2 = rom_data[torch_data + i + 1];
1723
1724 // Skip 0xFFFF markers
1725 if (b1 == 0xFF && b2 == 0xFF) {
1726 continue;
1727 }
1728
1729 // Check if this entry is for our room
1730 uint16_t torch_room_id = (b2 << 8) | b1;
1731 if (torch_room_id == room_id_) {
1732 // Found torches for this room, read them
1733 i += 2;
1734 while (i < bytes_count) {
1735 if (i + 1 >= bytes_count)
1736 break;
1737
1738 b1 = rom_data[torch_data + i];
1739 b2 = rom_data[torch_data + i + 1];
1740
1741 // End of torch list for this room
1742 if (b1 == 0xFF && b2 == 0xFF) {
1743 break;
1744 }
1745
1746 // Decode torch position and properties
1747 int address = ((b2 & 0x1F) << 8 | b1) >> 1;
1748 uint8_t px = address % 64;
1749 uint8_t py = address >> 6;
1750 uint8_t layer = (b2 & 0x20) >> 5;
1751 bool lit = (b2 & 0x80) == 0x80;
1752
1753 // Create torch object (ID 0x150)
1754 RoomObject torch_obj(0x150, px, py, 0, layer);
1755 torch_obj.SetRom(rom_);
1757 // Store lit state if needed (may require adding a field to RoomObject)
1758
1759 tile_objects_.push_back(torch_obj);
1760
1761 LOG_DEBUG("Room", "Loaded torch at (%d,%d) layer=%d lit=%d", px, py,
1762 layer, lit);
1763
1764 i += 2;
1765 }
1766 break; // Found and processed our room's torches
1767 } else {
1768 // Skip to next room's torches
1769 i += 2;
1770 while (i < bytes_count) {
1771 if (i + 1 >= bytes_count)
1772 break;
1773 b1 = rom_data[torch_data + i];
1774 b2 = rom_data[torch_data + i + 1];
1775 if (b1 == 0xFF && b2 == 0xFF) {
1776 break;
1777 }
1778 i += 2;
1779 }
1780 }
1781 }
1782}
1783
1785 auto rom_data = rom()->vector();
1786
1787 // Read blocks length
1788 int blocks_count =
1789 (rom_data[blocks_length + 1] << 8) | rom_data[blocks_length];
1790
1791 LOG_DEBUG("Room", "LoadBlocks: room_id=%d, blocks_count=%d", room_id_,
1792 blocks_count);
1793
1794 // Load block data from multiple pointers
1795 std::vector<uint8_t> blocks_data(blocks_count);
1796
1797 int pos1 = blocks_pointer1;
1798 int pos2 = blocks_pointer2;
1799 int pos3 = blocks_pointer3;
1800 int pos4 = blocks_pointer4;
1801
1802 // Read block data from 4 different locations
1803 for (int i = 0; i < 0x80 && i < blocks_count; i++) {
1804 blocks_data[i] = rom_data[pos1 + i];
1805
1806 if (i + 0x80 < blocks_count) {
1807 blocks_data[i + 0x80] = rom_data[pos2 + i];
1808 }
1809 if (i + 0x100 < blocks_count) {
1810 blocks_data[i + 0x100] = rom_data[pos3 + i];
1811 }
1812 if (i + 0x180 < blocks_count) {
1813 blocks_data[i + 0x180] = rom_data[pos4 + i];
1814 }
1815 }
1816
1817 // Parse blocks for this room (4 bytes per block entry)
1818 for (int i = 0; i < blocks_count; i += 4) {
1819 if (i + 3 >= blocks_count)
1820 break;
1821
1822 uint8_t b1 = blocks_data[i];
1823 uint8_t b2 = blocks_data[i + 1];
1824 uint8_t b3 = blocks_data[i + 2];
1825 uint8_t b4 = blocks_data[i + 3];
1826
1827 // Check if this block belongs to our room
1828 uint16_t block_room_id = (b2 << 8) | b1;
1829 if (block_room_id == room_id_) {
1830 // End marker for this room's blocks
1831 if (b3 == 0xFF && b4 == 0xFF) {
1832 break;
1833 }
1834
1835 // Decode block position
1836 int address = ((b4 & 0x1F) << 8 | b3) >> 1;
1837 uint8_t px = address % 64;
1838 uint8_t py = address >> 6;
1839 uint8_t layer = (b4 & 0x20) >> 5;
1840
1841 // Create block object (ID 0x0E00)
1842 RoomObject block_obj(0x0E00, px, py, 0, layer);
1843 block_obj.SetRom(rom_);
1845
1846 tile_objects_.push_back(block_obj);
1847
1848 LOG_DEBUG("Room", "Loaded block at (%d,%d) layer=%d", px, py, layer);
1849 }
1850 }
1851}
1852
1854 if (!rom_ || !rom_->is_loaded()) return;
1855 auto rom_data = rom()->vector();
1856
1857 // Load pot items
1858 // Format per ASM analysis (bank_01.asm):
1859 // - Pointer table at kRoomItemsPointers (0x01DB69)
1860 // - Each room has a pointer to item data
1861 // - Item data format: 3 bytes per item
1862 // - 2 bytes: position word (Y_hi, X_lo encoding)
1863 // - 1 byte: item type
1864 // - Terminated by 0xFFFF position word
1865
1866 int table_addr = kRoomItemsPointers; // 0x01DB69
1867
1868 // Read pointer for this room
1869 int ptr_addr = table_addr + (room_id_ * 2);
1870 if (ptr_addr + 1 >= static_cast<int>(rom_data.size())) return;
1871
1872 uint16_t item_ptr = (rom_data[ptr_addr + 1] << 8) | rom_data[ptr_addr];
1873
1874 // Convert to PC address (Bank 01 offset)
1875 int item_addr = SnesToPc(0x010000 | item_ptr);
1876
1877 pot_items_.clear();
1878
1879 // Read 3-byte entries until 0xFFFF terminator
1880 while (item_addr + 2 < static_cast<int>(rom_data.size())) {
1881 // Read position word (little endian)
1882 uint16_t position = (rom_data[item_addr + 1] << 8) | rom_data[item_addr];
1883
1884 // Check for terminator
1885 if (position == 0xFFFF) break;
1886
1887 // Read item type (3rd byte)
1888 uint8_t item_type = rom_data[item_addr + 2];
1889
1890 PotItem pot_item;
1891 pot_item.position = position;
1892 pot_item.item = item_type;
1893 pot_items_.push_back(pot_item);
1894
1895 item_addr += 3; // Move to next entry
1896 }
1897}
1898
1900 auto rom_data = rom()->vector();
1901
1902 // Read pit count
1903 int pit_entries = rom_data[pit_count] / 2;
1904
1905 // Read pit pointer (long pointer)
1906 int pit_ptr = (rom_data[pit_pointer + 2] << 16) |
1907 (rom_data[pit_pointer + 1] << 8) | rom_data[pit_pointer];
1908 int pit_data_addr = SnesToPc(pit_ptr);
1909
1910 LOG_DEBUG("Room", "LoadPits: room_id=%d, pit_entries=%d, pit_ptr=0x%06X",
1911 room_id_, pit_entries, pit_ptr);
1912
1913 // Pit data is stored as: room_id (2 bytes), target info (2 bytes)
1914 // This data is already loaded in LoadRoomFromRom() into pits_ destination
1915 // struct The pit destination (where you go when you fall) is set via
1916 // SetPitsTarget()
1917
1918 // Pits are typically represented in the layout/collision data, not as objects
1919 // The pits_ member already contains the target room and layer
1920 LOG_DEBUG("Room", "Pit destination - target=%d, target_layer=%d",
1922}
1923
1924} // namespace zelda3
1925} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
Definition rom.cc:340
auto data() const
Definition rom.h:135
auto size() const
Definition rom.h:134
bool is_loaded() const
Definition rom.h:128
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:34
static Arena & Get()
Definition arena.cc:19
void DrawBackground(std::span< uint8_t > gfx16_data)
void DrawFloor(const std::vector< uint8_t > &rom_data, int tile_address, int tile_address_floor, uint8_t floor_graphics)
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
TextureHandle texture() const
Definition bitmap.h:380
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:382
SDL_Surface * surface() const
Definition bitmap.h:379
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
Editor implementation of DungeonState.
Draws dungeon objects to background buffers using game patterns.
absl::Status DrawObjectList(const std::vector< RoomObject > &objects, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr)
Draw all objects in a room.
void DrawDoor(const DoorDef &door, int door_index, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const DungeonState *state=nullptr)
Draw a door to background buffers.
void DrawPotItem(uint8_t item_id, int x, int y, gfx::BackgroundBuffer &bg)
Draw a pot item visualization.
void SetCurrentBitmap(gfx::Bitmap *bitmap)
void LogPaletteLoad(const std::string &location, int palette_id, const gfx::SnesPalette &palette)
static PaletteDebugger & Get()
void LogPaletteApplication(const std::string &location, int palette_id, bool success, const std::string &reason="")
void SetCurrentPalette(const gfx::SnesPalette &palette)
void LogSurfaceState(const std::string &location, SDL_Surface *surface)
RoomLayerManager - Manages layer visibility and compositing.
void CompositeToOutput(Room &room, gfx::Bitmap &output) const
Composite all visible layers into a single output bitmap.
void SetRom(Rom *rom)
Definition room_layout.h:21
const std::vector< RoomObject > & GetObjects() const
Definition room_layout.h:31
absl::Status Draw(int room_id, const uint8_t *gfx_data, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, DungeonState *state) const
absl::Status LoadLayout(int layout_id)
static RoomObject DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t layer)
void SetRom(Rom *rom)
Definition room_object.h:68
void set_options(ObjectOption options)
void SetBlockset(uint8_t blockset)
Definition room.h:409
bool composite_dirty_
Definition room.h:576
bool ValidateObject(const RoomObject &object) const
Definition room.cc:1558
destination pits_
Definition room.h:634
std::vector< RoomObject > tile_objects_
Definition room.h:618
uint8_t cached_blockset_
Definition room.h:585
void SetSpriteset(uint8_t spriteset)
Definition room.h:415
EffectKey effect_
Definition room.h:629
gfx::BackgroundBuffer object_bg1_buffer_
Definition room.h:571
void SetTag2Direct(TagKey tag2)
Definition room.h:465
uint8_t cached_layout_
Definition room.h:588
absl::Status UpdateObject(size_t index, const RoomObject &object)
Definition room.cc:1533
TagKey cached_tag2_
Definition room.h:593
void SetStair4Target(uint8_t target)
Definition room.h:475
void SetPitsTarget(uint8_t target)
Definition room.h:471
void SetIsLight(bool is_light)
Definition room.h:402
void LoadChests()
Definition room.cc:1666
void MarkObjectsDirty()
Definition room.h:366
gfx::BackgroundBuffer bg2_buffer_
Definition room.h:570
uint8_t cached_floor2_graphics_
Definition room.h:590
std::vector< zelda3::Sprite > sprites_
Definition room.h:620
GameData * game_data_
Definition room.h:563
void SetLoaded(bool loaded)
Definition room.h:479
void CopyRoomGraphicsToBuffer()
Definition room.cc:421
uint8_t cached_palette_
Definition room.h:587
zelda3_version_pointers version_constants() const
Definition room.h:534
void LoadRoomGraphics(uint8_t entrance_blockset=0xFF)
Definition room.cc:370
uint8_t cached_effect_
Definition room.h:591
std::vector< Door > doors_
Definition room.h:623
bool graphics_dirty_
Definition room.h:596
void SetStaircaseRoom(int index, uint8_t room)
Definition room.h:444
absl::Status RemoveObject(size_t index)
Definition room.cc:1522
void SetStair1TargetLayer(uint8_t layer)
Definition room.h:467
void LoadBlocks()
Definition room.cc:1784
bool layout_dirty_
Definition room.h:598
void SetLayer2Mode(uint8_t mode)
Definition room.h:457
RoomLayout layout_
Definition room.h:625
void LoadLayoutTilesToBuffer()
Definition room.cc:810
void SetTag2(TagKey tag2)
Definition room.h:433
void ParseObjectsFromLocation(int objects_location)
Definition room.cc:1193
uint8_t cached_floor1_graphics_
Definition room.h:589
void SetTag1Direct(TagKey tag1)
Definition room.h:464
void LoadTorches()
Definition room.cc:1706
void SetPaletteDirect(uint8_t palette)
Definition room.h:460
void SetStair2Target(uint8_t target)
Definition room.h:473
int animated_frame_
Definition room.h:602
void SetCollision(CollisionKey collision)
Definition room.h:401
uint8_t blockset
Definition room.h:490
gfx::BackgroundBuffer bg1_buffer_
Definition room.h:569
absl::Status SaveObjects()
Definition room.cc:1376
void SetStaircasePlane(int index, uint8_t plane)
Definition room.h:439
void SetIsDark(bool is_dark)
Definition room.h:459
bool objects_dirty_
Definition room.h:597
uint8_t layout
Definition room.h:493
void RenderRoomGraphics()
Definition room.cc:518
bool textures_dirty_
Definition room.h:599
gfx::Bitmap composite_bitmap_
Definition room.h:575
Room & operator=(Room &&)
uint8_t staircase_rooms_[4]
Definition room.h:605
gfx::Bitmap & GetCompositeBitmap(RoomLayerManager &layer_mgr)
Get a composite bitmap of all layers merged.
Definition room.cc:510
std::vector< uint8_t > EncodeObjects() const
Definition room.cc:1289
void SetEffect(EffectKey effect)
Definition room.h:421
gfx::BackgroundBuffer object_bg2_buffer_
Definition room.h:572
std::array< uint8_t, 16 > blocks_
Definition room.h:615
void SetTag1(TagKey tag1)
Definition room.h:427
void SetBackgroundTileset(uint8_t tileset)
Definition room.h:461
void SetStair3TargetLayer(uint8_t layer)
Definition room.h:469
uint8_t floor2_graphics_
Definition room.h:612
bool IsLight() const
Definition room.h:453
uint8_t floor1_graphics_
Definition room.h:611
void SetMessageIdDirect(uint16_t message_id)
Definition room.h:456
absl::Status SaveSprites()
Definition room.cc:1430
void SetLayerMerging(LayerMergeType merging)
Definition room.h:458
void SetPitsTargetLayer(uint8_t layer)
Definition room.h:466
void LoadObjects()
Definition room.cc:1140
uint8_t spriteset
Definition room.h:491
void LoadPotItems()
Definition room.cc:1853
void SetSpriteTileset(uint8_t tileset)
Definition room.h:462
void SetStair1Target(uint8_t target)
Definition room.h:472
void SetBg2(background2 bg2)
Definition room.h:400
uint8_t palette
Definition room.h:492
std::unique_ptr< DungeonState > dungeon_state_
Definition room.h:640
void LoadAnimatedGraphics()
Definition room.cc:1071
std::vector< uint8_t > EncodeSprites() const
Definition room.cc:1346
std::vector< chest_data > chests_in_room_
Definition room.h:622
LayerMergeType layer_merging_
Definition room.h:627
uint8_t cached_spriteset_
Definition room.h:586
std::array< uint8_t, 0x10000 > current_gfx16_
Definition room.h:565
std::vector< staircase > z3_staircases_
Definition room.h:621
void SetPalette(uint8_t palette)
Definition room.h:403
std::vector< PotItem > pot_items_
Definition room.h:624
void LoadSprites()
Definition room.cc:1617
TagKey cached_tag1_
Definition room.h:592
void SetHolewarp(uint8_t holewarp)
Definition room.h:443
uint8_t background_tileset_
Definition room.h:607
void SetStair3Target(uint8_t target)
Definition room.h:474
void HandleSpecialObjects(short oid, uint8_t posX, uint8_t posY, int &nbr_of_staircase)
Definition room.cc:1580
void SetStair4TargetLayer(uint8_t layer)
Definition room.h:470
absl::Status AddObject(const RoomObject &object)
Definition room.cc:1509
absl::StatusOr< size_t > FindObjectAt(int x, int y, int layer) const
Definition room.cc:1548
void SetStair2TargetLayer(uint8_t layer)
Definition room.h:468
void RenderObjectsToBackground()
Definition room.cc:893
void SetLayer2Behavior(uint8_t behavior)
Definition room.h:463
A class for managing sprites in the overworld and underworld.
Definition sprite.h:35
auto id() const
Definition sprite.h:95
auto layer() const
Definition sprite.h:106
auto set_key_drop(int key)
Definition sprite.h:114
auto subtype() const
Definition sprite.h:107
auto y() const
Definition sprite.h:98
auto x() const
Definition sprite.h:97
zelda3_bg2_effect
Background layer 2 effects.
Definition zelda.h:369
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
SDL_Palette * GetSurfacePalette(SDL_Surface *surface)
Get the palette attached to a surface.
Definition sdl_compat.h:375
constexpr int kGfxBufferAnimatedFrameStride
Definition room.cc:415
const std::string RoomTag[65]
Definition room.cc:40
constexpr int kGfxBufferAnimatedFrameOffset
Definition room.cc:414
constexpr int chests_length_pointer
Definition room.h:41
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
Definition room.cc:206
constexpr int chests_data_pointer1
Definition room.h:42
constexpr int pit_count
Definition room.h:56
constexpr int blocks_pointer3
Definition room.h:47
constexpr int blocks_pointer4
Definition room.h:48
constexpr int NumberOfRooms
Definition room.h:70
constexpr int kGfxBufferRoomOffset
Definition room.cc:416
constexpr int rooms_sprite_pointer
Definition room.h:39
constexpr int kGfxBufferRoomSpriteOffset
Definition room.cc:417
RoomSize CalculateRoomSize(Rom *rom, int room_id)
Definition room.cc:107
constexpr int messages_id_dungeon
Definition room.h:43
constexpr int blocks_length
Definition room.h:44
constexpr int tile_address_floor
Definition room.h:69
constexpr int blocks_pointer2
Definition room.h:46
constexpr int kRoomItemsPointers
constexpr int kGfxBufferRoomSpriteStride
Definition room.cc:418
constexpr uint16_t stairsObjects[]
Definition room.h:71
Room LoadRoomFromRom(Rom *rom, int room_id)
Definition room.cc:177
constexpr uint32_t kDungeonPalettePointerTable
Definition game_data.h:44
constexpr int blocks_pointer1
Definition room.h:45
constexpr int torches_length_pointer
Definition room.h:50
const std::string RoomEffect[8]
Definition room.cc:28
constexpr int kRoomHeaderPointer
constexpr int torch_data
Definition room.h:49
constexpr int kRoomHeaderPointerBank
constexpr int kGfxBufferStride
Definition room.cc:413
constexpr int room_object_pointer
Definition room.h:34
constexpr int kGfxBufferRoomSpriteLastLineOffset
Definition room.cc:419
constexpr int tile_address
Definition room.h:68
constexpr int pit_pointer
Definition room.h:55
constexpr int dungeon_spr_ptrs
Definition room.h:67
constexpr int kGfxBufferOffset
Definition room.cc:412
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
SDL2/SDL3 compatibility layer.
Legacy chest data structure.
Definition zelda.h:438
uint8_t target_layer
Definition zelda.h:451
uint8_t target
Definition zelda.h:450
Represents a group of palettes.
void AddPalette(SnesPalette pal)
std::array< std::array< uint8_t, 4 >, kNumSpritesets > spriteset_ids
Definition game_data.h:93
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
Definition game_data.h:92
std::array< std::array< uint8_t, 4 >, kNumPalettesets > paletteset_ids
Definition game_data.h:99
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
Definition game_data.h:91
std::vector< uint8_t > graphics_buffer
Definition game_data.h:82
uint16_t position
Definition room.h:136
int64_t room_size_pointer
Definition room.h:650
static Door FromRomBytes(uint8_t b1, uint8_t b2)
Definition room.h:310
Yet Another Zelda3 Editor (YAZE) - Public C API.