7#include <unordered_set>
10#include "absl/strings/str_cat.h"
11#include "absl/strings/str_format.h"
37 "Light Torch to See Floor",
41const std::string
RoomTag[65] = {
"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",
62 "Collect Prize to Open",
63 "Hold Switch Open Door",
64 "Toggle Switch to Open Door",
73 "Push Switch Exploding Wall",
75 "Open Chest (Holes 0)",
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",
100 "Open Chest for Holes 8",
101 "Push Block for Chest",
102 "Clear Room for Triforce Door",
103 "Light Torches for Chest",
110 if (table_pc ==
nullptr) {
111 return absl::InvalidArgumentError(
"table_pc pointer is null");
114 return absl::OutOfRangeError(
"Sprite pointer table address is out of range");
120 if (pc < 0 || pc + (
kNumberOfRooms * 2) >
static_cast<int>(rom_data.size())) {
121 return absl::OutOfRangeError(
"Sprite pointer table is out of range");
125 return absl::OkStatus();
133 const int ptr_off = table_pc + (room_id * 2);
134 if (ptr_off < 0 || ptr_off + 1 >=
static_cast<int>(rom_data.size())) {
138 int sprite_address_snes = (0x09 << 16) | (rom_data[ptr_off + 1] << 8) |
140 return SnesToPc(sprite_address_snes);
144 int sprite_address,
int hard_end) {
145 if (sprite_address < 0 || sprite_address >= hard_end ||
146 sprite_address >=
static_cast<int>(rom_data.size())) {
150 int cursor = sprite_address + 1;
151 while (cursor < hard_end) {
152 if (rom_data[cursor] == 0xFF) {
156 if (cursor + 2 >= hard_end) {
163 return std::max(0, cursor - sprite_address);
167 int room_id,
int sprite_address) {
202 auto room_size_address = 0xF8000 + (room_id * 3);
205 if (room_size_address < 0 ||
206 room_size_address + 2 >=
static_cast<int>(rom->
size())) {
211 uint8_t low = rom->
data()[room_size_address];
212 uint8_t high = rom->
data()[room_size_address + 1];
213 uint8_t bank = rom->
data()[room_size_address + 2];
216 int long_address = (bank << 16) | (high << 8) | low;
219 if (long_address == 0x0A8000) {
227 int next_room_address = 0xF8000 + ((room_id + 1) * 3);
230 if (next_room_address < 0 ||
231 next_room_address + 2 >=
static_cast<int>(rom->
size())) {
236 uint8_t next_low = rom->
data()[next_room_address];
237 uint8_t next_high = rom->
data()[next_room_address + 1];
238 uint8_t next_bank = rom->
data()[next_room_address + 2];
241 int next_long_address = (next_bank << 16) | (next_high << 8) | next_low;
244 int actual_room_size = next_long_address - long_address;
275 Room room(room_id, rom);
291 header_pointer =
SnesToPc(header_pointer);
300 int table_offset = (header_pointer) + (room_id * 2);
301 if (table_offset < 0 || table_offset + 1 >=
static_cast<int>(rom->
size())) {
306 (rom->
data()[table_offset + 1] << 8) +
307 rom->
data()[table_offset];
309 auto header_location =
SnesToPc(address);
312 if (header_location < 0 ||
313 header_location + 13 >=
static_cast<int>(rom->
size())) {
322 room.
SetBg2(background2::DarkRoom);
358 header_pointer_2 =
SnesToPc(header_pointer_2);
367 int table_offset_2 = (header_pointer_2) + (room_id * 2);
368 if (table_offset_2 < 0 ||
369 table_offset_2 + 1 >=
static_cast<int>(rom->
size())) {
374 (rom->
data()[table_offset_2 + 1] << 8) +
375 rom->
data()[table_offset_2];
378 if (msg_addr >= 0 && msg_addr + 1 <
static_cast<int>(rom->
size())) {
379 uint16_t msg_val = (rom->
data()[msg_addr + 1] << 8) | rom->
data()[msg_addr];
387 if (hpos < 0 || hpos + 14 >=
static_cast<int>(rom->
size())) {
391 uint8_t b = rom->
data()[hpos];
418 b = rom->
data()[hpos];
445 game_data_(game_data),
465 for (
int i = 0; i < 8; i++) {
470 if (entrance_blockset != 0xFF && room_gfx[entrance_blockset][3] != 0) {
471 blocks_[i] = room_gfx[entrance_blockset][3];
480 for (
int i = 0; i < 4; i++) {
500 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: ROM not loaded");
505 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: GameData not set");
509 if (gfx_buffer_data->empty()) {
510 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: Graphics buffer is empty");
514 LOG_DEBUG(
"Room",
"Room %d: Copying 8BPP graphics (buffer size: %zu)",
531 auto is_right_palette_background_slot = [&](
int slot) ->
bool {
532 if (slot < 0 || slot >= 8) {
538 return (slot == 2 || slot == 3 || slot == 4 || slot == 7);
542 for (
int block = 0; block < 16; block++) {
546 if (sheet_id >= 223) {
547 LOG_WARN(
"Room",
"Invalid sheet index %d for block %d", sheet_id, block);
553 int src_sheet_offset = sheet_id * 4096;
556 if (src_sheet_offset + 4096 > gfx_buffer_data->size()) {
557 LOG_ERROR(
"Room",
"Graphics offset out of bounds: %d (size: %zu)",
558 src_sheet_offset, gfx_buffer_data->size());
563 int dest_index_base = block * 4096;
565 const uint8_t* src = gfx_buffer_data->data() + src_sheet_offset;
570 const bool right_pal = is_right_palette_background_slot(block);
572 memcpy(dst, src, 4096);
575 for (
int i = 0; i < 4096; ++i) {
577 if (p != 0 && p < 8) {
600 bool properties_changed =
false;
614 properties_changed =
true;
624 properties_changed =
true;
632 if (bg1_bmp.texture() && bg2_bmp.texture()) {
634 "Room %d: No changes detected, skipping render",
room_id_);
640 "Room %d: Rendering graphics (dirty_flags: g=%d o=%d l=%d t=%d)",
659 "Room %d: floor1=%d, floor2=%d, blocks_size=%zu",
room_id_,
664 bool need_floor_draw = was_graphics_dirty;
669 if (!bg1_bmp.is_active() || bg1_bmp.width() == 0 || !bg2_bmp.is_active() ||
670 bg2_bmp.width() == 0) {
671 need_floor_draw =
true;
673 "Room %d: Bitmaps not created yet, forcing floor draw",
room_id_);
676 if (need_floor_draw) {
685 bool need_bg_draw = was_graphics_dirty || need_floor_draw;
699 if (was_layout_dirty || need_floor_draw) {
708 int num_palettes = dungeon_pal_group.
size();
709 if (num_palettes == 0)
717 if (palette_ < game_data_->paletteset_ids.size() &&
722 if (palette_word.ok()) {
723 palette_id = palette_word.value() / 180;
726 if (palette_id < 0 || palette_id >= num_palettes) {
730 auto bg1_palette = dungeon_pal_group[palette_id];
736 LOG_DEBUG(
"Room",
"RenderRoomGraphics: Palette ID=%d, Size=%zu", palette_id,
738 if (!bg1_palette.empty()) {
739 LOG_DEBUG(
"Room",
"RenderRoomGraphics: First color: R=%d G=%d B=%d",
740 bg1_palette[0].rom_color().red, bg1_palette[0].rom_color().green,
741 bg1_palette[0].rom_color().blue);
748 if (bg1_palette.size() > 0) {
766 std::vector<SDL_Color> colors(
772 constexpr int kColorsPerRomBank = 15;
773 constexpr int kIndicesPerSdlBank = 16;
774 constexpr int kNumBanks = 6;
776 for (
int bank = 0; bank < kNumBanks; bank++) {
779 for (
int color = 0; color < kColorsPerRomBank; color++) {
780 size_t rom_index = bank * kColorsPerRomBank + color;
781 if (rom_index >= pal.size())
785 bank * kIndicesPerSdlBank + color + 1;
786 ImVec4 rgb = pal[rom_index].rgb();
787 colors[sdl_index] = {
788 static_cast<Uint8
>(rgb.x),
static_cast<Uint8
>(rgb.y),
789 static_cast<Uint8
>(rgb.z),
796 colors[255] = {0, 0, 0, 0};
801 SDL_SetColorKey(bmp.
surface(), SDL_TRUE, 255);
802 SDL_SetSurfaceBlendMode(bmp.
surface(), SDL_BLENDMODE_BLEND);
806 set_dungeon_palette(bg1_bmp, bg1_palette);
807 set_dungeon_palette(bg2_bmp, bg1_palette);
812 auto* surface = bg1_bmp.surface();
817 "Room::RenderRoomGraphics (BG1)", palette_id,
true);
821 "Room::RenderRoomGraphics (after SetPalette)", surface);
824 "Room::RenderRoomGraphics", palette_id,
false,
825 "SDL surface has no palette!");
839 if (bg2_bmp.surface()) {
840 SDL_SetSurfaceAlphaMod(bg2_bmp.surface(), 128);
848 if (bg2_bmp.surface()) {
849 SDL_SetSurfaceBlendMode(bg2_bmp.surface(), SDL_BLENDMODE_ADD);
873 LOG_DEBUG(
"[RenderRoomGraphics]",
"Queueing UPDATE for %s",
name);
877 LOG_DEBUG(
"[RenderRoomGraphics]",
"Queueing CREATE for %s",
name);
883 queue_texture(&bg1_bmp,
"bg1_buffer");
884 queue_texture(&bg2_bmp,
"bg2_buffer");
900 "Texture commands queued for batch processing");
908 LOG_DEBUG(
"Room",
"ROM not loaded, aborting");
915 if (!layout_status.ok()) {
917 layout_status.message().data());
923 if (layout_objects.empty()) {
931 LOG_DEBUG(
"RenderRoomGraphics",
"GameData not set, cannot render layout");
938 int num_palettes = dungeon_pal_group.
size();
939 if (num_palettes == 0)
942 if (palette_ < game_data_->paletteset_ids.size() &&
947 if (palette_word.ok()) {
948 palette_id = palette_word.value() / 180;
951 if (palette_id < 0 || palette_id >= num_palettes) {
955 auto room_palette = dungeon_pal_group[palette_id];
969 "RenderRoomGraphics",
"Layout Draw failed: %s",
970 std::string(status.message().data(), status.message().size()).c_str());
972 LOG_DEBUG(
"RenderRoomGraphics",
"Layout rendered with %zu objects",
973 layout_objects.size());
979 "Starting object rendering for room %d",
room_id_);
982 LOG_DEBUG(
"[RenderObjectsToBackground]",
"ROM not loaded, aborting");
991 bool bitmaps_exist = bg1_bmp.is_active() && bg1_bmp.width() > 0 &&
992 bg2_bmp.is_active() && bg2_bmp.width() > 0;
996 "Room %d: Objects not dirty, skipping render",
room_id_);
1002 LOG_DEBUG(
"[RenderObjectsToBackground]",
1003 "Room %d: Emulator rendering objects",
room_id_);
1009 int num_palettes = dungeon_pal_group.
size();
1014 if (palette_ < game_data_->paletteset_ids.size() &&
1019 if (palette_word.ok()) {
1020 palette_id = palette_word.value() / 180;
1023 if (palette_id < 0 || palette_id >= num_palettes) {
1027 auto room_palette = dungeon_pal_group[palette_id];
1063 int layer0_count = 0, layer1_count = 0, layer2_count = 0;
1065 switch (obj.GetLayerValue()) {
1079 "Room %03X Object Layer Summary: L0(BG1)=%d, L1(BG2)=%d, L2(BG3)=%d",
1080 room_id_, layer0_count, layer1_count, layer2_count);
1087 std::vector<RoomObject> objects_to_draw;
1099 objects_to_draw.push_back(obj);
1109 for (
int i = 0; i < static_cast<int>(
doors_.size()); ++i) {
1110 const auto& door =
doors_[i];
1112 door_def.
type = door.type;
1129 if (pot_item.item != 0) {
1131 int tile_x = pot_item.GetTileX();
1132 int tile_y = pot_item.GetTileY();
1139 for (
const auto& sprite :
sprites_) {
1140 if (sprite.key_drop() > 0) {
1146 uint8_t key_item = (sprite.key_drop() == 1) ? 0xFD : 0xFE;
1156 constexpr uint16_t kRoomDrawObj_PushableBlock = 0x0E52;
1157 constexpr uint16_t kRoomDrawObj_TorchUnlit = 0x0EC2;
1158 constexpr uint16_t kRoomDrawObj_TorchLit = 0x0ECA;
1162 static_cast<uint16_t
>(obj.id_), obj.x_, obj.y_, obj.layer_,
1167 const uint16_t off = obj.lit_ ? kRoomDrawObj_TorchLit
1168 : kRoomDrawObj_TorchUnlit;
1170 static_cast<uint16_t
>(obj.id_), obj.x_, obj.y_, obj.layer_, off,
1179 "[RenderObjectsToBackground]",
1180 "ObjectDrawer failed: %s - FALLING BACK TO MANUAL",
1181 std::string(status.message().data(), status.message().size()).c_str());
1183 LOG_DEBUG(
"[RenderObjectsToBackground]",
1184 "Room %d: Manual rendering objects (fallback)",
room_id_);
1188 int x = obj.x() * 8;
1189 int y = obj.y() * 8;
1194 uint8_t color_idx = 0;
1195 if (obj.GetLayerValue() == 0) {
1197 }
else if (obj.GetLayerValue() == 1) {
1203 for (
int py = y; py < y + height && py < bg1_bmp.height(); ++py) {
1204 for (
int px = x; px < x + width && px < bg1_bmp.width(); ++px) {
1205 int pixel_offset = (py * bg1_bmp.width()) + px;
1206 bg1_bmp.WriteToPixel(pixel_offset, color_idx);
1214 LOG_DEBUG(
"[RenderObjectsToBackground]",
1215 "Room %d: Objects rendered successfully",
room_id_);
1231 if (gfx_buffer_data->empty()) {
1235 auto rom_data =
rom()->vector();
1236 if (rom_data.empty()) {
1241 if (animated_frame_ < 0 || animated_frame_ > 10) {
1246 if (background_tileset_ < 0 || background_tileset_ > 255) {
1251 if (gfx_ptr < 0 || gfx_ptr >=
static_cast<int>(rom_data.size())) {
1256 while (data < 1024) {
1260 if (first_offset >= 0 &&
1261 first_offset <
static_cast<int>(gfx_buffer_data->size())) {
1262 uint8_t map_byte = (*gfx_buffer_data)[first_offset];
1265 int gfx_offset = data + (7 * 4096);
1266 if (gfx_offset >= 0 &&
1276 if (second_offset >= 0 &&
1277 second_offset <
static_cast<int>(gfx_buffer_data->size())) {
1278 uint8_t map_byte = (*gfx_buffer_data)[second_offset];
1281 int gfx_offset = data + (7 * 4096) - 1024;
1282 if (gfx_offset >= 0 &&
1294 auto rom_data =
rom()->vector();
1300 object_pointer =
SnesToPc(object_pointer);
1303 if (object_pointer < 0 || object_pointer >= (
int)
rom_->
size()) {
1307 int room_address = object_pointer + (
room_id_ * 3);
1310 if (room_address < 0 || room_address + 2 >= (
int)
rom_->
size()) {
1314 int tile_address = (rom_data[room_address + 2] << 16) +
1315 (rom_data[room_address + 1] << 8) + rom_data[room_address];
1317 int objects_location =
SnesToPc(tile_address);
1320 if (objects_location < 0 || objects_location >= (
int)
rom_->
size()) {
1325 if (objects_location + 1 < (
int)
rom_->
size()) {
1328 static_cast<uint8_t
>(rom_data[objects_location] & 0x0F);
1330 static_cast<uint8_t
>((rom_data[objects_location] >> 4) & 0x0F);
1332 "Room %d: Set floor1_graphics_=%d, floor2_graphics_=%d",
1336 layout_id_ =
static_cast<uint8_t
>((rom_data[objects_location + 1] >> 2) & 0x07);
1354 auto rom_data =
rom()->vector();
1360 int nbr_of_staircase = 0;
1362 int pos = objects_location;
1368 bool end_read =
false;
1372 while (!end_read && pos < (
int)
rom_->
size()) {
1374 if (pos + 1 >= (
int)
rom_->
size()) {
1379 b2 = rom_data[pos + 1];
1386 if (b1 == 0xFF && b2 == 0xFF) {
1389 LOG_DEBUG(
"Room",
"Room %03X: Layer transition to layer %d (%s)",
1391 layer == 1 ?
"BG2" : (layer == 2 ?
"BG3" :
"END"));
1401 if (b1 == 0xF0 && b2 == 0xFF) {
1408 if (pos + 2 >= (
int)
rom_->
size()) {
1412 b3 = rom_data[pos + 2];
1423 b1, b2, b3,
static_cast<uint8_t
>(layer));
1425 LOG_DEBUG(
"Room",
"Room %03X: Object 0x%03X at (%d,%d) layer=%d (%s)",
1427 layer == 0 ?
"BG1" : (layer == 1 ?
"BG2" :
"BG3"));
1431 if (r.
id_ >= 0 && r.
id_ <= 0xFFF) {
1445 "ParseDoor: room=%d b1=0x%02X b2=0x%02X pos=%d dir=%d type=%d",
1447 static_cast<int>(door.direction),
static_cast<int>(door.type));
1458 std::vector<uint8_t> bytes;
1461 std::vector<RoomObject> layer0_objects;
1462 std::vector<RoomObject> layer1_objects;
1463 std::vector<RoomObject> layer2_objects;
1476 switch (obj.GetLayerValue()) {
1478 layer0_objects.push_back(obj);
1481 layer1_objects.push_back(obj);
1484 layer2_objects.push_back(obj);
1500 for (
const auto& obj : layer0_objects) {
1501 auto encoded = obj.EncodeObjectToBytes();
1502 bytes.push_back(encoded.b1);
1503 bytes.push_back(encoded.b2);
1504 bytes.push_back(encoded.b3);
1506 bytes.push_back(0xFF);
1507 bytes.push_back(0xFF);
1510 for (
const auto& obj : layer1_objects) {
1511 auto encoded = obj.EncodeObjectToBytes();
1512 bytes.push_back(encoded.b1);
1513 bytes.push_back(encoded.b2);
1514 bytes.push_back(encoded.b3);
1516 bytes.push_back(0xFF);
1517 bytes.push_back(0xFF);
1520 for (
const auto& obj : layer2_objects) {
1521 auto encoded = obj.EncodeObjectToBytes();
1522 bytes.push_back(encoded.b1);
1523 bytes.push_back(encoded.b2);
1524 bytes.push_back(encoded.b3);
1529 bytes.push_back(0xF0);
1530 bytes.push_back(0xFF);
1531 for (
const auto& door :
doors_) {
1532 auto [b1, b2] = door.EncodeBytes();
1533 bytes.push_back(b1);
1534 bytes.push_back(b2);
1538 bytes.push_back(0xFF);
1539 bytes.push_back(0xFF);
1545 std::vector<uint8_t> bytes;
1547 for (
const auto& sprite :
sprites_) {
1555 b2 = (sprite.x() & 0x1F) | ((sprite.subtype() & 0x07) << 5);
1560 b1 = (sprite.y() & 0x1F) | ((sprite.subtype() & 0x18) << 2) |
1561 ((sprite.layer() & 0x01) << 7);
1563 bytes.push_back(b1);
1564 bytes.push_back(b2);
1565 bytes.push_back(b3);
1569 bytes.push_back(0xFF);
1579 const auto& rom_data = rom->
vector();
1580 int sprite_pointer = 0;
1581 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
1585 const int hard_end = std::min(
static_cast<int>(rom_data.size()),
kSpritesEndData);
1586 if (hard_end <= 0) {
1591 std::unordered_set<int> visited_addresses;
1593 int sprite_address = ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
1594 if (sprite_address < kSpritesData || sprite_address >= hard_end) {
1597 if (!visited_addresses.insert(sprite_address).second) {
1601 int stream_size = MeasureSpriteStreamSize(rom_data, sprite_address, hard_end);
1602 int stream_end = sprite_address + stream_size;
1603 if (stream_end > max_used) {
1604 max_used = stream_end;
1612 const std::vector<uint8_t>& encoded_bytes) {
1614 return absl::InvalidArgumentError(
"ROM not loaded");
1617 return absl::OutOfRangeError(
"Room ID out of range");
1619 if (encoded_bytes.empty() || encoded_bytes.back() != 0xFF ||
1620 (encoded_bytes.size() % 3) != 1) {
1621 return absl::InvalidArgumentError(
1622 "Encoded sprite payload must be N*3 bytes plus 0xFF terminator");
1625 const auto& rom_data = rom->
vector();
1626 int sprite_pointer = 0;
1629 int old_sprite_address =
1630 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
1631 if (old_sprite_address < 0 ||
1632 old_sprite_address >=
static_cast<int>(rom_data.size())) {
1633 return absl::OutOfRangeError(
"Sprite address out of range");
1636 const int hard_end = std::min(
static_cast<int>(rom_data.size()),
kSpritesEndData);
1637 const int old_stream_size =
1638 MeasureSpriteStreamSize(rom_data, old_sprite_address, hard_end);
1639 const uint8_t sort_mode = rom_data[old_sprite_address];
1640 const bool old_pointer_shared =
1641 IsSpritePointerShared(rom_data, sprite_pointer, room_id, old_sprite_address);
1644 const size_t required_size = 1u + encoded_bytes.size();
1646 static_cast<size_t>(write_pos) + required_size >
1648 return absl::ResourceExhaustedError(absl::StrFormat(
1649 "Not enough sprite data space. Need %d bytes at 0x%06X, "
1650 "region ends at 0x%06X",
1653 if (
static_cast<size_t>(write_pos) + required_size > rom_data.size()) {
1654 const int required_end = write_pos +
static_cast<int>(required_size);
1655 return absl::OutOfRangeError(absl::StrFormat(
1656 "ROM too small for sprite relocation write (need end=0x%06X, size=0x%06X)",
1657 required_end,
static_cast<int>(rom_data.size())));
1660 std::vector<uint8_t> relocated;
1661 relocated.reserve(required_size);
1662 relocated.push_back(sort_mode);
1663 relocated.insert(relocated.end(), encoded_bytes.begin(), encoded_bytes.end());
1666 const uint32_t snes_addr =
PcToSnes(write_pos);
1667 const int ptr_off = sprite_pointer + (room_id * 2);
1671 if (!old_pointer_shared && old_stream_size > 0 &&
1672 old_sprite_address + old_stream_size <=
static_cast<int>(rom_data.size())) {
1675 std::vector<uint8_t>(old_stream_size, 0x00)));
1678 return absl::OkStatus();
1682 if (
rom_ ==
nullptr) {
1683 return absl::InvalidArgumentError(
"ROM pointer is null");
1686 auto rom_data =
rom()->vector();
1692 object_pointer =
SnesToPc(object_pointer);
1694 if (object_pointer < 0 || object_pointer >= (
int)
rom_->
size()) {
1695 return absl::OutOfRangeError(
"Object pointer out of range");
1698 int room_address = object_pointer + (
room_id_ * 3);
1700 if (room_address < 0 || room_address + 2 >= (
int)
rom_->
size()) {
1701 return absl::OutOfRangeError(
"Room address out of range");
1704 int tile_address = (rom_data[room_address + 2] << 16) +
1705 (rom_data[room_address + 1] << 8) + rom_data[room_address];
1707 int objects_location =
SnesToPc(tile_address);
1709 if (objects_location < 0 || objects_location >= (
int)
rom_->
size()) {
1710 return absl::OutOfRangeError(
"Objects location out of range");
1715 int available_size = room_size_info.
room_size;
1718 int write_pos = objects_location + 2;
1725 if (encoded_bytes.size() > available_size - 2) {
1726 return absl::OutOfRangeError(absl::StrFormat(
1727 "Room %d object data too large! Size: %d, Available: %d",
room_id_,
1728 encoded_bytes.size(), available_size - 2));
1735 const int door_list_offset =
static_cast<int>(encoded_bytes.size()) -
1736 static_cast<int>(
doors_.size()) * 2 - 2;
1737 const int door_pointer_pc = write_pos + door_list_offset;
1740 static_cast<uint32_t
>(
PcToSnes(door_pointer_pc))));
1742 return absl::OkStatus();
1746 if (
rom_ ==
nullptr) {
1747 return absl::InvalidArgumentError(
"ROM pointer is null");
1750 const auto& rom_data =
rom()->vector();
1752 return absl::OutOfRangeError(
"Room ID out of range");
1755 int sprite_pointer = 0;
1757 int sprite_address =
1758 ReadRoomSpriteAddressPc(rom_data, sprite_pointer,
room_id_);
1759 if (sprite_address < 0 || sprite_address >=
static_cast<int>(
rom_->
size())) {
1760 return absl::OutOfRangeError(
"Sprite address out of range");
1763 int available_payload_size = 0;
1765 int next_sprite_address =
1766 ReadRoomSpriteAddressPc(rom_data, sprite_pointer,
room_id_ + 1);
1767 if (next_sprite_address >= 0) {
1768 int available_size = next_sprite_address - sprite_address;
1769 if (available_size > 0 && available_size <= 0x1000) {
1770 available_payload_size = std::max(0, available_size - 1);
1775 if (available_payload_size == 0) {
1776 const int hard_end =
1778 const int current_stream_size =
1779 MeasureSpriteStreamSize(rom_data, sprite_address, hard_end);
1780 available_payload_size = std::max(0, current_stream_size - 1);
1783 const int payload_address = sprite_address + 1;
1784 if (payload_address < 0 || payload_address >=
static_cast<int>(
rom_->
size())) {
1785 return absl::OutOfRangeError(absl::StrFormat(
1786 "Room %d has invalid sprite payload address",
room_id_));
1790 if (
static_cast<int>(encoded_bytes.size()) > available_payload_size) {
1798 if (
rom_ ==
nullptr) {
1799 return absl::InvalidArgumentError(
"ROM pointer is null");
1802 const auto& rom_data =
rom()->vector();
1805 return absl::OutOfRangeError(
"Room header pointer out of range");
1809 return absl::OutOfRangeError(
"Room header pointer bank out of range");
1815 header_pointer =
SnesToPc(header_pointer);
1817 int table_offset = header_pointer + (
room_id_ * 2);
1818 if (table_offset < 0 ||
1819 table_offset + 1 >=
static_cast<int>(rom_data.size())) {
1820 return absl::OutOfRangeError(
"Room header table offset out of range");
1824 (rom_data[table_offset + 1] << 8) + rom_data[table_offset];
1825 int header_location =
SnesToPc(address);
1827 if (header_location < 0 ||
1828 header_location + 13 >=
static_cast<int>(rom_data.size())) {
1829 return absl::OutOfRangeError(
"Room header location out of range");
1833 uint8_t byte0 = (
static_cast<uint8_t
>(
bg2()) << 5) |
1834 (
static_cast<uint8_t
>(
collision()) << 2) |
1860 if (msg_addr < 0 || msg_addr + 1 >=
static_cast<int>(rom_data.size())) {
1861 return absl::OutOfRangeError(
"Message ID address out of range");
1865 return absl::OkStatus();
1875 return absl::InvalidArgumentError(
"Invalid object parameters");
1882 return absl::OkStatus();
1887 return absl::OutOfRangeError(
"Object index out of range");
1893 return absl::OkStatus();
1898 return absl::OutOfRangeError(
"Object index out of range");
1902 return absl::InvalidArgumentError(
"Invalid object parameters");
1908 return absl::OkStatus();
1914 if (obj.x() == x && obj.y() == y && obj.GetLayerValue() == layer) {
1918 return absl::NotFoundError(
"No object found at position");
1923 if (
object.x() < 0 ||
object.x() > 63)
1925 if (
object.y() < 0 ||
object.y() > 63)
1929 if (
object.GetLayerValue() < 0 ||
object.GetLayerValue() > 2)
1933 if (
object.id_ < 0 || object.id_ > 0xFFF)
1937 if (
object.id_ < 0x100 &&
object.size() > 15)
1944 int& nbr_of_staircase) {
1948 if (nbr_of_staircase < 4) {
1971 }
else if (oid == 0xFB1) {
1981 const auto& rom_data =
rom()->vector();
1988 int sprite_pointer = 0;
1989 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
1993 int sprite_address =
1994 ReadRoomSpriteAddressPc(rom_data, sprite_pointer,
room_id_);
1995 if (sprite_address < 0 || sprite_address + 1 >=
static_cast<int>(rom_data.size())) {
2000 sprite_address += 1;
2002 while (sprite_address + 2 <
static_cast<int>(rom_data.size())) {
2003 uint8_t b1 = rom_data[sprite_address];
2004 uint8_t b2 = rom_data[sprite_address + 1];
2005 uint8_t b3 = rom_data[sprite_address + 2];
2011 sprites_.emplace_back(b3, (b2 & 0x1F), (b1 & 0x1F),
2012 ((b2 & 0xE0) >> 5) + ((b1 & 0x60) >> 2),
2019 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1E &&
2025 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1D &&
2032 sprite_address += 3;
2037 auto rom_data =
rom()->vector();
2044 for (
size_t i = 0; i < clength; i++) {
2045 if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
2049 if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
2050 0x8000) == 0x8000) {
2055 chest_data{rom_data[cpos + (i * 3) + 2], big});
2061 auto rom_data =
rom()->vector();
2072 "LoadDoors for room %d - doors are loaded via object stream",
2077 auto rom_data =
rom()->vector();
2090 return (obj.options() & ObjectOption::Torch) !=
2091 ObjectOption::Nothing;
2096 for (
int i = 0; i < bytes_count; i += 2) {
2097 if (i + 1 >= bytes_count)
2104 if (b1 == 0xFF && b2 == 0xFF) {
2109 uint16_t torch_room_id = (b2 << 8) | b1;
2113 while (i < bytes_count) {
2114 if (i + 1 >= bytes_count)
2121 if (b1 == 0xFF && b2 == 0xFF) {
2126 int address = ((b2 & 0x1F) << 8 | b1) >> 1;
2127 uint8_t px = address % 64;
2128 uint8_t py = address >> 6;
2129 uint8_t layer = (b2 & 0x20) >> 5;
2130 bool lit = (b2 & 0x80) == 0x80;
2133 RoomObject torch_obj(0x150, px, py, 0, layer);
2136 torch_obj.
lit_ = lit;
2140 LOG_DEBUG(
"Room",
"Loaded torch at (%d,%d) layer=%d lit=%d", px, py,
2149 while (i < bytes_count) {
2150 if (i + 1 >= bytes_count)
2154 if (b1 == 0xFF && b2 == 0xFF) {
2169 const std::vector<uint8_t>& rom_data,
int bytes_count) {
2175 if (b1 == 0xFF && b2 == 0xFF) {
2179 uint16_t room_id = (b2 << 8) | b1;
2184 std::vector<uint8_t> seg;
2191 if (b1 == 0xFF && b2 == 0xFF) {
2192 seg.push_back(0xFF);
2193 seg.push_back(0xFF);
2201 if (room_id < segments.size()) {
2202 segments[room_id] = std::move(seg);
2212 return absl::InvalidArgumentError(
"ROM not loaded");
2215 bool any_torch_objects =
false;
2216 for (
const auto& room : rooms) {
2217 for (
const auto& obj : room.GetTileObjects()) {
2219 any_torch_objects =
true;
2223 if (any_torch_objects) {
2227 if (!any_torch_objects) {
2228 return absl::OkStatus();
2231 const auto& rom_data = rom->
vector();
2234 if (existing_count > kTorchesMaxSize) {
2235 existing_count = kTorchesMaxSize;
2237 auto rom_segments = ParseRomTorchSegments(rom_data, existing_count);
2239 std::vector<uint8_t> bytes;
2240 for (
size_t room_id = 0;
2241 room_id < rooms.size() && room_id < rom_segments.size(); ++room_id) {
2242 const auto& room = rooms[room_id];
2243 bool has_torch_objects =
false;
2244 for (
const auto& obj : room.GetTileObjects()) {
2246 has_torch_objects =
true;
2250 if (has_torch_objects) {
2251 bytes.push_back(room_id & 0xFF);
2252 bytes.push_back((room_id >> 8) & 0xFF);
2253 for (
const auto& obj : room.GetTileObjects()) {
2257 int address = obj.x() + (obj.y() * 64);
2258 int word = address << 1;
2259 uint8_t b1 = word & 0xFF;
2260 uint8_t b2 = ((word >> 8) & 0x1F) | ((obj.GetLayerValue() & 1) << 5);
2264 bytes.push_back(b1);
2265 bytes.push_back(b2);
2267 bytes.push_back(0xFF);
2268 bytes.push_back(0xFF);
2269 }
else if (!rom_segments[room_id].empty()) {
2270 for (uint8_t b : rom_segments[room_id]) {
2276 if (bytes.size() > kTorchesMaxSize) {
2277 return absl::ResourceExhaustedError(
2278 absl::StrFormat(
"Torch data too large: %d bytes (max %d)", bytes.size(),
2285 const uint16_t current_len =
2288 if (current_len == bytes.size() &&
2289 kTorchData +
static_cast<int>(bytes.size()) <=
2290 static_cast<int>(rom_data.size()) &&
2291 std::equal(bytes.begin(), bytes.end(), rom_data.begin() +
kTorchData)) {
2292 return absl::OkStatus();
2296 static_cast<uint16_t
>(bytes.size())));
2302 return absl::InvalidArgumentError(
"ROM not loaded");
2304 const auto& rom_data = rom->
vector();
2305 if (kPitCount < 0 || kPitCount >=
static_cast<int>(rom_data.size()) ||
2306 kPitPointer + 2 >=
static_cast<int>(rom_data.size())) {
2307 return absl::OutOfRangeError(
"Pit count/pointer out of range");
2309 int pit_count_byte = rom_data[
kPitCount];
2310 int pit_entries = pit_count_byte / 2;
2311 if (pit_entries <= 0) {
2312 return absl::OkStatus();
2314 int pit_ptr_snes = (rom_data[
kPitPointer + 2] << 16) |
2316 int pit_data_pc =
SnesToPc(pit_ptr_snes);
2317 int data_len = pit_entries * 2;
2318 if (pit_data_pc < 0 ||
2319 pit_data_pc + data_len >
static_cast<int>(rom_data.size())) {
2320 return absl::OutOfRangeError(
"Pit data region out of range");
2322 std::vector<uint8_t> data(rom_data.begin() + pit_data_pc,
2323 rom_data.begin() + pit_data_pc + data_len);
2333 return absl::InvalidArgumentError(
"ROM not loaded");
2335 const auto& rom_data = rom->
vector();
2336 if (
kBlocksLength + 1 >=
static_cast<int>(rom_data.size())) {
2337 return absl::OutOfRangeError(
"Blocks length out of range");
2341 if (blocks_count <= 0) {
2342 return absl::OkStatus();
2344 const int kRegionSize = 0x80;
2347 for (
int r = 0; r < 4; ++r) {
2348 if (ptrs[r] + 2 >=
static_cast<int>(rom_data.size())) {
2349 return absl::OutOfRangeError(
"Blocks pointer out of range");
2351 int snes = (rom_data[ptrs[r] + 2] << 16) | (rom_data[ptrs[r] + 1] << 8) |
2354 int off = r * kRegionSize;
2355 int len = std::min(kRegionSize, blocks_count - off);
2358 if (pc < 0 || pc + len >
static_cast<int>(rom_data.size())) {
2359 return absl::OutOfRangeError(
"Blocks data region out of range");
2361 std::vector<uint8_t> chunk(rom_data.begin() + pc,
2362 rom_data.begin() + pc + len);
2367 return absl::OkStatus();
2372 return absl::InvalidArgumentError(
"ROM not loaded");
2379 const auto& rom_data = rom->
vector();
2384 if (!has_ptr_table) {
2385 for (
auto& room : rooms) {
2386 if (room.custom_collision_dirty()) {
2387 return absl::FailedPreconditionError(
2388 "Custom collision region not present in this ROM");
2391 return absl::OkStatus();
2394 if (!has_data_region) {
2395 for (
auto& room : rooms) {
2396 if (room.custom_collision_dirty()) {
2397 return absl::FailedPreconditionError(
2398 "Custom collision data region not present in this ROM");
2401 return absl::OkStatus();
2410 "CustomCollisionPointers"));
2413 "CustomCollisionData"));
2416 for (
auto& room : rooms) {
2417 if (!room.custom_collision_dirty()) {
2421 const int room_id = room.id();
2423 if (ptr_offset + 2 >=
static_cast<int>(rom_data.size())) {
2424 return absl::OutOfRangeError(
"Custom collision pointer out of range");
2427 if (!room.has_custom_collision()) {
2432 room.ClearCustomCollisionDirty();
2438 for (uint8_t v : room.custom_collision().tiles) {
2448 room.ClearCustomCollisionDirty();
2453 room.ClearCustomCollisionDirty();
2456 return absl::OkStatus();
2463 const std::vector<uint8_t>& rom_data,
int cpos,
int clength) {
2464 std::vector<std::vector<std::pair<uint8_t, bool>>> per_room(
kNumberOfRooms);
2466 i < clength && cpos + i * 3 + 2 < static_cast<int>(rom_data.size());
2468 int off = cpos + i * 3;
2469 uint16_t word = (rom_data[off + 1] << 8) | rom_data[off];
2470 uint16_t room_id = word & 0x7FFF;
2471 bool big = (word & 0x8000) != 0;
2472 uint8_t
id = rom_data[off + 2];
2474 per_room[room_id].emplace_back(
id, big);
2481 const std::vector<uint8_t>& rom_data) {
2484 if (table_addr + (
kNumberOfRooms * 2) >
static_cast<int>(rom_data.size())) {
2488 int ptr_off = table_addr + (room_id * 2);
2489 uint16_t item_ptr = (rom_data[ptr_off + 1] << 8) | rom_data[ptr_off];
2490 int item_addr =
SnesToPc(0x010000 | item_ptr);
2491 if (item_addr < 0 || item_addr >=
static_cast<int>(rom_data.size())) {
2494 int next_ptr_off = table_addr + ((room_id + 1) * 2);
2495 int next_item_addr =
2497 ?
SnesToPc(0x010000 | ((rom_data[next_ptr_off + 1] << 8) |
2498 rom_data[next_ptr_off]))
2499 : item_addr + 0x100;
2500 int max_len = next_item_addr - item_addr;
2504 std::vector<uint8_t> bytes;
2505 int cursor = item_addr;
2507 std::min(item_addr + max_len,
static_cast<int>(rom_data.size()));
2508 while (cursor + 1 < limit) {
2509 uint8_t b1 = rom_data[cursor++];
2510 uint8_t b2 = rom_data[cursor++];
2511 bytes.push_back(b1);
2512 bytes.push_back(b2);
2513 if (b1 == 0xFF && b2 == 0xFF) {
2516 if (cursor >= limit) {
2519 bytes.push_back(rom_data[cursor++]);
2521 if (!bytes.empty()) {
2522 per_room[room_id] = std::move(bytes);
2532 return absl::InvalidArgumentError(
"ROM not loaded");
2534 const auto& rom_data = rom->
vector();
2537 return absl::OutOfRangeError(
"Chest pointers out of range");
2544 auto rom_chests = ParseRomChests(rom_data, cpos, clength);
2546 std::vector<uint8_t> bytes;
2547 for (
size_t room_id = 0;
2548 room_id < rooms.size() && room_id < rom_chests.size(); ++room_id) {
2549 const auto& room = rooms[room_id];
2550 const auto& chests = room.GetChests();
2551 if (chests.empty()) {
2552 for (
const auto& [
id, big] : rom_chests[room_id]) {
2553 uint16_t word = room_id | (big ? 0x8000 : 0);
2554 bytes.push_back(word & 0xFF);
2555 bytes.push_back((word >> 8) & 0xFF);
2556 bytes.push_back(
id);
2559 for (
const auto& c : chests) {
2560 uint16_t word = room_id | (c.size ? 0x8000 : 0);
2561 bytes.push_back(word & 0xFF);
2562 bytes.push_back((word >> 8) & 0xFF);
2563 bytes.push_back(c.id);
2568 if (cpos < 0 || cpos +
static_cast<int>(bytes.size()) >
2569 static_cast<int>(rom_data.size())) {
2570 return absl::OutOfRangeError(
"Chest data region out of range");
2573 static_cast<uint16_t
>(bytes.size() / 3)));
2579 return absl::InvalidArgumentError(
"ROM not loaded");
2581 const auto& rom_data = rom->
vector();
2582 const auto rom_pot_items = ParseRomPotItems(rom_data);
2584 if (table_addr + (
kNumberOfRooms * 2) >
static_cast<int>(rom_data.size())) {
2585 return absl::OutOfRangeError(
"Room items pointer table out of range");
2587 for (
size_t room_id = 0; room_id < rooms.size() && room_id <
kNumberOfRooms;
2589 const auto& room = rooms[room_id];
2590 const bool room_loaded = room.IsLoaded();
2591 const auto& pot_items = room.GetPotItems();
2592 int ptr_off = table_addr + (room_id * 2);
2593 uint16_t item_ptr = (rom_data[ptr_off + 1] << 8) | rom_data[ptr_off];
2594 int item_addr =
SnesToPc(0x010000 | item_ptr);
2595 if (item_addr < 0 || item_addr + 2 >=
static_cast<int>(rom_data.size())) {
2598 int next_ptr_off = table_addr + ((room_id + 1) * 2);
2599 int next_item_addr =
2601 ?
SnesToPc(0x010000 | ((rom_data[next_ptr_off + 1] << 8) |
2602 rom_data[next_ptr_off]))
2603 : item_addr + 0x100;
2604 int max_len = next_item_addr - item_addr;
2607 std::vector<uint8_t> bytes;
2609 if (room_id < rom_pot_items.size()) {
2610 bytes = rom_pot_items[room_id];
2612 if (bytes.empty()) {
2616 for (
const auto& pi : pot_items) {
2617 bytes.push_back(pi.position & 0xFF);
2618 bytes.push_back((pi.position >> 8) & 0xFF);
2619 bytes.push_back(pi.item);
2621 bytes.push_back(0xFF);
2622 bytes.push_back(0xFF);
2624 if (
static_cast<int>(bytes.size()) > max_len) {
2629 return absl::OkStatus();
2633 auto rom_data =
rom()->vector();
2646 return (obj.options() & ObjectOption::Block) !=
2647 ObjectOption::Nothing;
2652 std::vector<uint8_t> blocks_data(blocks_count);
2660 for (
int i = 0; i < 0x80 && i < blocks_count; i++) {
2661 blocks_data[i] = rom_data[pos1 + i];
2663 if (i + 0x80 < blocks_count) {
2664 blocks_data[i + 0x80] = rom_data[pos2 + i];
2666 if (i + 0x100 < blocks_count) {
2667 blocks_data[i + 0x100] = rom_data[pos3 + i];
2669 if (i + 0x180 < blocks_count) {
2670 blocks_data[i + 0x180] = rom_data[pos4 + i];
2675 for (
int i = 0; i < blocks_count; i += 4) {
2676 if (i + 3 >= blocks_count)
2679 uint8_t b1 = blocks_data[i];
2680 uint8_t b2 = blocks_data[i + 1];
2681 uint8_t b3 = blocks_data[i + 2];
2682 uint8_t b4 = blocks_data[i + 3];
2685 uint16_t block_room_id = (b2 << 8) | b1;
2688 if (b3 == 0xFF && b4 == 0xFF) {
2693 int address = ((b4 & 0x1F) << 8 | b3) >> 1;
2694 uint8_t px = address % 64;
2695 uint8_t py = address >> 6;
2696 uint8_t layer = (b4 & 0x20) >> 5;
2699 RoomObject block_obj(0x0E00, px, py, 0, layer);
2705 LOG_DEBUG(
"Room",
"Loaded block at (%d,%d) layer=%d", px, py, layer);
2713 auto rom_data =
rom()->vector();
2727 int ptr_addr = table_addr + (
room_id_ * 2);
2728 if (ptr_addr + 1 >=
static_cast<int>(rom_data.size()))
2731 uint16_t item_ptr = (rom_data[ptr_addr + 1] << 8) | rom_data[ptr_addr];
2734 int item_addr =
SnesToPc(0x010000 | item_ptr);
2739 while (item_addr + 2 <
static_cast<int>(rom_data.size())) {
2741 uint16_t position = (rom_data[item_addr + 1] << 8) | rom_data[item_addr];
2744 if (position == 0xFFFF)
2748 uint8_t item_type = rom_data[item_addr + 2];
2752 pot_item.
item = item_type;
2760 auto rom_data =
rom()->vector();
2763 int pit_entries = rom_data[
kPitCount] / 2;
2768 int pit_data_addr =
SnesToPc(pit_ptr);
2770 LOG_DEBUG(
"Room",
"LoadPits: room_id=%d, pit_entries=%d, pit_ptr=0x%06X",
2780 LOG_DEBUG(
"Room",
"Pit destination - target=%d, target_layer=%d",
2795 for (
const auto& sprite :
sprites_) {
2796 if (sprite.IsOverlord()) {
2806 for (
const auto& door :
doors_) {
2808 const bool is_special = [&]() ->
bool {
2809 switch (door.type) {
2839 auto options = obj.options();
2852 if (obj.id_ == 0x11E || obj.id_ == 0x11F) {
2857 if (obj.id_ >= 0xF83 && obj.id_ <= 0xF8F) {
2864 if ((obj.id_ >= 0x130 && obj.id_ <= 0x135) ||
2865 obj.id_ == 0x139 || obj.id_ == 0x13A || obj.id_ == 0x13B) {
2869 else if (obj.id_ >= 0x13C && obj.id_ <= 0x13F) {
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::Status WriteByte(int addr, uint8_t value)
const auto & vector() const
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
absl::Status WriteWord(int addr, uint16_t value)
absl::Status WriteLong(uint32_t addr, uint32_t value)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
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)
void EnsureBitmapInitialized()
void ClearCoverageBuffer()
void ClearPriorityBuffer()
Represents a bitmap image optimized for SNES ROM hacking.
TextureHandle texture() const
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
SDL_Surface * surface() const
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
absl::Status Allow(uint32_t start, uint32_t end, std::string_view label)
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.
absl::Status DrawRoomDrawObjectData2x2(uint16_t object_id, int tile_x, int tile_y, RoomObject::LayerType layer, uint16_t room_draw_object_data_offset, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2)
Draw a fixed 2x2 (16x16) tile pattern from RoomDrawObjectData.
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.
const std::vector< RoomObject > & GetObjects() const
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 set_options(ObjectOption options)
bool ValidateObject(const RoomObject &object) const
std::vector< RoomObject > tile_objects_
gfx::BackgroundBuffer object_bg1_buffer_
void SetTag2Direct(TagKey tag2)
bool HasExceededLimits() const
Check if any object limits are exceeded.
absl::Status UpdateObject(size_t index, const RoomObject &object)
void SetStair4Target(uint8_t target)
void SetPitsTarget(uint8_t target)
void SetIsLight(bool is_light)
gfx::BackgroundBuffer bg2_buffer_
uint8_t cached_floor2_graphics_
std::vector< zelda3::Sprite > sprites_
CustomCollisionMap custom_collision_
void SetLoaded(bool loaded)
void CopyRoomGraphicsToBuffer()
zelda3_version_pointers version_constants() const
void LoadRoomGraphics(uint8_t entrance_blockset=0xFF)
std::vector< Door > doors_
void SetStaircaseRoom(int index, uint8_t room)
absl::Status RemoveObject(size_t index)
void SetStair1TargetLayer(uint8_t layer)
void SetLayer2Mode(uint8_t mode)
void LoadLayoutTilesToBuffer()
uint8_t staircase_room(int index) const
void SetTag2(TagKey tag2)
std::vector< DungeonLimitInfo > GetExceededLimitDetails() const
Get list of exceeded limits with details.
void ParseObjectsFromLocation(int objects_location)
uint8_t cached_floor1_graphics_
bool custom_collision_dirty_
void SetTag1Direct(TagKey tag1)
void SetHolewarp(uint8_t hw)
void SetStair2Target(uint8_t target)
void SetCollision(CollisionKey collision)
gfx::BackgroundBuffer bg1_buffer_
absl::Status SaveObjects()
void SetStaircasePlane(int index, uint8_t plane)
void SetIsDark(bool is_dark)
std::map< DungeonLimit, int > GetLimitedObjectCounts() const
Count limited objects in this room.
void RenderRoomGraphics()
absl::Status SaveRoomHeader()
gfx::Bitmap composite_bitmap_
Room & operator=(Room &&)
CollisionKey collision() const
uint8_t staircase_rooms_[4]
gfx::Bitmap & GetCompositeBitmap(RoomLayerManager &layer_mgr)
Get a composite bitmap of all layers merged.
std::vector< uint8_t > EncodeObjects() const
void SetEffect(EffectKey effect)
gfx::BackgroundBuffer object_bg2_buffer_
std::array< uint8_t, 16 > blocks_
void SetTag1(TagKey tag1)
void SetBackgroundTileset(uint8_t tileset)
void SetStair3TargetLayer(uint8_t layer)
absl::Status SaveSprites()
void SetLayerMerging(LayerMergeType merging)
void SetPitsTargetLayer(uint8_t layer)
void SetSpriteTileset(uint8_t tileset)
void SetStair1Target(uint8_t target)
void SetBg2(background2 bg2)
void SetSpriteset(uint8_t ss)
std::unique_ptr< DungeonState > dungeon_state_
void LoadAnimatedGraphics()
std::vector< uint8_t > EncodeSprites() const
std::vector< chest_data > chests_in_room_
void SetBlockset(uint8_t bs)
LayerMergeType layer_merging_
uint8_t staircase_plane(int index) const
uint8_t cached_spriteset_
std::array< uint8_t, 0x10000 > current_gfx16_
std::vector< staircase > z3_staircases_
std::vector< PotItem > pot_items_
uint8_t background_tileset_
void SetStair3Target(uint8_t target)
void HandleSpecialObjects(short oid, uint8_t posX, uint8_t posY, int &nbr_of_staircase)
void SetStair4TargetLayer(uint8_t layer)
absl::Status AddObject(const RoomObject &object)
absl::StatusOr< size_t > FindObjectAt(int x, int y, int layer) const
void SetPalette(uint8_t pal)
void SetStair2TargetLayer(uint8_t layer)
void RenderObjectsToBackground()
void SetLayer2Behavior(uint8_t behavior)
void SetMessageId(uint16_t mid)
A class for managing sprites in the overworld and underworld.
auto set_key_drop(int key)
zelda3_bg2_effect
Background layer 2 effects.
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
std::vector< std::vector< uint8_t > > ParseRomPotItems(const std::vector< uint8_t > &rom_data)
constexpr int kTorchesMaxSize
bool IsSpritePointerShared(const std::vector< uint8_t > &rom_data, int table_pc, int room_id, int sprite_address)
absl::Status GetSpritePointerTablePc(const std::vector< uint8_t > &rom_data, int *table_pc)
std::vector< std::vector< uint8_t > > ParseRomTorchSegments(const std::vector< uint8_t > &rom_data, int bytes_count)
int ReadRoomSpriteAddressPc(const std::vector< uint8_t > &rom_data, int table_pc, int room_id)
std::vector< std::vector< std::pair< uint8_t, bool > > > ParseRomChests(const std::vector< uint8_t > &rom_data, int cpos, int clength)
int MeasureSpriteStreamSize(const std::vector< uint8_t > &rom_data, int sprite_address, int hard_end)
constexpr int kBlocksPointer4
absl::Status SaveAllChests(Rom *rom, absl::Span< const Room > rooms)
constexpr int kDoorPointers
constexpr int kGfxBufferAnimatedFrameStride
const std::string RoomTag[65]
absl::Status WriteTrackCollision(Rom *rom, int room_id, const CustomCollisionMap &map)
constexpr int kGfxBufferAnimatedFrameOffset
@ NormalDoorOneSidedShutter
Normal door (lower layer; with one-sided shutters)
@ TopShutterLower
Top-sided shutter door (lower layer)
@ SmallKeyDoor
Small key door.
@ BottomShutterLower
Bottom-sided shutter door (lower layer)
@ TopSidedShutter
Top-sided shutter door.
@ DoubleSidedShutterLower
Double-sided shutter (lower layer)
@ UnusableBottomShutter
Unusable bottom-sided shutter door.
@ UnopenableBigKeyDoor
Unopenable, double-sided big key door.
@ BottomSidedShutter
Bottom-sided shutter door.
@ UnusedDoubleSidedShutter
Unused double-sided shutter.
@ CurtainDoor
Curtain door.
@ BigKeyDoor
Big key door.
@ EyeWatchDoor
Eye watch door.
@ DoubleSidedShutter
Double sided shutter door.
constexpr int kSpritesEndData
constexpr int kTorchesLengthPointer
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
constexpr int kCustomCollisionDataSoftEnd
std::vector< DungeonLimitInfo > GetExceededLimits(const std::map< DungeonLimit, int > &counts)
constexpr int kChestsLengthPointer
int FindMaxUsedSpriteAddress(Rom *rom)
constexpr int kMessagesIdDungeon
absl::Status RelocateSpriteData(Rom *rom, int room_id, const std::vector< uint8_t > &encoded_bytes)
constexpr int kGfxBufferRoomOffset
absl::Status SaveAllPotItems(Rom *rom, absl::Span< const Room > rooms)
constexpr int kGfxBufferRoomSpriteOffset
RoomSize CalculateRoomSize(Rom *rom, int room_id)
constexpr int kPitPointer
constexpr int kSpritesData
absl::Status SaveAllTorches(Rom *rom, absl::Span< const Room > rooms)
constexpr int kTileAddress
constexpr int kRoomsSpritePointer
constexpr int kTileAddressFloor
absl::Status SaveAllPits(Rom *rom)
constexpr int kChestsDataPointer1
constexpr int kBlocksLength
absl::Status SaveAllBlocks(Rom *rom)
constexpr int kBlocksPointer1
constexpr int kRoomItemsPointers
constexpr int kGfxBufferRoomSpriteStride
absl::StatusOr< CustomCollisionMap > LoadCustomCollisionMap(Rom *rom, int room_id)
constexpr bool HasCustomCollisionPointerTable(std::size_t rom_size)
Room LoadRoomFromRom(Rom *rom, int room_id)
constexpr int kCustomCollisionDataPosition
bool HasExceededLimits(const std::map< DungeonLimit, int > &counts)
constexpr uint32_t kDungeonPalettePointerTable
constexpr uint16_t kStairsObjects[]
constexpr int kNumberOfRooms
const std::string RoomEffect[8]
constexpr int kRoomHeaderPointer
constexpr bool HasCustomCollisionDataRegion(std::size_t rom_size)
constexpr int kBlocksPointer3
constexpr int kRoomHeaderPointerBank
constexpr int kCustomCollisionRoomPointers
constexpr int kGfxBufferStride
std::map< DungeonLimit, int > CreateLimitCounter()
absl::Status SaveAllCollision(Rom *rom, absl::Span< Room > rooms)
constexpr int kGfxBufferRoomSpriteLastLineOffset
constexpr int kBlocksPointer2
constexpr int kRoomObjectPointer
constexpr int kGfxBufferOffset
uint32_t PcToSnes(uint32_t addr)
uint32_t SnesToPc(uint32_t addr) noexcept
SDL2/SDL3 compatibility layer.
#define RETURN_IF_ERROR(expr)
Legacy chest data structure.
PaletteGroup dungeon_main
Represents a group of palettes.
void AddPalette(SnesPalette pal)
std::array< std::array< uint8_t, 4 >, kNumSpritesets > spriteset_ids
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
std::array< std::array< uint8_t, 4 >, kNumPalettesets > paletteset_ids
gfx::PaletteGroupMap palette_groups
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
std::vector< uint8_t > graphics_buffer
int64_t room_size_pointer
static Door FromRomBytes(uint8_t b1, uint8_t b2)
Public YAZE API umbrella header.