8#include <unordered_set>
11#include "absl/strings/str_cat.h"
12#include "absl/strings/str_format.h"
34 return std::any_of(objects.begin(), objects.end(),
35 [](
const RoomObject& obj) { return obj.id_ == 0x31; });
38template <
typename WriteColor>
41 WriteColor write_color) {
42 if (hud_palette !=
nullptr) {
43 const size_t hud_count = std::min<size_t>(hud_palette->
size(), 32);
44 for (
size_t i = 0; i < hud_count; ++i) {
45 write_color(
static_cast<int>(i), (*hud_palette)[i]);
49 constexpr int kColorsPerRomBank = 15;
50 constexpr int kIndicesPerSdlBank = 16;
51 constexpr int kNumRomBanks = 6;
52 constexpr int kDungeonBankStart = 2;
53 for (
int rom_bank = 0; rom_bank < kNumRomBanks; ++rom_bank) {
54 const int sdl_bank = rom_bank + kDungeonBankStart;
55 for (
int color = 0; color < kColorsPerRomBank; ++color) {
56 const size_t rom_index =
57 static_cast<size_t>(rom_bank * kColorsPerRomBank + color);
58 if (rom_index >= dungeon_palette.
size()) {
61 const int dst_index = sdl_bank * kIndicesPerSdlBank + color + 1;
62 write_color(dst_index, dungeon_palette[rom_index]);
72 std::vector<SDL_Color> colors(256, {0, 0, 0, 0});
73 PopulateDungeonRenderPaletteRows(
74 dungeon_palette, hud_palette,
76 if (dst_index < 0 || dst_index >=
static_cast<int>(colors.size())) {
79 const ImVec4 rgb = color.
rgb();
80 colors[dst_index] = {
static_cast<Uint8
>(rgb.x),
81 static_cast<Uint8
>(rgb.y),
82 static_cast<Uint8
>(rgb.z), 255};
84 colors[255] = {0, 0, 0, 0};
91 PopulateDungeonRenderPaletteRows(
92 dungeon_palette, hud_palette,
94 if (dst_index < 0 || dst_index >=
static_cast<int>(cgram.size())) {
97 cgram[dst_index] = color.
snes();
108 "Light Torch to See Floor",
113 "NW Kill Enemy to Open",
114 "NE Kill Enemy to Open",
115 "SW Kill Enemy to Open",
116 "SE Kill Enemy to Open",
117 "W Kill Enemy to Open",
118 "E Kill Enemy to Open",
119 "N Kill Enemy to Open",
120 "S Kill Enemy to Open",
121 "Clear Quadrant to Open",
122 "Clear Full Tile to Open",
123 "NW Push Block to Open",
124 "NE Push Block to Open",
125 "SW Push Block to Open",
126 "SE Push Block to Open",
127 "W Push Block to Open",
128 "E Push Block to Open",
129 "N Push Block to Open",
130 "S Push Block to Open",
131 "Push Block to Open",
132 "Pull Lever to Open",
133 "Collect Prize to Open",
134 "Hold Switch Open Door",
135 "Toggle Switch to Open Door",
144 "Push Switch Exploding Wall",
146 "Open Chest (Holes 0)",
149 "Defeat Boss for Dungeon Prize",
150 "SE Kill Enemy to Push Block",
151 "Trigger Switch Chest",
152 "Pull Lever Exploding Wall",
153 "NW Kill Enemy for Chest",
154 "NE Kill Enemy for Chest",
155 "SW Kill Enemy for Chest",
156 "SE Kill Enemy for Chest",
157 "W Kill Enemy for Chest",
158 "E Kill Enemy for Chest",
159 "N Kill Enemy for Chest",
160 "S Kill Enemy for Chest",
161 "Clear Quadrant for Chest",
162 "Clear Full Tile for Chest",
163 "Light Torches to Open",
171 "Open Chest for Holes 8",
172 "Push Block for Chest",
173 "Clear Room for Triforce Door",
174 "Light Torches for Chest",
181 if (table_pc ==
nullptr) {
182 return absl::InvalidArgumentError(
"table_pc pointer is null");
185 return absl::OutOfRangeError(
186 "Sprite pointer table address is out of range");
192 if (pc < 0 || pc + (
kNumberOfRooms * 2) >
static_cast<int>(rom_data.size())) {
193 return absl::OutOfRangeError(
"Sprite pointer table is out of range");
197 return absl::OkStatus();
205 const int ptr_off = table_pc + (room_id * 2);
206 if (ptr_off < 0 || ptr_off + 1 >=
static_cast<int>(rom_data.size())) {
210 int sprite_address_snes =
211 (0x09 << 16) | (rom_data[ptr_off + 1] << 8) | rom_data[ptr_off];
212 return SnesToPc(sprite_address_snes);
216 int sprite_address,
int hard_end) {
217 if (sprite_address < 0 || sprite_address >= hard_end ||
218 sprite_address >=
static_cast<int>(rom_data.size())) {
222 int cursor = sprite_address + 1;
223 while (cursor < hard_end) {
224 if (rom_data[cursor] == 0xFF) {
228 if (cursor + 2 >= hard_end) {
235 return std::max(0, cursor - sprite_address);
239 int room_id,
int sprite_address) {
274 auto room_size_address = 0xF8000 + (room_id * 3);
277 if (room_size_address < 0 ||
278 room_size_address + 2 >=
static_cast<int>(rom->
size())) {
283 uint8_t low = rom->
data()[room_size_address];
284 uint8_t high = rom->
data()[room_size_address + 1];
285 uint8_t bank = rom->
data()[room_size_address + 2];
288 int long_address = (bank << 16) | (high << 8) | low;
291 if (long_address == 0x0A8000) {
299 int next_room_address = 0xF8000 + ((room_id + 1) * 3);
302 if (next_room_address < 0 ||
303 next_room_address + 2 >=
static_cast<int>(rom->
size())) {
308 uint8_t next_low = rom->
data()[next_room_address];
309 uint8_t next_high = rom->
data()[next_room_address + 1];
310 uint8_t next_bank = rom->
data()[next_room_address + 2];
313 int next_long_address = (next_bank << 16) | (next_high << 8) | next_low;
316 int actual_room_size = next_long_address - long_address;
347 Room room(room_id, rom);
363 header_pointer =
SnesToPc(header_pointer);
372 int table_offset = (header_pointer) + (room_id * 2);
373 if (table_offset < 0 || table_offset + 1 >=
static_cast<int>(rom->
size())) {
378 (rom->
data()[table_offset + 1] << 8) +
379 rom->
data()[table_offset];
381 auto header_location =
SnesToPc(address);
384 if (header_location < 0 ||
385 header_location + 13 >=
static_cast<int>(rom->
size())) {
394 room.
SetBg2(background2::DarkRoom);
430 header_pointer_2 =
SnesToPc(header_pointer_2);
439 int table_offset_2 = (header_pointer_2) + (room_id * 2);
440 if (table_offset_2 < 0 ||
441 table_offset_2 + 1 >=
static_cast<int>(rom->
size())) {
446 (rom->
data()[table_offset_2 + 1] << 8) +
447 rom->
data()[table_offset_2];
450 if (msg_addr >= 0 && msg_addr + 1 <
static_cast<int>(rom->
size())) {
451 uint16_t msg_val = (rom->
data()[msg_addr + 1] << 8) | rom->
data()[msg_addr];
459 if (hpos < 0 || hpos + 14 >=
static_cast<int>(rom->
size())) {
463 uint8_t b = rom->
data()[hpos];
490 b = rom->
data()[hpos];
517 game_data_(game_data),
529 const int num_palettes =
static_cast<int>(group.size());
530 if (num_palettes == 0)
534 if (palette_ < game_data_->paletteset_ids.size() &&
542 if (id < 0 || id >= num_palettes)
559 for (
int i = 0; i < 8; i++) {
564 if (entrance_blockset != 0xFF && room_gfx[entrance_blockset][3] != 0) {
565 blocks_[i] = room_gfx[entrance_blockset][3];
574 for (
int i = 0; i < 4; i++) {
605 (void)entrance_blockset;
614 (void)entrance_blockset;
621 !bg2_bmp.is_active() || bg2_bmp.width() == 0) {
637 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: ROM not loaded");
642 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: GameData not set");
646 if (gfx_buffer_data->empty()) {
647 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: Graphics buffer is empty");
651 LOG_DEBUG(
"Room",
"Room %d: Copying 8BPP graphics (buffer size: %zu)",
668 auto is_right_palette_background_slot = [&](
int slot) ->
bool {
669 if (slot < 0 || slot >= 8) {
675 return (slot == 2 || slot == 3 || slot == 4 || slot == 7);
679 for (
int block = 0; block < 16; block++) {
683 if (sheet_id >= 223) {
684 LOG_WARN(
"Room",
"Invalid sheet index %d for block %d", sheet_id, block);
690 int src_sheet_offset = sheet_id * 4096;
693 if (src_sheet_offset + 4096 > gfx_buffer_data->size()) {
694 LOG_ERROR(
"Room",
"Graphics offset out of bounds: %d (size: %zu)",
695 src_sheet_offset, gfx_buffer_data->size());
700 int dest_index_base = block * 4096;
702 const uint8_t* src = gfx_buffer_data->data() + src_sheet_offset;
707 const bool right_pal = is_right_palette_background_slot(block);
709 memcpy(dst, src, 4096);
712 for (
int i = 0; i < 4096; ++i) {
714 if (p != 0 && p < 8) {
741 bool properties_changed =
false;
755 properties_changed =
true;
765 properties_changed =
true;
773 if (bg1_bmp.is_active() && bg1_bmp.width() > 0 && bg2_bmp.is_active() &&
774 bg2_bmp.width() > 0) {
776 "Room %d: No changes detected, skipping render",
room_id_);
782 "Room %d: Rendering graphics (dirty_flags: g=%d o=%d l=%d t=%d)",
801 "Room %d: floor1=%d, floor2=%d, blocks_size=%zu",
room_id_,
806 bool need_floor_draw = was_graphics_dirty;
811 if (!bg1_bmp.is_active() || bg1_bmp.width() == 0 || !bg2_bmp.is_active() ||
812 bg2_bmp.width() == 0) {
813 need_floor_draw =
true;
815 "Room %d: Bitmaps not created yet, forcing floor draw",
room_id_);
818 if (need_floor_draw) {
827 bool need_bg_draw = was_graphics_dirty || need_floor_draw;
841 if (was_layout_dirty || need_floor_draw) {
850 if (dungeon_pal_group.empty())
854 auto bg1_palette = dungeon_pal_group[palette_id];
863 LOG_DEBUG(
"Room",
"RenderRoomGraphics: Palette ID=%d, Size=%zu", palette_id,
865 if (!bg1_palette.empty()) {
866 LOG_DEBUG(
"Room",
"RenderRoomGraphics: First color: R=%d G=%d B=%d",
867 bg1_palette[0].rom_color().red, bg1_palette[0].rom_color().green,
868 bg1_palette[0].rom_color().blue);
875 if (bg1_palette.size() > 0) {
876 std::optional<gfx::SnesPalette> hud_palette_storage;
880 hud_palette = &*hud_palette_storage;
904 SDL_SetColorKey(bmp.
surface(), SDL_TRUE, 255);
905 SDL_SetSurfaceBlendMode(bmp.
surface(), SDL_BLENDMODE_BLEND);
909 set_dungeon_palette(bg1_bmp, bg1_palette);
910 set_dungeon_palette(bg2_bmp, bg1_palette);
915 auto* surface = bg1_bmp.surface();
920 "Room::RenderRoomGraphics (BG1)", palette_id,
true);
924 "Room::RenderRoomGraphics (after SetPalette)", surface);
927 "Room::RenderRoomGraphics", palette_id,
false,
928 "SDL surface has no palette!");
942 if (bg2_bmp.surface()) {
943 SDL_SetSurfaceAlphaMod(bg2_bmp.surface(), 128);
951 if (bg2_bmp.surface()) {
952 SDL_SetSurfaceBlendMode(bg2_bmp.surface(), SDL_BLENDMODE_ADD);
968 if (bitmap->texture()) {
974 release_texture(&bg1_bmp);
975 release_texture(&bg2_bmp);
990 "Texture commands queued for batch processing");
998 LOG_DEBUG(
"Room",
"ROM not loaded, aborting");
1005 if (!layout_status.ok()) {
1007 layout_status.message().data());
1013 layout_objects.size());
1014 if (layout_objects.empty()) {
1022 LOG_DEBUG(
"RenderRoomGraphics",
"GameData not set, cannot render layout");
1028 if (dungeon_pal_group.empty())
1032 auto room_palette = dungeon_pal_group[palette_id];
1046 "RenderRoomGraphics",
"Layout Draw failed: %s",
1047 std::string(status.message().data(), status.message().size()).c_str());
1049 LOG_DEBUG(
"RenderRoomGraphics",
"Layout rendered with %zu objects",
1050 layout_objects.size());
1055 LOG_DEBUG(
"[RenderObjectsToBackground]",
1056 "Starting object rendering for room %d",
room_id_);
1059 LOG_DEBUG(
"[RenderObjectsToBackground]",
"ROM not loaded, aborting");
1068 bool bitmaps_exist = bg1_bmp.is_active() && bg1_bmp.width() > 0 &&
1069 bg2_bmp.is_active() && bg2_bmp.width() > 0;
1072 LOG_DEBUG(
"[RenderObjectsToBackground]",
1073 "Room %d: Objects not dirty, skipping render",
room_id_);
1079 LOG_DEBUG(
"[RenderObjectsToBackground]",
1080 "Room %d: Emulator rendering objects",
room_id_);
1085 if (dungeon_pal_group.empty())
1089 auto room_palette = dungeon_pal_group[palette_id];
1127 int layer0_count = 0, layer1_count = 0, layer2_count = 0;
1129 switch (obj.GetLayerValue()) {
1143 "Room %03X Object Stream Summary: Main=%d, BG2Overlay=%d, BG1Overlay=%d",
1144 room_id_, layer0_count, layer1_count, layer2_count);
1159 std::vector<std::vector<RoomObject>> by_list(3);
1171 uint8_t list_index = obj.GetLayerValue();
1172 if (list_index > 2) {
1177 by_list[list_index].push_back(std::move(render_obj));
1180 absl::Status status = absl::OkStatus();
1181 bool reset_chest_for_next_chunk =
true;
1182 for (
int pass = 0; pass < 3; ++pass) {
1183 if (by_list[pass].empty()) {
1189 reset_chest_for_next_chunk =
false;
1190 if (!chunk_status.ok() && status.ok()) {
1191 status = chunk_status;
1198 for (
int i = 0; i < static_cast<int>(
doors_.size()); ++i) {
1199 const auto& door =
doors_[i];
1201 door_def.
type = door.type;
1218 if (pot_item.item != 0) {
1220 int tile_x = pot_item.GetTileX();
1221 int tile_y = pot_item.GetTileY();
1228 for (
const auto& sprite :
sprites_) {
1229 if (sprite.key_drop() > 0) {
1235 uint8_t key_item = (sprite.key_drop() == 1) ? 0xFD : 0xFE;
1245 constexpr uint16_t kRoomDrawObj_PushableBlock = 0x0E52;
1246 constexpr uint16_t kRoomDrawObj_TorchUnlit = 0x0EC2;
1247 constexpr uint16_t kRoomDrawObj_TorchLit = 0x0ECA;
1251 static_cast<uint16_t
>(obj.id_), obj.x_, obj.y_, obj.layer_,
1256 const uint16_t off =
1257 obj.lit_ ? kRoomDrawObj_TorchLit : kRoomDrawObj_TorchUnlit;
1259 static_cast<uint16_t
>(obj.id_), obj.x_, obj.y_, obj.layer_, off,
1267 "[RenderObjectsToBackground]",
1268 "Room %03X: ObjectDrawer failed: %s (objects left dirty for retry)",
1270 std::string(status.message().data(), status.message().size()).c_str());
1277 LOG_DEBUG(
"[RenderObjectsToBackground]",
1278 "Room %d: Objects rendered successfully",
room_id_);
1294 if (gfx_buffer_data->empty()) {
1298 auto rom_data =
rom()->vector();
1299 if (rom_data.empty()) {
1304 if (animated_frame_ < 0 || animated_frame_ > 10) {
1309 if (background_tileset_ < 0 || background_tileset_ > 255) {
1314 if (gfx_ptr < 0 || gfx_ptr >=
static_cast<int>(rom_data.size())) {
1319 while (data < 1024) {
1323 if (first_offset >= 0 &&
1324 first_offset <
static_cast<int>(gfx_buffer_data->size())) {
1325 uint8_t map_byte = (*gfx_buffer_data)[first_offset];
1328 int gfx_offset = data + (7 * 4096);
1329 if (gfx_offset >= 0 &&
1339 if (second_offset >= 0 &&
1340 second_offset <
static_cast<int>(gfx_buffer_data->size())) {
1341 uint8_t map_byte = (*gfx_buffer_data)[second_offset];
1344 int gfx_offset = data + (7 * 4096) - 1024;
1345 if (gfx_offset >= 0 &&
1357 auto rom_data =
rom()->vector();
1363 object_pointer =
SnesToPc(object_pointer);
1366 if (object_pointer < 0 || object_pointer >= (
int)
rom_->
size()) {
1370 int room_address = object_pointer + (
room_id_ * 3);
1373 if (room_address < 0 || room_address + 2 >= (
int)
rom_->
size()) {
1377 int tile_address = (rom_data[room_address + 2] << 16) +
1378 (rom_data[room_address + 1] << 8) + rom_data[room_address];
1380 int objects_location =
SnesToPc(tile_address);
1383 if (objects_location < 0 || objects_location >= (
int)
rom_->
size()) {
1388 if (objects_location + 1 < (
int)
rom_->
size()) {
1391 static_cast<uint8_t
>(rom_data[objects_location] & 0x0F);
1393 static_cast<uint8_t
>((rom_data[objects_location] >> 4) & 0x0F);
1395 "Room %d: Set floor1_graphics_=%d, floor2_graphics_=%d",
1400 static_cast<uint8_t
>((rom_data[objects_location + 1] >> 2) & 0x07);
1422 auto rom_data =
rom()->vector();
1428 int nbr_of_staircase = 0;
1430 int pos = objects_location;
1436 bool end_read =
false;
1440 while (!end_read && pos < (
int)
rom_->
size()) {
1442 if (pos + 1 >= (
int)
rom_->
size()) {
1447 b2 = rom_data[pos + 1];
1454 if (b1 == 0xFF && b2 == 0xFF) {
1458 "Room",
"Room %03X: Object list transition to index %d (%s)",
1460 layer == 1 ?
"BG2 overlay" : (layer == 2 ?
"BG1 overlay" :
"END"));
1470 if (b1 == 0xF0 && b2 == 0xFF) {
1477 if (pos + 2 >= (
int)
rom_->
size()) {
1481 b3 = rom_data[pos + 2];
1492 b1, b2, b3,
static_cast<uint8_t
>(layer));
1494 LOG_DEBUG(
"Room",
"Room %03X: Object 0x%03X at (%d,%d) layer=%d (%s)",
1496 layer == 0 ?
"BG1" : (layer == 1 ?
"BG2" :
"BG3"));
1500 if (r.
id_ >= 0 && r.
id_ <= 0xFFF) {
1514 "ParseDoor: room=%d b1=0x%02X b2=0x%02X pos=%d dir=%d type=%d",
1516 static_cast<int>(door.direction),
static_cast<int>(door.type));
1527 std::vector<uint8_t> bytes;
1531 std::vector<RoomObject> layer0_objects;
1532 std::vector<RoomObject> layer1_objects;
1533 std::vector<RoomObject> layer2_objects;
1546 switch (obj.GetLayerValue()) {
1548 layer0_objects.push_back(obj);
1551 layer1_objects.push_back(obj);
1554 layer2_objects.push_back(obj);
1570 for (
const auto& obj : layer0_objects) {
1571 auto encoded = obj.EncodeObjectToBytes();
1572 bytes.push_back(encoded.b1);
1573 bytes.push_back(encoded.b2);
1574 bytes.push_back(encoded.b3);
1576 bytes.push_back(0xFF);
1577 bytes.push_back(0xFF);
1580 for (
const auto& obj : layer1_objects) {
1581 auto encoded = obj.EncodeObjectToBytes();
1582 bytes.push_back(encoded.b1);
1583 bytes.push_back(encoded.b2);
1584 bytes.push_back(encoded.b3);
1586 bytes.push_back(0xFF);
1587 bytes.push_back(0xFF);
1590 for (
const auto& obj : layer2_objects) {
1591 auto encoded = obj.EncodeObjectToBytes();
1592 bytes.push_back(encoded.b1);
1593 bytes.push_back(encoded.b2);
1594 bytes.push_back(encoded.b3);
1599 bytes.push_back(0xF0);
1600 bytes.push_back(0xFF);
1601 for (
const auto& door :
doors_) {
1602 auto [b1, b2] = door.EncodeBytes();
1603 bytes.push_back(b1);
1604 bytes.push_back(b2);
1608 bytes.push_back(0xFF);
1609 bytes.push_back(0xFF);
1615 std::vector<uint8_t> bytes;
1617 for (
const auto& sprite :
sprites_) {
1625 b2 = (sprite.x() & 0x1F) | ((sprite.subtype() & 0x07) << 5);
1630 b1 = (sprite.y() & 0x1F) | ((sprite.subtype() & 0x18) << 2) |
1631 ((sprite.layer() & 0x01) << 7);
1633 bytes.push_back(b1);
1634 bytes.push_back(b2);
1635 bytes.push_back(b3);
1639 bytes.push_back(0xFF);
1649 const auto& rom_data = rom->
vector();
1650 int sprite_pointer = 0;
1651 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
1655 const int hard_end =
1657 if (hard_end <= 0) {
1662 std::unordered_set<int> visited_addresses;
1664 int sprite_address =
1665 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
1666 if (sprite_address < kSpritesData || sprite_address >= hard_end) {
1669 if (!visited_addresses.insert(sprite_address).second) {
1674 MeasureSpriteStreamSize(rom_data, sprite_address, hard_end);
1675 int stream_end = sprite_address + stream_size;
1676 if (stream_end > max_used) {
1677 max_used = stream_end;
1685 const std::vector<uint8_t>& encoded_bytes) {
1687 return absl::InvalidArgumentError(
"ROM not loaded");
1690 return absl::OutOfRangeError(
"Room ID out of range");
1692 if (encoded_bytes.empty() || encoded_bytes.back() != 0xFF ||
1693 (encoded_bytes.size() % 3) != 1) {
1694 return absl::InvalidArgumentError(
1695 "Encoded sprite payload must be N*3 bytes plus 0xFF terminator");
1698 const auto& rom_data = rom->
vector();
1699 int sprite_pointer = 0;
1702 int old_sprite_address =
1703 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
1704 if (old_sprite_address < 0 ||
1705 old_sprite_address >=
static_cast<int>(rom_data.size())) {
1706 return absl::OutOfRangeError(
"Sprite address out of range");
1709 const int hard_end =
1711 const int old_stream_size =
1712 MeasureSpriteStreamSize(rom_data, old_sprite_address, hard_end);
1713 const uint8_t sort_mode = rom_data[old_sprite_address];
1714 const bool old_pointer_shared = IsSpritePointerShared(
1715 rom_data, sprite_pointer, room_id, old_sprite_address);
1718 const size_t required_size = 1u + encoded_bytes.size();
1720 static_cast<size_t>(write_pos) + required_size >
1722 return absl::ResourceExhaustedError(absl::StrFormat(
1723 "Not enough sprite data space. Need %d bytes at 0x%06X, "
1724 "region ends at 0x%06X",
1727 if (
static_cast<size_t>(write_pos) + required_size > rom_data.size()) {
1728 const int required_end = write_pos +
static_cast<int>(required_size);
1729 return absl::OutOfRangeError(
1730 absl::StrFormat(
"ROM too small for sprite relocation write (need "
1731 "end=0x%06X, size=0x%06X)",
1732 required_end,
static_cast<int>(rom_data.size())));
1735 std::vector<uint8_t> relocated;
1736 relocated.reserve(required_size);
1737 relocated.push_back(sort_mode);
1738 relocated.insert(relocated.end(), encoded_bytes.begin(), encoded_bytes.end());
1741 const uint32_t snes_addr =
PcToSnes(write_pos);
1742 const int ptr_off = sprite_pointer + (room_id * 2);
1746 if (!old_pointer_shared && old_stream_size > 0 &&
1747 old_sprite_address + old_stream_size <=
1748 static_cast<int>(rom_data.size())) {
1750 old_sprite_address, std::vector<uint8_t>(old_stream_size, 0x00)));
1753 return absl::OkStatus();
1757 if (
rom_ ==
nullptr) {
1758 return absl::InvalidArgumentError(
"ROM pointer is null");
1761 auto rom_data =
rom()->vector();
1767 object_pointer =
SnesToPc(object_pointer);
1769 if (object_pointer < 0 || object_pointer >= (
int)
rom_->
size()) {
1770 return absl::OutOfRangeError(
"Object pointer out of range");
1773 int room_address = object_pointer + (
room_id_ * 3);
1775 if (room_address < 0 || room_address + 2 >= (
int)
rom_->
size()) {
1776 return absl::OutOfRangeError(
"Room address out of range");
1779 int tile_address = (rom_data[room_address + 2] << 16) +
1780 (rom_data[room_address + 1] << 8) + rom_data[room_address];
1782 int objects_location =
SnesToPc(tile_address);
1784 if (objects_location < 0 || objects_location >= (
int)
rom_->
size()) {
1785 return absl::OutOfRangeError(
"Objects location out of range");
1790 int available_size = room_size_info.
room_size;
1793 int write_pos = objects_location + 2;
1800 if (encoded_bytes.size() > available_size - 2) {
1801 return absl::OutOfRangeError(absl::StrFormat(
1802 "Room %d object data too large! Size: %d, Available: %d",
room_id_,
1803 encoded_bytes.size(), available_size - 2));
1810 const int door_list_offset =
static_cast<int>(encoded_bytes.size()) -
1811 static_cast<int>(
doors_.size()) * 2 - 2;
1812 const int door_pointer_pc = write_pos + door_list_offset;
1815 static_cast<uint32_t
>(
PcToSnes(door_pointer_pc))));
1817 return absl::OkStatus();
1821 if (
rom_ ==
nullptr) {
1822 return absl::InvalidArgumentError(
"ROM pointer is null");
1825 const auto& rom_data =
rom()->vector();
1827 return absl::OutOfRangeError(
"Room ID out of range");
1830 int sprite_pointer = 0;
1832 int sprite_address =
1833 ReadRoomSpriteAddressPc(rom_data, sprite_pointer,
room_id_);
1834 if (sprite_address < 0 || sprite_address >=
static_cast<int>(
rom_->
size())) {
1835 return absl::OutOfRangeError(
"Sprite address out of range");
1838 int available_payload_size = 0;
1840 int next_sprite_address =
1841 ReadRoomSpriteAddressPc(rom_data, sprite_pointer,
room_id_ + 1);
1842 if (next_sprite_address >= 0) {
1843 int available_size = next_sprite_address - sprite_address;
1844 if (available_size > 0 && available_size <= 0x1000) {
1845 available_payload_size = std::max(0, available_size - 1);
1850 if (available_payload_size == 0) {
1851 const int hard_end =
1853 const int current_stream_size =
1854 MeasureSpriteStreamSize(rom_data, sprite_address, hard_end);
1855 available_payload_size = std::max(0, current_stream_size - 1);
1858 const int payload_address = sprite_address + 1;
1859 if (payload_address < 0 ||
1860 payload_address >=
static_cast<int>(
rom_->
size())) {
1861 return absl::OutOfRangeError(absl::StrFormat(
1862 "Room %d has invalid sprite payload address",
room_id_));
1866 if (
static_cast<int>(encoded_bytes.size()) > available_payload_size) {
1874 if (
rom_ ==
nullptr) {
1875 return absl::InvalidArgumentError(
"ROM pointer is null");
1878 const auto& rom_data =
rom()->vector();
1881 return absl::OutOfRangeError(
"Room header pointer out of range");
1885 return absl::OutOfRangeError(
"Room header pointer bank out of range");
1891 header_pointer =
SnesToPc(header_pointer);
1893 int table_offset = header_pointer + (
room_id_ * 2);
1894 if (table_offset < 0 ||
1895 table_offset + 1 >=
static_cast<int>(rom_data.size())) {
1896 return absl::OutOfRangeError(
"Room header table offset out of range");
1900 (rom_data[table_offset + 1] << 8) + rom_data[table_offset];
1901 int header_location =
SnesToPc(address);
1903 if (header_location < 0 ||
1904 header_location + 13 >=
static_cast<int>(rom_data.size())) {
1905 return absl::OutOfRangeError(
"Room header location out of range");
1909 uint8_t byte0 = (
static_cast<uint8_t
>(
bg2()) << 5) |
1910 (
static_cast<uint8_t
>(
collision()) << 2) |
1936 if (msg_addr < 0 || msg_addr + 1 >=
static_cast<int>(rom_data.size())) {
1937 return absl::OutOfRangeError(
"Message ID address out of range");
1941 return absl::OkStatus();
1951 return absl::InvalidArgumentError(
"Invalid object parameters");
1959 return absl::OkStatus();
1964 return absl::OutOfRangeError(
"Object index out of range");
1971 return absl::OkStatus();
1976 return absl::OutOfRangeError(
"Object index out of range");
1980 return absl::InvalidArgumentError(
"Invalid object parameters");
1987 return absl::OkStatus();
1993 if (obj.x() == x && obj.y() == y && obj.GetLayerValue() == layer) {
1997 return absl::NotFoundError(
"No object found at position");
2002 if (
object.x() < 0 ||
object.x() > 63)
2004 if (
object.y() < 0 ||
object.y() > 63)
2008 if (
object.GetLayerValue() < 0 ||
object.GetLayerValue() > 2)
2012 if (
object.id_ < 0 || object.id_ > 0xFFF)
2016 if (
object.id_ < 0x100 &&
object.size() > 15)
2023 int& nbr_of_staircase) {
2027 if (nbr_of_staircase < 4) {
2050 }
else if (oid == 0xFB1) {
2060 const auto& rom_data =
rom()->vector();
2068 int sprite_pointer = 0;
2069 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
2073 int sprite_address =
2074 ReadRoomSpriteAddressPc(rom_data, sprite_pointer,
room_id_);
2075 if (sprite_address < 0 ||
2076 sprite_address + 1 >=
static_cast<int>(rom_data.size())) {
2081 sprite_address += 1;
2083 while (sprite_address + 2 <
static_cast<int>(rom_data.size())) {
2084 uint8_t b1 = rom_data[sprite_address];
2085 uint8_t b2 = rom_data[sprite_address + 1];
2086 uint8_t b3 = rom_data[sprite_address + 2];
2092 sprites_.emplace_back(b3, (b2 & 0x1F), (b1 & 0x1F),
2093 ((b2 & 0xE0) >> 5) + ((b1 & 0x60) >> 2),
2100 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1E &&
2106 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1D &&
2113 sprite_address += 3;
2120 auto rom_data =
rom()->vector();
2128 for (
size_t i = 0; i < clength; i++) {
2129 if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
2133 if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
2134 0x8000) == 0x8000) {
2139 chest_data{rom_data[cpos + (i * 3) + 2], big});
2145 auto rom_data =
rom()->vector();
2156 "LoadDoors for room %d - doors are loaded via object stream",
2161 auto rom_data =
rom()->vector();
2174 return (obj.options() & ObjectOption::Torch) !=
2175 ObjectOption::Nothing;
2180 for (
int i = 0; i < bytes_count; i += 2) {
2181 if (i + 1 >= bytes_count)
2188 if (b1 == 0xFF && b2 == 0xFF) {
2193 uint16_t torch_room_id = (b2 << 8) | b1;
2197 while (i < bytes_count) {
2198 if (i + 1 >= bytes_count)
2205 if (b1 == 0xFF && b2 == 0xFF) {
2210 int address = ((b2 & 0x1F) << 8 | b1) >> 1;
2211 uint8_t px = address % 64;
2212 uint8_t py = address >> 6;
2213 uint8_t layer = (b2 & 0x20) >> 5;
2214 bool lit = (b2 & 0x80) == 0x80;
2217 RoomObject torch_obj(0x150, px, py, 0, layer);
2220 torch_obj.
lit_ = lit;
2224 LOG_DEBUG(
"Room",
"Loaded torch at (%d,%d) layer=%d lit=%d", px, py,
2233 while (i < bytes_count) {
2234 if (i + 1 >= bytes_count)
2238 if (b1 == 0xFF && b2 == 0xFF) {
2253 const std::vector<uint8_t>& rom_data,
int bytes_count) {
2259 if (b1 == 0xFF && b2 == 0xFF) {
2263 uint16_t room_id = (b2 << 8) | b1;
2268 std::vector<uint8_t> seg;
2275 if (b1 == 0xFF && b2 == 0xFF) {
2276 seg.push_back(0xFF);
2277 seg.push_back(0xFF);
2285 if (room_id < segments.size()) {
2286 segments[room_id] = std::move(seg);
2294template <
typename RoomLookup>
2296 RoomLookup&& room_lookup) {
2298 return absl::InvalidArgumentError(
"ROM not loaded");
2301 bool any_torch_objects =
false;
2302 for (
int room_id = 0; room_id < room_count; ++room_id) {
2303 const Room* room = room_lookup(room_id);
2304 if (room ==
nullptr) {
2309 any_torch_objects =
true;
2313 if (any_torch_objects) {
2317 if (!any_torch_objects) {
2318 return absl::OkStatus();
2321 const auto& rom_data = rom->
vector();
2324 if (existing_count > kTorchesMaxSize) {
2325 existing_count = kTorchesMaxSize;
2327 auto rom_segments = ParseRomTorchSegments(rom_data, existing_count);
2329 std::vector<uint8_t> bytes;
2330 const int room_limit =
2331 std::min(room_count,
static_cast<int>(rom_segments.size()));
2332 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2333 const Room* room = room_lookup(room_id);
2334 bool has_torch_objects =
false;
2335 if (room !=
nullptr) {
2338 has_torch_objects =
true;
2343 if (has_torch_objects) {
2344 bytes.push_back(room_id & 0xFF);
2345 bytes.push_back((room_id >> 8) & 0xFF);
2350 int address = obj.x() + (obj.y() * 64);
2351 int word = address << 1;
2352 uint8_t b1 = word & 0xFF;
2353 uint8_t b2 = ((word >> 8) & 0x1F) | ((obj.GetLayerValue() & 1) << 5);
2357 bytes.push_back(b1);
2358 bytes.push_back(b2);
2360 bytes.push_back(0xFF);
2361 bytes.push_back(0xFF);
2362 }
else if (!rom_segments[room_id].empty()) {
2363 for (uint8_t b : rom_segments[room_id]) {
2369 if (bytes.size() > kTorchesMaxSize) {
2370 return absl::ResourceExhaustedError(
2371 absl::StrFormat(
"Torch data too large: %d bytes (max %d)", bytes.size(),
2375 const uint16_t current_len =
2378 if (current_len == bytes.size() &&
2379 kTorchData +
static_cast<int>(bytes.size()) <=
2380 static_cast<int>(rom_data.size()) &&
2381 std::equal(bytes.begin(), bytes.end(), rom_data.begin() +
kTorchData)) {
2382 return absl::OkStatus();
2386 static_cast<uint16_t
>(bytes.size())));
2392 [&rooms](
int room_id) { return &rooms[room_id]; });
2396 Rom* rom,
int room_count,
2397 const std::function<
const Room*(
int)>& room_lookup) {
2403 return absl::InvalidArgumentError(
"ROM not loaded");
2405 const auto& rom_data = rom->
vector();
2406 if (kPitCount < 0 || kPitCount >=
static_cast<int>(rom_data.size()) ||
2407 kPitPointer + 2 >=
static_cast<int>(rom_data.size())) {
2408 return absl::OutOfRangeError(
"Pit count/pointer out of range");
2410 int pit_count_byte = rom_data[
kPitCount];
2411 int pit_entries = pit_count_byte / 2;
2412 if (pit_entries <= 0) {
2413 return absl::OkStatus();
2415 int pit_ptr_snes = (rom_data[
kPitPointer + 2] << 16) |
2417 int pit_data_pc =
SnesToPc(pit_ptr_snes);
2418 int data_len = pit_entries * 2;
2419 if (pit_data_pc < 0 ||
2420 pit_data_pc + data_len >
static_cast<int>(rom_data.size())) {
2421 return absl::OutOfRangeError(
"Pit data region out of range");
2423 std::vector<uint8_t> data(rom_data.begin() + pit_data_pc,
2424 rom_data.begin() + pit_data_pc + data_len);
2434 return absl::InvalidArgumentError(
"ROM not loaded");
2436 const auto& rom_data = rom->
vector();
2437 if (
kBlocksLength + 1 >=
static_cast<int>(rom_data.size())) {
2438 return absl::OutOfRangeError(
"Blocks length out of range");
2442 if (blocks_count <= 0) {
2443 return absl::OkStatus();
2445 const int kRegionSize = 0x80;
2448 for (
int r = 0; r < 4; ++r) {
2449 if (ptrs[r] + 2 >=
static_cast<int>(rom_data.size())) {
2450 return absl::OutOfRangeError(
"Blocks pointer out of range");
2452 int snes = (rom_data[ptrs[r] + 2] << 16) | (rom_data[ptrs[r] + 1] << 8) |
2455 int off = r * kRegionSize;
2456 int len = std::min(kRegionSize, blocks_count - off);
2459 if (pc < 0 || pc + len >
static_cast<int>(rom_data.size())) {
2460 return absl::OutOfRangeError(
"Blocks data region out of range");
2462 std::vector<uint8_t> chunk(rom_data.begin() + pc,
2463 rom_data.begin() + pc + len);
2468 return absl::OkStatus();
2471template <
typename RoomLookup>
2473 RoomLookup&& room_lookup) {
2475 return absl::InvalidArgumentError(
"ROM not loaded");
2482 const auto& rom_data = rom->
vector();
2487 if (!has_ptr_table) {
2488 for (
int room_id = 0; room_id < room_count; ++room_id) {
2489 Room* room = room_lookup(room_id);
2491 return absl::FailedPreconditionError(
2492 "Custom collision region not present in this ROM");
2495 return absl::OkStatus();
2498 if (!has_data_region) {
2499 for (
int room_id = 0; room_id < room_count; ++room_id) {
2500 Room* room = room_lookup(room_id);
2502 return absl::FailedPreconditionError(
2503 "Custom collision data region not present in this ROM");
2506 return absl::OkStatus();
2515 "CustomCollisionPointers"));
2519 "CustomCollisionData"));
2523 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2524 Room* room = room_lookup(room_id);
2529 const int actual_room_id = room->
id();
2531 if (ptr_offset + 2 >=
static_cast<int>(rom_data.size())) {
2532 return absl::OutOfRangeError(
"Custom collision pointer out of range");
2565 return absl::OkStatus();
2570 rom,
static_cast<int>(rooms.size()),
2571 [&rooms](
int room_id) { return &rooms[room_id]; });
2575 const std::function<
Room*(
int)>& room_lookup) {
2583 const std::vector<uint8_t>& rom_data,
int cpos,
int clength) {
2584 std::vector<std::vector<std::pair<uint8_t, bool>>> per_room(
kNumberOfRooms);
2586 i < clength && cpos + i * 3 + 2 < static_cast<int>(rom_data.size());
2588 int off = cpos + i * 3;
2589 uint16_t word = (rom_data[off + 1] << 8) | rom_data[off];
2590 uint16_t room_id = word & 0x7FFF;
2591 bool big = (word & 0x8000) != 0;
2592 uint8_t
id = rom_data[off + 2];
2594 per_room[room_id].emplace_back(
id, big);
2601 const std::vector<uint8_t>& rom_data) {
2604 if (table_addr + (
kNumberOfRooms * 2) >
static_cast<int>(rom_data.size())) {
2608 int ptr_off = table_addr + (room_id * 2);
2609 uint16_t item_ptr = (rom_data[ptr_off + 1] << 8) | rom_data[ptr_off];
2610 int item_addr =
SnesToPc(0x010000 | item_ptr);
2611 if (item_addr < 0 || item_addr >=
static_cast<int>(rom_data.size())) {
2614 int next_ptr_off = table_addr + ((room_id + 1) * 2);
2615 int next_item_addr =
2617 ?
SnesToPc(0x010000 | ((rom_data[next_ptr_off + 1] << 8) |
2618 rom_data[next_ptr_off]))
2619 : item_addr + 0x100;
2620 int max_len = next_item_addr - item_addr;
2624 std::vector<uint8_t> bytes;
2625 int cursor = item_addr;
2627 std::min(item_addr + max_len,
static_cast<int>(rom_data.size()));
2628 while (cursor + 1 < limit) {
2629 uint8_t b1 = rom_data[cursor++];
2630 uint8_t b2 = rom_data[cursor++];
2631 bytes.push_back(b1);
2632 bytes.push_back(b2);
2633 if (b1 == 0xFF && b2 == 0xFF) {
2636 if (cursor >= limit) {
2639 bytes.push_back(rom_data[cursor++]);
2641 if (!bytes.empty()) {
2642 per_room[room_id] = std::move(bytes);
2650template <
typename RoomLookup>
2652 RoomLookup&& room_lookup) {
2654 return absl::InvalidArgumentError(
"ROM not loaded");
2656 const auto& rom_data = rom->
vector();
2659 return absl::OutOfRangeError(
"Chest pointers out of range");
2666 auto rom_chests = ParseRomChests(rom_data, cpos, clength);
2668 std::vector<uint8_t> bytes;
2669 const int room_limit =
2670 std::min(room_count,
static_cast<int>(rom_chests.size()));
2671 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2672 const Room* room = room_lookup(room_id);
2673 const auto* chests = room !=
nullptr ? &room->
GetChests() :
nullptr;
2674 if (chests ==
nullptr || chests->empty()) {
2675 for (
const auto& [
id, big] : rom_chests[room_id]) {
2676 uint16_t word = room_id | (big ? 0x8000 : 0);
2677 bytes.push_back(word & 0xFF);
2678 bytes.push_back((word >> 8) & 0xFF);
2679 bytes.push_back(
id);
2682 for (
const auto& c : *chests) {
2683 uint16_t word = room_id | (c.size ? 0x8000 : 0);
2684 bytes.push_back(word & 0xFF);
2685 bytes.push_back((word >> 8) & 0xFF);
2686 bytes.push_back(c.id);
2691 if (cpos < 0 || cpos +
static_cast<int>(bytes.size()) >
2692 static_cast<int>(rom_data.size())) {
2693 return absl::OutOfRangeError(
"Chest data region out of range");
2696 static_cast<uint16_t
>(bytes.size() / 3)));
2702 [&rooms](
int room_id) { return &rooms[room_id]; });
2706 const std::function<
const Room*(
int)>& room_lookup) {
2710template <
typename RoomLookup>
2712 RoomLookup&& room_lookup) {
2714 return absl::InvalidArgumentError(
"ROM not loaded");
2716 const auto& rom_data = rom->
vector();
2717 const auto rom_pot_items = ParseRomPotItems(rom_data);
2719 if (table_addr + (
kNumberOfRooms * 2) >
static_cast<int>(rom_data.size())) {
2720 return absl::OutOfRangeError(
"Room items pointer table out of range");
2723 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2724 const Room* room = room_lookup(room_id);
2725 const bool room_loaded = room !=
nullptr && room->
IsLoaded();
2726 const auto* pot_items = room !=
nullptr ? &room->
GetPotItems() :
nullptr;
2727 int ptr_off = table_addr + (room_id * 2);
2728 uint16_t item_ptr = (rom_data[ptr_off + 1] << 8) | rom_data[ptr_off];
2729 int item_addr =
SnesToPc(0x010000 | item_ptr);
2730 if (item_addr < 0 || item_addr + 2 >=
static_cast<int>(rom_data.size())) {
2733 int next_ptr_off = table_addr + ((room_id + 1) * 2);
2734 int next_item_addr =
2736 ?
SnesToPc(0x010000 | ((rom_data[next_ptr_off + 1] << 8) |
2737 rom_data[next_ptr_off]))
2738 : item_addr + 0x100;
2739 int max_len = next_item_addr - item_addr;
2742 std::vector<uint8_t> bytes;
2744 if (room_id < rom_pot_items.size()) {
2745 bytes = rom_pot_items[room_id];
2747 if (bytes.empty()) {
2751 for (
const auto& pi : *pot_items) {
2752 bytes.push_back(pi.position & 0xFF);
2753 bytes.push_back((pi.position >> 8) & 0xFF);
2754 bytes.push_back(pi.item);
2756 bytes.push_back(0xFF);
2757 bytes.push_back(0xFF);
2759 if (
static_cast<int>(bytes.size()) > max_len) {
2764 return absl::OkStatus();
2769 [&rooms](
int room_id) { return &rooms[room_id]; });
2773 Rom* rom,
int room_count,
2774 const std::function<
const Room*(
int)>& room_lookup) {
2779 auto rom_data =
rom()->vector();
2792 return (obj.options() & ObjectOption::Block) !=
2793 ObjectOption::Nothing;
2798 std::vector<uint8_t> blocks_data(blocks_count);
2806 for (
int i = 0; i < 0x80 && i < blocks_count; i++) {
2807 blocks_data[i] = rom_data[pos1 + i];
2809 if (i + 0x80 < blocks_count) {
2810 blocks_data[i + 0x80] = rom_data[pos2 + i];
2812 if (i + 0x100 < blocks_count) {
2813 blocks_data[i + 0x100] = rom_data[pos3 + i];
2815 if (i + 0x180 < blocks_count) {
2816 blocks_data[i + 0x180] = rom_data[pos4 + i];
2821 for (
int i = 0; i < blocks_count; i += 4) {
2822 if (i + 3 >= blocks_count)
2825 uint8_t b1 = blocks_data[i];
2826 uint8_t b2 = blocks_data[i + 1];
2827 uint8_t b3 = blocks_data[i + 2];
2828 uint8_t b4 = blocks_data[i + 3];
2831 uint16_t block_room_id = (b2 << 8) | b1;
2834 if (b3 == 0xFF && b4 == 0xFF) {
2839 int address = ((b4 & 0x1F) << 8 | b3) >> 1;
2840 uint8_t px = address % 64;
2841 uint8_t py = address >> 6;
2842 uint8_t layer = (b4 & 0x20) >> 5;
2845 RoomObject block_obj(0x0E00, px, py, 0, layer);
2851 LOG_DEBUG(
"Room",
"Loaded block at (%d,%d) layer=%d", px, py, layer);
2859 auto rom_data =
rom()->vector();
2875 int ptr_addr = table_addr + (
room_id_ * 2);
2876 if (ptr_addr + 1 >=
static_cast<int>(rom_data.size()))
2879 uint16_t item_ptr = (rom_data[ptr_addr + 1] << 8) | rom_data[ptr_addr];
2882 int item_addr =
SnesToPc(0x010000 | item_ptr);
2885 while (item_addr + 2 <
static_cast<int>(rom_data.size())) {
2887 uint16_t position = (rom_data[item_addr + 1] << 8) | rom_data[item_addr];
2890 if (position == 0xFFFF)
2894 uint8_t item_type = rom_data[item_addr + 2];
2898 pot_item.
item = item_type;
2908 auto rom_data =
rom()->vector();
2911 int pit_entries = rom_data[
kPitCount] / 2;
2916 int pit_data_addr =
SnesToPc(pit_ptr);
2918 LOG_DEBUG(
"Room",
"LoadPits: room_id=%d, pit_entries=%d, pit_ptr=0x%06X",
2928 LOG_DEBUG(
"Room",
"Pit destination - target=%d, target_layer=%d",
2943 for (
const auto& sprite :
sprites_) {
2944 if (sprite.IsOverlord()) {
2954 for (
const auto& door :
doors_) {
2956 const bool is_special = [&]() ->
bool {
2957 switch (door.type) {
2988 auto options = obj.options();
3001 if (obj.id_ == 0x11E || obj.id_ == 0x11F) {
3006 if (obj.id_ >= 0xF83 && obj.id_ <= 0xF8F) {
3013 if ((obj.id_ >= 0x130 && obj.id_ <= 0x135) || obj.id_ == 0x139 ||
3014 obj.id_ == 0x13A || obj.id_ == 0x13B) {
3018 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::StatusOr< uint16_t > ReadWord(int offset) const
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.
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
SDL_Surface * surface() const
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
constexpr uint16_t snes() const
Get SNES 15-bit color.
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.
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 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, bool reset_chest_index=true)
Draw all objects in a room.
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 SetAllowTrackCornerAliases(bool allow)
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.
uint64_t CompositeStateSignature() const
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.
const CustomCollisionMap & custom_collision() const
absl::Status UpdateObject(size_t index, const RoomObject &object)
void SetStair4Target(uint8_t target)
void SetPitsTarget(uint8_t target)
void SetIsLight(bool is_light)
void EnsureSpritesLoaded()
gfx::BackgroundBuffer bg2_buffer_
const std::vector< chest_data > & GetChests() const
uint8_t cached_floor2_graphics_
std::vector< zelda3::Sprite > sprites_
CustomCollisionMap custom_collision_
void SetLoaded(bool loaded)
void ClearCustomCollisionDirty()
void CopyRoomGraphicsToBuffer()
bool custom_collision_dirty() const
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 PrepareForRender(uint8_t entrance_blockset=0xFF)
void ReloadGraphics(uint8_t entrance_blockset=0xFF)
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 &&)
uint64_t composite_signature_
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)
const std::vector< RoomObject > & GetTileObjects() const
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 EnsureObjectsLoaded()
void SetSpriteset(uint8_t ss)
int ResolveDungeonPaletteId() const
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_
void EnsurePotItemsLoaded()
uint8_t staircase_plane(int index) const
bool has_composite_signature_
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)
bool has_custom_collision() const
const std::vector< PotItem > & GetPotItems() const
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)
bool RoomUsesTrackCornerAliases(const std::vector< RoomObject > &objects)
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)
void PopulateDungeonRenderPaletteRows(const gfx::SnesPalette &dungeon_palette, const gfx::SnesPalette *hud_palette, WriteColor write_color)
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
RoomObject::LayerType MapRoomObjectListIndexToDrawLayer(uint8_t list_index)
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 kDungeonPaletteBytes
constexpr int kSpritesData
void LoadDungeonRenderPaletteToCgram(std::span< uint16_t > cgram, const gfx::SnesPalette &dungeon_palette, const gfx::SnesPalette *hud_palette)
absl::Status SaveAllTorches(Rom *rom, absl::Span< const Room > rooms)
constexpr int kTileAddress
std::vector< SDL_Color > BuildDungeonRenderPalette(const gfx::SnesPalette &dungeon_palette, const gfx::SnesPalette *hud_palette)
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)
absl::Status SaveAllCollisionImpl(Rom *rom, int room_count, RoomLookup &&room_lookup)
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[]
absl::Status SaveAllChestsImpl(Rom *rom, int room_count, RoomLookup &&room_lookup)
absl::Status SaveAllPotItemsImpl(Rom *rom, int room_count, RoomLookup &&room_lookup)
absl::Status SaveAllTorchesImpl(Rom *rom, int room_count, RoomLookup &&room_lookup)
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.
const SnesPalette & palette_ref(int i) const
void AddPalette(SnesPalette pal)
std::array< uint8_t, 64 *64 > tiles
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.