12#include <unordered_map>
13#include <unordered_set>
16#include "absl/strings/str_cat.h"
17#include "absl/strings/str_format.h"
43 return std::any_of(objects.begin(), objects.end(),
44 [](
const RoomObject& obj) { return obj.id_ == 0x31; });
48 return static_cast<uint8_t
>((byte0 >> 5) & 0x07);
52 return (byte0 & 0x01) != 0;
63 return background2::DarkRoom;
68template <
typename WriteColor>
71 WriteColor write_color) {
72 if (hud_palette !=
nullptr) {
73 const size_t hud_count = std::min<size_t>(hud_palette->
size(), 32);
74 for (
size_t i = 0; i < hud_count; ++i) {
75 write_color(
static_cast<int>(i), (*hud_palette)[i]);
79 constexpr int kColorsPerRomBank = 15;
80 constexpr int kIndicesPerSdlBank = 16;
81 constexpr int kNumRomBanks = 6;
82 constexpr int kDungeonBankStart = 2;
83 for (
int rom_bank = 0; rom_bank < kNumRomBanks; ++rom_bank) {
84 const int sdl_bank = rom_bank + kDungeonBankStart;
85 for (
int color = 0; color < kColorsPerRomBank; ++color) {
86 const size_t rom_index =
87 static_cast<size_t>(rom_bank * kColorsPerRomBank + color);
88 if (rom_index >= dungeon_palette.
size()) {
91 const int dst_index = sdl_bank * kIndicesPerSdlBank + color + 1;
92 write_color(dst_index, dungeon_palette[rom_index]);
102 std::vector<SDL_Color> colors(256, {0, 0, 0, 0});
103 PopulateDungeonRenderPaletteRows(
104 dungeon_palette, hud_palette,
106 if (dst_index < 0 || dst_index >=
static_cast<int>(colors.size())) {
109 const ImVec4 rgb = color.
rgb();
110 colors[dst_index] = {
static_cast<Uint8
>(rgb.x),
111 static_cast<Uint8
>(rgb.y),
112 static_cast<Uint8
>(rgb.z), 255};
114 colors[255] = {0, 0, 0, 0};
121 PopulateDungeonRenderPaletteRows(
122 dungeon_palette, hud_palette,
124 if (dst_index < 0 || dst_index >=
static_cast<int>(cgram.size())) {
127 cgram[dst_index] = color.
snes();
138 "Light Torch to See Floor",
143 "NW Kill Enemy to Open",
144 "NE Kill Enemy to Open",
145 "SW Kill Enemy to Open",
146 "SE Kill Enemy to Open",
147 "W Kill Enemy to Open",
148 "E Kill Enemy to Open",
149 "N Kill Enemy to Open",
150 "S Kill Enemy to Open",
151 "Clear Quadrant to Open",
152 "Clear Full Tile to Open",
153 "NW Push Block to Open",
154 "NE Push Block to Open",
155 "SW Push Block to Open",
156 "SE Push Block to Open",
157 "W Push Block to Open",
158 "E Push Block to Open",
159 "N Push Block to Open",
160 "S Push Block to Open",
161 "Push Block to Open",
162 "Pull Lever to Open",
163 "Collect Prize to Open",
164 "Hold Switch Open Door",
165 "Toggle Switch to Open Door",
174 "Push Switch Exploding Wall",
176 "Open Chest (Holes 0)",
179 "Defeat Boss for Dungeon Prize",
180 "SE Kill Enemy to Push Block",
181 "Trigger Switch Chest",
182 "Pull Lever Exploding Wall",
183 "NW Kill Enemy for Chest",
184 "NE Kill Enemy for Chest",
185 "SW Kill Enemy for Chest",
186 "SE Kill Enemy for Chest",
187 "W Kill Enemy for Chest",
188 "E Kill Enemy for Chest",
189 "N Kill Enemy for Chest",
190 "S Kill Enemy for Chest",
191 "Clear Quadrant for Chest",
192 "Clear Full Tile for Chest",
193 "Light Torches to Open",
201 "Open Chest for Holes 8",
202 "Push Block for Chest",
203 "Clear Room for Triforce Door",
204 "Light Torches for Chest",
211 int physical_end = -1;
215 return physical_end > address ? physical_end - address : 0;
221 int known_region_end = -1) {
223 if (room_id < 0 || room_id >=
static_cast<int>(room_addresses.size())) {
227 info.
address = room_addresses[room_id];
232 int next_address = std::numeric_limits<int>::max();
233 for (
int other_room_id = 0;
234 other_room_id < static_cast<int>(room_addresses.size());
236 if (other_room_id == room_id || room_addresses[other_room_id] < 0) {
239 const int other_address = room_addresses[other_room_id];
240 if (other_address == info.
address) {
242 }
else if (other_address > info.
address) {
243 next_address = std::min(next_address, other_address);
247 if (known_region_end > info.
address) {
248 next_address = std::min(next_address, known_region_end);
250 if (next_address == std::numeric_limits<int>::max()) {
258 constexpr int kLoRomBankSize = 0x8000;
259 const int bank_end = ((info.
address / kLoRomBankSize) + 1) * kLoRomBankSize;
260 if (next_address > bank_end) {
269 if (table_pc ==
nullptr) {
270 return absl::InvalidArgumentError(
"table_pc pointer is null");
273 return absl::OutOfRangeError(
274 "Object pointer table address is out of range");
277 const uint32_t table_snes =
281 const int pc =
static_cast<int>(
SnesToPc(table_snes));
282 if (pc < 0 || pc + (
kNumberOfRooms * 3) >
static_cast<int>(rom_data.size())) {
283 return absl::OutOfRangeError(
"Object pointer table is out of range");
287 return absl::OkStatus();
291 int table_pc,
int room_id) {
295 const int ptr_off = table_pc + (room_id * 3);
296 if (ptr_off < 0 || ptr_off + 2 >=
static_cast<int>(rom_data.size())) {
299 return (
static_cast<uint32_t
>(rom_data[ptr_off + 2]) << 16) |
300 (
static_cast<uint32_t
>(rom_data[ptr_off + 1]) << 8) |
307 if ((snes & 0xFFFF) < 0x8000) {
310 const int pc =
static_cast<int>(
SnesToPc(snes));
311 return pc >= 0 && pc < static_cast<int>(rom_data.size()) ? pc : -1;
315 const std::vector<uint8_t>& rom_data,
int room_id) {
317 return absl::OutOfRangeError(
"Room ID out of range");
329 return absl::OutOfRangeError(
"Object stream pointer is out of range");
336 if (table_pc ==
nullptr) {
337 return absl::InvalidArgumentError(
"table_pc pointer is null");
340 return absl::OutOfRangeError(
341 "Sprite pointer table address is out of range");
347 if (pc < 0 || pc + (
kNumberOfRooms * 2) >
static_cast<int>(rom_data.size())) {
348 return absl::OutOfRangeError(
"Sprite pointer table is out of range");
352 return absl::OkStatus();
360 const int ptr_off = table_pc + (room_id * 2);
361 if (ptr_off < 0 || ptr_off + 1 >=
static_cast<int>(rom_data.size())) {
365 const uint16_t pointer =
366 (
static_cast<uint16_t
>(rom_data[ptr_off + 1]) << 8) | rom_data[ptr_off];
367 if (pointer < 0x8000) {
370 const int sprite_address =
static_cast<int>(
SnesToPc((0x09 << 16) | pointer));
371 return sprite_address >= 0 &&
372 sprite_address < static_cast<int>(rom_data.size())
378 const std::vector<uint8_t>& rom_data,
int room_id) {
380 return absl::OutOfRangeError(
"Room ID out of range");
393 return absl::OutOfRangeError(
"Sprite stream pointer is out of range");
399 int sprite_address,
int hard_end) {
400 if (sprite_address < 0 || sprite_address >= hard_end ||
401 sprite_address >=
static_cast<int>(rom_data.size())) {
405 int cursor = sprite_address + 1;
406 while (cursor < hard_end) {
407 if (rom_data[cursor] == 0xFF) {
411 if (cursor + 2 >= hard_end) {
418 return std::max(0, cursor - sprite_address);
424 std::vector<uint8_t> encoded_stream) {
425 if (layout.
kind != expected_kind) {
426 return absl::InvalidArgumentError(absl::StrFormat(
427 "Room %d relocation layout has the wrong dungeon stream kind",
436 std::move(encoded_stream)}}));
443 if (layout.
kind != expected_kind) {
444 return absl::InvalidArgumentError(absl::StrFormat(
445 "Room %d save layout has the wrong dungeon stream kind", room_id));
447 if (room_id < 0 ||
static_cast<uint32_t
>(room_id) >= layout.
pointer_count) {
448 return absl::OutOfRangeError(
449 "Room ID is outside the dungeon stream layout");
454 if (!inventory.
ok()) {
455 return absl::FailedPreconditionError(absl::StrFormat(
456 "Dungeon stream inventory has %zu issue(s); refusing an in-place "
458 inventory.
issues.size()));
461 const auto contains_room = [room_id](
const std::vector<uint32_t>& owners) {
462 return std::find(owners.begin(), owners.end(),
463 static_cast<uint32_t
>(room_id)) != owners.end();
465 for (
const auto& alias : inventory.
aliases) {
466 if (contains_room(alias.room_ids)) {
470 for (
const auto& overlap : inventory.
overlaps) {
471 if (contains_room(overlap.first_room_ids) ||
472 contains_room(overlap.second_room_ids)) {
477 const auto& record = inventory.
streams[room_id];
478 const uint64_t replacement_end =
479 static_cast<uint64_t
>(record.data_pc) + replacement_size;
480 const uint32_t bank_end = ((record.data_pc / 0x8000u) + 1u) * 0x8000u;
481 const bool stays_in_declared_data =
482 replacement_end <= bank_end &&
485 return range.begin <= record.data_pc &&
486 replacement_end <= range.end;
488 return !stays_in_declared_data;
495 if (!rom || !rom->
is_loaded() || rom->
size() == 0 || room_id < 0 ||
500 const auto& rom_data = rom->
vector();
502 if (!GetObjectPointerTablePc(rom_data, &table_pc).ok()) {
505 room_size.room_size_pointer =
506 ReadRoomObjectAddressSnes(rom_data, table_pc, room_id);
508 auto stream_info = GetObjectStreamInfo(rom_data, room_id);
509 if (!stream_info.ok() || stream_info->shared) {
512 room_size.room_size = stream_info->capacity();
544 Room room(room_id, rom);
560 header_pointer =
SnesToPc(header_pointer);
569 int table_offset = (header_pointer) + (room_id * 2);
570 if (table_offset < 0 || table_offset + 1 >=
static_cast<int>(rom->
size())) {
575 (rom->
data()[table_offset + 1] << 8) +
576 rom->
data()[table_offset];
578 auto header_location =
SnesToPc(address);
581 if (header_location < 0 ||
582 header_location + 13 >=
static_cast<int>(rom->
size())) {
586 const uint8_t header_byte0 = rom->
data()[header_location];
589 room.
SetBg2(Background2FromHeaderByte(header_byte0));
591 room.
SetIsLight(IsDarkRoomHeaderByte(header_byte0));
592 room.
SetIsDark(IsDarkRoomHeaderByte(header_byte0));
627 header_pointer_2 =
SnesToPc(header_pointer_2);
636 int table_offset_2 = (header_pointer_2) + (room_id * 2);
637 if (table_offset_2 < 0 ||
638 table_offset_2 + 1 >=
static_cast<int>(rom->
size())) {
643 (rom->
data()[table_offset_2 + 1] << 8) +
644 rom->
data()[table_offset_2];
647 if (msg_addr >= 0 && msg_addr + 1 <
static_cast<int>(rom->
size())) {
648 uint16_t msg_val = (rom->
data()[msg_addr + 1] << 8) | rom->
data()[msg_addr];
656 if (hpos < 0 || hpos + 14 >=
static_cast<int>(rom->
size())) {
660 uint8_t b = rom->
data()[hpos];
686 b = rom->
data()[hpos];
716 game_data_(game_data),
728 const int num_palettes =
static_cast<int>(group.size());
729 if (num_palettes == 0)
733 if (palette_ < game_data_->paletteset_ids.size() &&
741 if (id < 0 || id >= num_palettes)
754 const uint8_t requested_main_blockset =
756 uint8_t main_blockset = 0;
757 if (requested_main_blockset != 0xFF &&
758 requested_main_blockset < game_data_->main_blockset_ids.size()) {
759 main_blockset = requested_main_blockset;
760 }
else if (blockset_ < game_data_->main_blockset_ids.size()) {
764 "Room %d: invalid main fallback blockset %d; using main group 0",
767 if (requested_main_blockset != 0xFF &&
770 "Room %d: entrance main blockset %d out of range; using %d",
771 room_id_, requested_main_blockset, main_blockset);
776 "Room %d: room_blockset=%d, main_blockset=%d, spriteset=%d, "
780 for (
int i = 0; i < 8; i++) {
782 if (i >= 3 && i <= 6 &&
blockset_ < room_gfx.size()) {
783 const uint8_t room_sheet = room_gfx[
blockset_][i - 3];
784 if (room_sheet != 0) {
790 LOG_WARN(
"Room",
"Room %d: room blockset %d out of range; skipped $0AA2",
798 const size_t sprite_gfx_index =
static_cast<size_t>(
spriteset_) + 64;
799 if (sprite_gfx_index < sprite_gfx.size()) {
800 for (
int i = 0; i < 4; i++) {
802 static_cast<uint8_t
>(sprite_gfx[sprite_gfx_index][i] + 115);
806 "Room %d: spriteset %d out of range; clearing sprite sheets",
808 for (
int i = 0; i < 4; i++) {
840 if (entrance_blockset.has_value()) {
851 if (entrance_blockset.has_value()) {
860 !bg2_bmp.is_active() || bg2_bmp.width() == 0) {
876 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: ROM not loaded");
881 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: GameData not set");
885 if (gfx_buffer_data->empty()) {
886 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: Graphics buffer is empty");
890 LOG_DEBUG(
"Room",
"Room %d: Copying 8BPP graphics (buffer size: %zu)",
908 const uint8_t active_main_blockset =
913 auto is_right_palette_background_slot = [&](
int block) ->
bool {
914 if (block < 0 || block >= 8) {
917 const int runtime_slot = 7 - block;
918 if (active_main_blockset < 0x20) {
919 return runtime_slot >= 4;
921 return (runtime_slot == 2 || runtime_slot == 3 || runtime_slot == 4 ||
926 for (
int block = 0; block < 16; block++) {
930 if (sheet_id >= 223) {
931 LOG_WARN(
"Room",
"Invalid sheet index %d for block %d", sheet_id, block);
937 int src_sheet_offset = sheet_id * 4096;
940 if (src_sheet_offset + 4096 > gfx_buffer_data->size()) {
941 LOG_ERROR(
"Room",
"Graphics offset out of bounds: %d (size: %zu)",
942 src_sheet_offset, gfx_buffer_data->size());
947 int dest_index_base = block * 4096;
949 const uint8_t* src = gfx_buffer_data->data() + src_sheet_offset;
954 const bool right_pal = is_right_palette_background_slot(block);
956 memcpy(dst, src, 4096);
959 for (
int i = 0; i < 4096; ++i) {
961 if (p != 0 && p < 8) {
989 bool properties_changed =
false;
1003 properties_changed =
true;
1013 properties_changed =
true;
1021 if (bg1_bmp.is_active() && bg1_bmp.width() > 0 && bg2_bmp.is_active() &&
1022 bg2_bmp.width() > 0) {
1024 "Room %d: No changes detected, skipping render",
room_id_);
1030 "Room %d: Rendering graphics (dirty_flags: g=%d o=%d l=%d t=%d)",
1049 "Room %d: floor1=%d, floor2=%d, blocks_size=%zu",
room_id_,
1054 bool need_floor_draw = was_graphics_dirty;
1059 if (!bg1_bmp.is_active() || bg1_bmp.width() == 0 || !bg2_bmp.is_active() ||
1060 bg2_bmp.width() == 0) {
1061 need_floor_draw =
true;
1063 "Room %d: Bitmaps not created yet, forcing floor draw",
room_id_);
1066 if (need_floor_draw) {
1075 bool need_bg_draw = was_graphics_dirty || need_floor_draw;
1087 if (was_layout_dirty || need_floor_draw) {
1096 if (dungeon_pal_group.empty())
1100 auto bg1_palette = dungeon_pal_group[palette_id];
1109 LOG_DEBUG(
"Room",
"RenderRoomGraphics: Palette ID=%d, Size=%zu", palette_id,
1110 bg1_palette.size());
1111 if (!bg1_palette.empty()) {
1112 LOG_DEBUG(
"Room",
"RenderRoomGraphics: First color: R=%d G=%d B=%d",
1113 bg1_palette[0].rom_color().red, bg1_palette[0].rom_color().green,
1114 bg1_palette[0].rom_color().blue);
1117 if (bg1_palette.size() > 0) {
1118 std::optional<gfx::SnesPalette> hud_palette_storage;
1122 hud_palette = &*hud_palette_storage;
1141 const auto render_palette =
1149 auto set_dungeon_palette = [&](
gfx::Bitmap& bmp) {
1150 bmp.SetPalette(render_palette);
1151 if (bmp.surface()) {
1153 SDL_SetColorKey(bmp.surface(), SDL_TRUE, 255);
1154 SDL_SetSurfaceBlendMode(bmp.surface(), SDL_BLENDMODE_BLEND);
1158 set_dungeon_palette(bg1_bmp);
1159 set_dungeon_palette(bg2_bmp);
1164 auto* surface = bg1_bmp.surface();
1169 "Room::RenderRoomGraphics (BG1)", palette_id,
true);
1173 "Room::RenderRoomGraphics (after SetPalette)", surface);
1176 "Room::RenderRoomGraphics", palette_id,
false,
1177 "SDL surface has no palette!");
1191 if (bg2_bmp.surface()) {
1192 SDL_SetSurfaceAlphaMod(bg2_bmp.surface(), 128);
1200 if (bg2_bmp.surface()) {
1201 SDL_SetSurfaceBlendMode(bg2_bmp.surface(), SDL_BLENDMODE_ADD);
1217 if (bitmap->texture()) {
1223 release_texture(&bg1_bmp);
1224 release_texture(&bg2_bmp);
1239 "Texture commands queued for batch processing");
1247 LOG_DEBUG(
"Room",
"ROM not loaded, aborting");
1258 if (!layout_status.ok()) {
1260 layout_status.message().data());
1266 layout_objects.size());
1267 if (layout_objects.empty()) {
1275 LOG_DEBUG(
"RenderRoomGraphics",
"GameData not set, cannot render layout");
1281 if (dungeon_pal_group.empty())
1285 auto room_palette = dungeon_pal_group[palette_id];
1299 "RenderRoomGraphics",
"Layout Draw failed: %s",
1300 std::string(status.message().data(), status.message().size()).c_str());
1302 LOG_DEBUG(
"RenderRoomGraphics",
"Layout rendered with %zu objects",
1303 layout_objects.size());
1308 LOG_DEBUG(
"[RenderObjectsToBackground]",
1309 "Starting object rendering for room %d",
room_id_);
1312 LOG_DEBUG(
"[RenderObjectsToBackground]",
"ROM not loaded, aborting");
1321 bool bitmaps_exist = bg1_bmp.is_active() && bg1_bmp.width() > 0 &&
1322 bg2_bmp.is_active() && bg2_bmp.width() > 0;
1325 LOG_DEBUG(
"[RenderObjectsToBackground]",
1326 "Room %d: Objects not dirty, skipping render",
room_id_);
1332 LOG_DEBUG(
"[RenderObjectsToBackground]",
1333 "Room %d: Emulator rendering objects",
room_id_);
1338 if (dungeon_pal_group.empty())
1342 auto room_palette = dungeon_pal_group[palette_id];
1387 int layer0_count = 0, layer1_count = 0, layer2_count = 0;
1389 switch (obj.GetLayerValue()) {
1403 "Room %03X Object Stream Summary: Main=%d, BG2Overlay=%d, BG1Overlay=%d",
1404 room_id_, layer0_count, layer1_count, layer2_count);
1421 std::vector<std::vector<RoomObject>> by_list(3);
1433 uint8_t list_index = obj.GetLayerValue();
1434 if (list_index > 2) {
1439 by_list[list_index].push_back(std::move(render_obj));
1442 absl::Status status = absl::OkStatus();
1443 bool reset_room_events_for_next_chunk =
true;
1444 for (
int pass = 0; pass < 3; ++pass) {
1445 if (by_list[pass].empty()) {
1451 reset_room_events_for_next_chunk =
false;
1452 if (!chunk_status.ok() && status.ok()) {
1453 status = chunk_status;
1460 for (
int i = 0; i < static_cast<int>(
doors_.size()); ++i) {
1461 const auto& door =
doors_[i];
1463 door_def.
type = door.type;
1480 if (pot_item.item != 0) {
1482 int tile_x = pot_item.GetTileX();
1483 int tile_y = pot_item.GetTileY();
1490 for (
const auto& sprite :
sprites_) {
1491 if (sprite.key_drop() > 0) {
1497 uint8_t key_item = (sprite.key_drop() == 1) ? 0xFD : 0xFE;
1507 constexpr uint16_t kRoomDrawObj_PushableBlock = 0x0E52;
1508 constexpr uint16_t kRoomDrawObj_TorchUnlit = 0x0EC2;
1509 constexpr uint16_t kRoomDrawObj_TorchLit = 0x0ECA;
1516 static_cast<uint16_t
>(obj.id_), obj.x_, obj.y_, obj.layer_,
1521 const uint16_t off =
1522 obj.lit_ ? kRoomDrawObj_TorchLit : kRoomDrawObj_TorchUnlit;
1527 static_cast<uint16_t
>(obj.id_), obj.x_, obj.y_, obj.layer_, off,
1535 "[RenderObjectsToBackground]",
1536 "Room %03X: ObjectDrawer failed: %s (objects left dirty for retry)",
1538 std::string(status.message().data(), status.message().size()).c_str());
1545 LOG_DEBUG(
"[RenderObjectsToBackground]",
1546 "Room %d: Objects rendered successfully",
room_id_);
1562 if (gfx_buffer_data->empty()) {
1566 auto rom_data =
rom()->vector();
1567 if (rom_data.empty()) {
1572 if (animated_frame_ < 0 || animated_frame_ > 10) {
1577 if (background_tileset_ < 0 || background_tileset_ > 255) {
1582 if (gfx_ptr < 0 || gfx_ptr >=
static_cast<int>(rom_data.size())) {
1587 while (data < 1024) {
1591 if (first_offset >= 0 &&
1592 first_offset <
static_cast<int>(gfx_buffer_data->size())) {
1593 uint8_t map_byte = (*gfx_buffer_data)[first_offset];
1596 int gfx_offset = data + (7 * 4096);
1597 if (gfx_offset >= 0 &&
1607 if (second_offset >= 0 &&
1608 second_offset <
static_cast<int>(gfx_buffer_data->size())) {
1609 uint8_t map_byte = (*gfx_buffer_data)[second_offset];
1612 int gfx_offset = data + (7 * 4096) - 1024;
1613 if (gfx_offset >= 0 &&
1625 auto rom_data =
rom()->vector();
1631 object_pointer =
SnesToPc(object_pointer);
1634 if (object_pointer < 0 || object_pointer >= (
int)
rom_->
size()) {
1638 int room_address = object_pointer + (
room_id_ * 3);
1641 if (room_address < 0 || room_address + 2 >= (
int)
rom_->
size()) {
1645 int tile_address = (rom_data[room_address + 2] << 16) +
1646 (rom_data[room_address + 1] << 8) + rom_data[room_address];
1648 int objects_location =
SnesToPc(tile_address);
1651 if (objects_location < 0 || objects_location >= (
int)
rom_->
size()) {
1656 if (objects_location + 1 < (
int)
rom_->
size()) {
1659 static_cast<uint8_t
>(rom_data[objects_location] & 0x0F);
1661 static_cast<uint8_t
>((rom_data[objects_location] >> 4) & 0x0F);
1663 "Room %d: Set floor1_graphics_=%d, floor2_graphics_=%d",
1668 static_cast<uint8_t
>((rom_data[objects_location + 1] >> 2) & 0x07);
1690 auto rom_data =
rom()->vector();
1696 int nbr_of_staircase = 0;
1698 int pos = objects_location;
1704 bool end_read =
false;
1708 while (!end_read && pos < (
int)
rom_->
size()) {
1710 if (pos + 1 >= (
int)
rom_->
size()) {
1715 b2 = rom_data[pos + 1];
1722 if (b1 == 0xFF && b2 == 0xFF) {
1726 "Room",
"Room %03X: Object list transition to index %d (%s)",
1728 layer == 1 ?
"BG2 overlay" : (layer == 2 ?
"BG1 overlay" :
"END"));
1738 if (b1 == 0xF0 && b2 == 0xFF) {
1745 if (pos + 2 >= (
int)
rom_->
size()) {
1749 b3 = rom_data[pos + 2];
1760 b1, b2, b3,
static_cast<uint8_t
>(layer));
1762 LOG_DEBUG(
"Room",
"Room %03X: Object 0x%03X at (%d,%d) stream=%d (%s)",
1764 layer == 0 ?
"Primary"
1765 : (layer == 1 ?
"BG2 overlay" :
"BG1 overlay"));
1769 if (r.
id_ >= 0 && r.
id_ <= 0xFFF) {
1783 "ParseDoor: room=%d b1=0x%02X b2=0x%02X pos=%d dir=%d type=%d",
1785 static_cast<int>(door.direction),
static_cast<int>(door.type));
1796 std::vector<uint8_t> bytes;
1800 std::vector<RoomObject> layer0_objects;
1801 std::vector<RoomObject> layer1_objects;
1802 std::vector<RoomObject> layer2_objects;
1815 switch (obj.GetLayerValue()) {
1817 layer0_objects.push_back(obj);
1820 layer1_objects.push_back(obj);
1823 layer2_objects.push_back(obj);
1839 for (
const auto& obj : layer0_objects) {
1840 auto encoded = obj.EncodeObjectToBytes();
1841 bytes.push_back(encoded.b1);
1842 bytes.push_back(encoded.b2);
1843 bytes.push_back(encoded.b3);
1845 bytes.push_back(0xFF);
1846 bytes.push_back(0xFF);
1849 for (
const auto& obj : layer1_objects) {
1850 auto encoded = obj.EncodeObjectToBytes();
1851 bytes.push_back(encoded.b1);
1852 bytes.push_back(encoded.b2);
1853 bytes.push_back(encoded.b3);
1855 bytes.push_back(0xFF);
1856 bytes.push_back(0xFF);
1859 for (
const auto& obj : layer2_objects) {
1860 auto encoded = obj.EncodeObjectToBytes();
1861 bytes.push_back(encoded.b1);
1862 bytes.push_back(encoded.b2);
1863 bytes.push_back(encoded.b3);
1868 bytes.push_back(0xF0);
1869 bytes.push_back(0xFF);
1870 for (
const auto& door :
doors_) {
1871 auto [b1, b2] = door.EncodeBytes();
1872 bytes.push_back(b1);
1873 bytes.push_back(b2);
1877 bytes.push_back(0xFF);
1878 bytes.push_back(0xFF);
1884 std::vector<uint8_t> bytes;
1886 for (
const auto& sprite :
sprites_) {
1894 b2 = (sprite.x() & 0x1F) | ((sprite.subtype() & 0x07) << 5);
1899 b1 = (sprite.y() & 0x1F) | ((sprite.subtype() & 0x18) << 2) |
1900 ((sprite.layer() & 0x01) << 7);
1902 bytes.push_back(b1);
1903 bytes.push_back(b2);
1904 bytes.push_back(b3);
1908 if (sprite.key_drop() == 1) {
1909 bytes.insert(bytes.end(), {0xFE, 0x00, 0xE4});
1910 }
else if (sprite.key_drop() == 2) {
1911 bytes.insert(bytes.end(), {0xFD, 0x00, 0xE4});
1916 bytes.push_back(0xFF);
1926 const auto& rom_data = rom->
vector();
1927 int sprite_pointer = 0;
1928 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
1932 const int hard_end =
1934 if (hard_end <= 0) {
1939 std::unordered_set<int> visited_addresses;
1941 int sprite_address =
1942 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
1943 if (sprite_address < kSpritesData || sprite_address >= hard_end) {
1946 if (!visited_addresses.insert(sprite_address).second) {
1951 MeasureSpriteStreamSize(rom_data, sprite_address, hard_end);
1952 int stream_end = sprite_address + stream_size;
1953 if (stream_end > max_used) {
1954 max_used = stream_end;
1962 const std::vector<uint8_t>& encoded_bytes) {
1964 return absl::InvalidArgumentError(
"ROM not loaded");
1967 return absl::OutOfRangeError(
"Room ID out of range");
1969 if (encoded_bytes.empty() || encoded_bytes.back() != 0xFF ||
1970 (encoded_bytes.size() % 3) != 1) {
1971 return absl::InvalidArgumentError(
1972 "Encoded sprite payload must be N*3 bytes plus 0xFF terminator");
1975 const auto& rom_data = rom->
vector();
1976 int sprite_pointer = 0;
1979 int old_sprite_address =
1980 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
1981 if (old_sprite_address < 0 ||
1982 old_sprite_address >=
static_cast<int>(rom_data.size())) {
1983 return absl::OutOfRangeError(
"Sprite address out of range");
1986 const uint8_t sort_mode = rom_data[old_sprite_address];
1989 const size_t required_size = 1u + encoded_bytes.size();
1991 static_cast<size_t>(write_pos) + required_size >
1993 return absl::ResourceExhaustedError(absl::StrFormat(
1994 "Not enough sprite data space. Need %d bytes at 0x%06X, "
1995 "region ends at 0x%06X",
1998 if (
static_cast<size_t>(write_pos) + required_size > rom_data.size()) {
1999 const int required_end = write_pos +
static_cast<int>(required_size);
2000 return absl::OutOfRangeError(
2001 absl::StrFormat(
"ROM too small for sprite relocation write (need "
2002 "end=0x%06X, size=0x%06X)",
2003 required_end,
static_cast<int>(rom_data.size())));
2006 std::vector<uint8_t> relocated;
2007 relocated.reserve(required_size);
2008 relocated.push_back(sort_mode);
2009 relocated.insert(relocated.end(), encoded_bytes.begin(), encoded_bytes.end());
2012 const uint32_t snes_addr =
PcToSnes(write_pos);
2013 const int ptr_off = sprite_pointer + (room_id * 2);
2017 return absl::OkStatus();
2021 if (
rom_ ==
nullptr) {
2022 return absl::InvalidArgumentError(
"ROM pointer is null");
2025 return absl::OkStatus();
2028 const auto& rom_data =
rom()->vector();
2030 GetObjectStreamInfo(rom_data,
room_id_));
2032 bool requires_copy_on_write =
false;
2033 if (layout !=
nullptr) {
2035 DungeonStreamRequiresCopyOnWrite(
2037 encoded_bytes.size() + 2u));
2039 const auto relocate = [&]() -> absl::Status {
2040 if (stream_info.address + 2 >
static_cast<int>(rom_data.size())) {
2041 return absl::OutOfRangeError(
"Object stream header is out of range");
2043 std::vector<uint8_t> replacement = {rom_data[stream_info.address],
2044 rom_data[stream_info.address + 1]};
2045 replacement.insert(replacement.end(), encoded_bytes.begin(),
2046 encoded_bytes.end());
2049 std::move(replacement)));
2051 return absl::OkStatus();
2053 if (stream_info.shared || requires_copy_on_write) {
2054 if (layout !=
nullptr) {
2057 return absl::FailedPreconditionError(absl::StrFormat(
2058 "Room %d object stream at PC 0x%06X is shared; repacking is required",
2061 if (stream_info.capacity() <= 2) {
2062 if (layout !=
nullptr) {
2065 return absl::FailedPreconditionError(absl::StrFormat(
2066 "Room %d object stream has no safe physical boundary",
room_id_));
2070 const int write_pos = stream_info.address + 2;
2073 const int available_payload_size = stream_info.capacity() - 2;
2077 if (encoded_bytes.size() >
static_cast<size_t>(available_payload_size)) {
2078 if (layout !=
nullptr) {
2081 return absl::ResourceExhaustedError(absl::StrFormat(
2082 "Room %d object data too large! Size: %d, Available: %d",
room_id_,
2083 static_cast<int>(encoded_bytes.size()), available_payload_size));
2086 const int door_list_offset =
static_cast<int>(encoded_bytes.size()) -
2087 static_cast<int>(
doors_.size()) * 2 - 2;
2088 if (door_list_offset < 0) {
2089 return absl::FailedPreconditionError(
"Invalid encoded door list offset");
2092 if (door_pointer_slot < 0 ||
2093 door_pointer_slot + 2 >=
static_cast<int>(rom_data.size())) {
2094 return absl::OutOfRangeError(
"Door pointer slot is out of range");
2096 const int door_pointer_pc = write_pos + door_list_offset;
2103 door_pointer_slot,
static_cast<uint32_t
>(
PcToSnes(door_pointer_pc))));
2107 return absl::OkStatus();
2111 if (
rom_ ==
nullptr) {
2112 return absl::InvalidArgumentError(
"ROM pointer is null");
2115 return absl::OkStatus();
2118 return absl::InvalidArgumentError(
2119 "Dungeon floor graphics values must be in range 0..15");
2122 return absl::InvalidArgumentError(
2123 "Dungeon layout ID must be in range 0..7");
2128 GetObjectStreamInfo(rom_data,
room_id_));
2129 if (stream_info.address < 0 ||
2130 stream_info.address + 1 >=
static_cast<int>(rom_data.size())) {
2131 return absl::OutOfRangeError(
"Object stream header is out of range");
2135 auto patch_header = [&](std::vector<uint8_t>* stream) -> absl::Status {
2136 if (stream ==
nullptr || stream->size() < 2) {
2137 return absl::DataLossError(
2138 "Object stream is missing its two-byte header");
2141 (*stream)[0] =
static_cast<uint8_t
>(((*stream)[0] & 0xF0) |
2149 (*stream)[1] =
static_cast<uint8_t
>(((*stream)[1] & 0xE3) |
2152 return absl::OkStatus();
2155 bool requires_copy_on_write = stream_info.shared;
2156 std::vector<uint8_t> replacement;
2157 if (layout !=
nullptr) {
2159 return absl::InvalidArgumentError(
2160 "Object-stream header save requires an object stream layout");
2164 if (!inventory.
ok()) {
2165 return absl::FailedPreconditionError(absl::StrFormat(
2166 "Dungeon stream inventory has %zu issue(s); refusing object "
2168 inventory.
issues.size()));
2172 return absl::OutOfRangeError(
2173 "Room ID is outside the dungeon stream layout");
2176 bool layout_requires_copy_on_write =
false;
2178 DungeonStreamRequiresCopyOnWrite(
2180 replacement.size()));
2181 requires_copy_on_write =
2182 requires_copy_on_write || layout_requires_copy_on_write;
2185 if (requires_copy_on_write) {
2186 if (layout ==
nullptr) {
2187 return absl::FailedPreconditionError(absl::StrFormat(
2188 "Room %d object stream at PC 0x%06X is shared; a copy-on-write "
2189 "manifest is required to save its header",
2195 std::move(replacement)));
2197 return absl::OkStatus();
2200 std::vector<uint8_t> header = {rom_data[stream_info.address],
2201 rom_data[stream_info.address + 1]};
2205 return absl::OkStatus();
2209 if (
rom_ ==
nullptr) {
2210 return absl::InvalidArgumentError(
"ROM pointer is null");
2213 return absl::OkStatus();
2216 const auto& rom_data =
rom()->vector();
2218 return absl::OutOfRangeError(
"Room ID out of range");
2222 GetSpriteStreamInfo(rom_data,
room_id_));
2224 bool requires_copy_on_write =
false;
2225 if (layout !=
nullptr) {
2227 DungeonStreamRequiresCopyOnWrite(
2229 encoded_bytes.size() + 1u));
2231 const auto relocate = [&]() -> absl::Status {
2232 std::vector<uint8_t> replacement = {rom_data[stream_info.address]};
2233 replacement.insert(replacement.end(), encoded_bytes.begin(),
2234 encoded_bytes.end());
2237 std::move(replacement)));
2239 return absl::OkStatus();
2241 if (stream_info.shared || requires_copy_on_write) {
2242 if (layout !=
nullptr) {
2245 return absl::FailedPreconditionError(absl::StrFormat(
2246 "Room %d sprite stream at PC 0x%06X is shared; repacking is required",
2249 if (stream_info.capacity() <= 1) {
2250 if (layout !=
nullptr) {
2253 return absl::FailedPreconditionError(absl::StrFormat(
2254 "Room %d sprite stream has no safe physical boundary",
room_id_));
2257 const int available_payload_size = stream_info.capacity() - 1;
2258 const int payload_address = stream_info.address + 1;
2259 if (payload_address < 0 ||
2260 payload_address >=
static_cast<int>(
rom_->
size())) {
2261 return absl::OutOfRangeError(absl::StrFormat(
2262 "Room %d has invalid sprite payload address",
room_id_));
2265 if (
static_cast<int>(encoded_bytes.size()) > available_payload_size) {
2266 if (layout !=
nullptr) {
2269 return absl::ResourceExhaustedError(absl::StrFormat(
2270 "Room %d sprite data too large! Size: %d, Available: %d; repacking "
2272 room_id_,
static_cast<int>(encoded_bytes.size()),
2273 available_payload_size));
2278 return absl::OkStatus();
2282 if (
rom_ ==
nullptr) {
2283 return absl::InvalidArgumentError(
"ROM pointer is null");
2286 const auto& rom_data =
rom()->vector();
2289 return absl::OutOfRangeError(
"Room header pointer out of range");
2293 return absl::OutOfRangeError(
"Room header pointer bank out of range");
2299 header_pointer =
SnesToPc(header_pointer);
2301 int table_offset = header_pointer + (
room_id_ * 2);
2302 if (table_offset < 0 ||
2303 table_offset + 1 >=
static_cast<int>(rom_data.size())) {
2304 return absl::OutOfRangeError(
"Room header table offset out of range");
2308 (rom_data[table_offset + 1] << 8) + rom_data[table_offset];
2309 int header_location =
SnesToPc(address);
2311 if (header_location < 0 ||
2312 header_location + 13 >=
static_cast<int>(rom_data.size())) {
2313 return absl::OutOfRangeError(
"Room header location out of range");
2320 if (
bg2() != background2::DarkRoom) {
2321 layer2_mode_for_save =
static_cast<uint8_t
>(
bg2()) & 0x07;
2323 const bool dark_room =
2325 uint8_t byte0 =
static_cast<uint8_t
>(
2326 (layer2_mode_for_save << 5) |
2327 ((
static_cast<uint8_t
>(
collision()) & 0x07) << 2) |
2328 (rom_data[header_location] & 0x02) | (dark_room ? 1 : 0));
2336 const uint8_t byte8 =
static_cast<uint8_t
>(
2337 (rom_data[header_location + 8] & 0xFC) | (
staircase_plane(3) & 0x03));
2358 if (msg_addr < 0 || msg_addr + 1 >=
static_cast<int>(rom_data.size())) {
2359 return absl::OutOfRangeError(
"Message ID address out of range");
2365 return absl::OkStatus();
2375 return absl::InvalidArgumentError(
"Invalid object parameters");
2383 return absl::OkStatus();
2388 return absl::OutOfRangeError(
"Object index out of range");
2396 return absl::OkStatus();
2401 return absl::OutOfRangeError(
"Object index out of range");
2405 return absl::InvalidArgumentError(
"Invalid object parameters");
2413 return absl::OkStatus();
2419 if (obj.x() == x && obj.y() == y && obj.GetLayerValue() == layer) {
2423 return absl::NotFoundError(
"No object found at position");
2428 if (
object.x() < 0 ||
object.x() > 63)
2430 if (
object.y() < 0 ||
object.y() > 63)
2434 if (
object.GetLayerValue() < 0 ||
object.GetLayerValue() > 2)
2438 if (
object.id_ < 0 || object.id_ > 0xFFF)
2442 if (
object.id_ < 0x100 &&
object.size() > 15)
2449 int& nbr_of_staircase) {
2453 if (nbr_of_staircase < 4) {
2476 }
else if (oid == 0xFB1) {
2486 const auto& rom_data =
rom()->vector();
2494 int sprite_pointer = 0;
2495 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
2499 int sprite_address =
2500 ReadRoomSpriteAddressPc(rom_data, sprite_pointer,
room_id_);
2501 if (sprite_address < 0 ||
2502 sprite_address + 1 >=
static_cast<int>(rom_data.size())) {
2507 sprite_address += 1;
2509 while (sprite_address + 2 <
static_cast<int>(rom_data.size())) {
2510 uint8_t b1 = rom_data[sprite_address];
2511 uint8_t b2 = rom_data[sprite_address + 1];
2512 uint8_t b3 = rom_data[sprite_address + 2];
2518 sprites_.emplace_back(b3, (b2 & 0x1F), (b1 & 0x1F),
2519 ((b2 & 0xE0) >> 5) + ((b1 & 0x60) >> 2),
2526 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1E &&
2532 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1D &&
2539 sprite_address += 3;
2551 const auto& rom_data =
rom()->vector();
2557 const int cpos =
static_cast<int>(
SnesToPc(
2561 const size_t byte_length =
2564 const size_t bounded_byte_length = std::min<size_t>(
2565 byte_length, cpos >= 0 && cpos <
static_cast<int>(rom_data.size())
2566 ? rom_data.size() -
static_cast<size_t>(cpos)
2568 const size_t record_count = std::min<size_t>(
2571 for (
size_t i = 0; i < record_count; ++i) {
2572 const size_t offset =
2574 if ((((rom_data[offset + 1] << 8) + rom_data[offset]) & 0x7FFF) ==
2578 if ((((rom_data[offset + 1] << 8) + rom_data[offset]) & 0x8000) ==
2590 auto rom_data =
rom()->vector();
2601 "LoadDoors for room %d - doors are loaded via object stream",
2606 auto rom_data =
rom()->vector();
2619 return (obj.options() & ObjectOption::Torch) !=
2620 ObjectOption::Nothing;
2625 for (
int i = 0; i < bytes_count; i += 2) {
2626 if (i + 1 >= bytes_count)
2633 if (b1 == 0xFF && b2 == 0xFF) {
2638 uint16_t torch_room_id = (b2 << 8) | b1;
2642 while (i < bytes_count) {
2643 if (i + 1 >= bytes_count)
2650 if (b1 == 0xFF && b2 == 0xFF) {
2658 torch_obj.SetRom(
rom_);
2660 torch_obj.set_torch_reserved_bit(entry.
reserved);
2661 torch_obj.lit_ = entry.
lit;
2666 "Room",
"Loaded torch at (%d,%d) draw_layer=%d reserved=%d lit=%d",
2675 while (i < bytes_count) {
2676 if (i + 1 >= bytes_count)
2680 if (b1 == 0xFF && b2 == 0xFF) {
2695 uint16_t room_id = 0;
2701 const std::vector<uint8_t>& rom_data,
int bytes_count) {
2702 std::vector<TorchSegment> segments;
2707 if (b1 == 0xFF && b2 == 0xFF) {
2713 padding.
bytes = {0xFF, 0xFF};
2714 segments.push_back(std::move(padding));
2718 uint16_t room_id = (b2 << 8) | b1;
2725 seg.
bytes.push_back(b1);
2726 seg.
bytes.push_back(b2);
2731 if (b1 == 0xFF && b2 == 0xFF) {
2732 seg.
bytes.push_back(0xFF);
2733 seg.
bytes.push_back(0xFF);
2737 seg.
bytes.push_back(b1);
2738 seg.
bytes.push_back(b2);
2741 segments.push_back(std::move(seg));
2747 std::vector<uint8_t> bytes;
2752 if (bytes.empty()) {
2753 bytes.push_back(room_id & 0xFF);
2754 bytes.push_back((room_id >> 8) & 0xFF);
2757 .px =
static_cast<uint8_t
>(obj.x()),
2758 .py =
static_cast<uint8_t
>(obj.y()),
2759 .draw_layer =
static_cast<uint8_t
>(obj.GetLayerValue() & 1),
2760 .reserved = obj.torch_reserved_bit(),
2763 bytes.push_back(encoded.
low);
2764 bytes.push_back(encoded.
high);
2766 if (!bytes.empty()) {
2767 bytes.push_back(0xFF);
2768 bytes.push_back(0xFF);
2775 const char* object_type) {
2776 const uint8_t selector =
object.GetLayerValue();
2777 if (selector <= 1) {
2778 return absl::OkStatus();
2780 return absl::InvalidArgumentError(absl::StrFormat(
2781 "%s in room 0x%03X has invalid special draw-layer selector %d; "
2783 "(upper/BG1) or 1 (lower/BG2)",
2784 object_type, room_id, selector));
2791 if (
object.x() <= 0x3E &&
object.y() <= 0x3E) {
2792 return absl::OkStatus();
2794 return absl::InvalidArgumentError(absl::StrFormat(
2795 "Torch in room 0x%03X has invalid position (%d,%d); expected x/y in "
2797 room_id,
object.x(),
object.y()));
2802template <
typename RoomLookup>
2804 RoomLookup&& room_lookup) {
2806 return absl::InvalidArgumentError(
"ROM not loaded");
2809 const auto& rom_data = rom->
vector();
2812 if (existing_count > kTorchesMaxSize) {
2813 existing_count = kTorchesMaxSize;
2815 auto rom_segments = ParseRomTorchSegments(rom_data, existing_count);
2817 std::vector<uint8_t> bytes;
2819 std::vector<bool> owned_rooms(room_limit,
false);
2820 std::vector<bool> seen_original_room(room_limit,
false);
2821 std::vector<bool> emitted_owned_room(room_limit,
false);
2822 std::vector<std::vector<uint8_t>> replacements(room_limit);
2823 bool any_owned_room =
false;
2824 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2825 const Room* room = room_lookup(room_id);
2826 const bool room_owned =
2836 owned_rooms[room_id] =
true;
2837 any_owned_room =
true;
2838 replacements[room_id] = EncodeTorchSegmentForRoom(room_id, *room);
2841 if (!any_owned_room) {
2842 return absl::OkStatus();
2845 for (
const auto& segment : rom_segments) {
2846 if (segment.room_id < room_limit) {
2847 seen_original_room[segment.room_id] =
true;
2848 if (owned_rooms[segment.room_id]) {
2849 if (!emitted_owned_room[segment.room_id]) {
2850 bytes.insert(bytes.end(), replacements[segment.room_id].begin(),
2851 replacements[segment.room_id].end());
2852 emitted_owned_room[segment.room_id] =
true;
2857 bytes.insert(bytes.end(), segment.bytes.begin(), segment.bytes.end());
2860 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2861 if (owned_rooms[room_id] && !seen_original_room[room_id] &&
2862 !replacements[room_id].empty()) {
2863 bytes.insert(bytes.end(), replacements[room_id].begin(),
2864 replacements[room_id].end());
2868 if (bytes.size() > kTorchesMaxSize) {
2869 return absl::ResourceExhaustedError(
2870 absl::StrFormat(
"Torch data too large: %d bytes (max %d)", bytes.size(),
2874 const uint16_t current_len =
2877 if (current_len == bytes.size() &&
2878 kTorchData +
static_cast<int>(bytes.size()) <=
2879 static_cast<int>(rom_data.size()) &&
2880 std::equal(bytes.begin(), bytes.end(), rom_data.begin() +
kTorchData)) {
2881 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2882 if (
const Room* room = room_lookup(room_id);
2884 const_cast<Room*
>(room)->ClearTorchesDirty();
2887 return absl::OkStatus();
2891 static_cast<uint16_t
>(bytes.size())));
2893 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2894 if (
const Room* room = room_lookup(room_id);
2896 const_cast<Room*
>(room)->ClearTorchesDirty();
2899 return absl::OkStatus();
2904 [&rooms](
int room_id) { return &rooms[room_id]; });
2908 Rom* rom,
int room_count,
2909 const std::function<
const Room*(
int)>& room_lookup) {
2921 if (pit_damage_table !=
nullptr && pit_damage_table->
dirty()) {
2924 return absl::OkStatus();
2927 return absl::InvalidArgumentError(
"ROM not loaded");
2929 const auto& rom_data = rom->
vector();
2930 if (kPitCount < 0 || kPitCount >=
static_cast<int>(rom_data.size()) ||
2931 kPitPointer + 2 >=
static_cast<int>(rom_data.size())) {
2932 return absl::OutOfRangeError(
"Pit count/pointer out of range");
2938 int data_len = max_offset + 2;
2939 int pit_ptr_snes = (rom_data[
kPitPointer + 2] << 16) |
2941 int pit_data_pc =
SnesToPc(pit_ptr_snes);
2942 if (pit_data_pc < 0 ||
2943 pit_data_pc + data_len >
static_cast<int>(rom_data.size())) {
2944 return absl::OutOfRangeError(
"Pit data region out of range");
2946 std::vector<uint8_t> data(rom_data.begin() + pit_data_pc,
2947 rom_data.begin() + pit_data_pc + data_len);
2963 return first_begin < second_end && second_begin < first_end;
2967 const std::vector<uint8_t>& rom_data,
int operand_pc) {
2968 if (operand_pc <= 0 || operand_pc + 5 >=
static_cast<int>(rom_data.size())) {
2969 return absl::OutOfRangeError(
"Blocks pointer operand out of range");
2983 if (rom_data[operand_pc - 1] != 0xBF || rom_data[operand_pc + 3] != 0x9D) {
2984 return absl::FailedPreconditionError(absl::StrFormat(
2985 "Blocks pointer operand at PC 0x%05X is not in the expected "
2986 "LDA.l ...,X / STA.w loader sequence",
2989 return absl::OkStatus();
2993 const std::vector<uint8_t>& rom_data, std::array<int, 4>* destination_pcs) {
2996 return absl::OutOfRangeError(
"Blocks length out of range");
3002 const int snes = (rom_data[operand_pc + 2] << 16) |
3003 (rom_data[operand_pc + 1] << 8) | rom_data[operand_pc];
3004 const int data_pc =
SnesToPc(snes);
3007 return absl::OutOfRangeError(absl::StrFormat(
3008 "Blocks data region out of range for loader page %d", page + 1));
3010 (*destination_pcs)[page] = data_pc;
3014 for (
size_t page = 0; page < destination_pcs->size(); ++page) {
3015 const int page_begin = (*destination_pcs)[page];
3018 kLengthMetadataEnd)) {
3019 return absl::FailedPreconditionError(absl::StrFormat(
3020 "Blocks data page %d at PC [0x%05X, 0x%05X) overlaps block-table "
3021 "length metadata [0x%05X, 0x%05X)",
3022 page + 1, page_begin, page_end,
kBlocksLength, kLengthMetadataEnd));
3033 return absl::FailedPreconditionError(absl::StrFormat(
3034 "Blocks data page %d at PC [0x%05X, 0x%05X) overlaps loader %d "
3035 "opcode/operand metadata [0x%05X, 0x%05X)",
3036 page + 1, page_begin, page_end, loader + 1, loader_begin,
3041 for (
size_t previous = 0; previous < page; ++previous) {
3042 const int previous_begin = (*destination_pcs)[previous];
3046 return absl::FailedPreconditionError(absl::StrFormat(
3047 "Blocks data pages %d and %d overlap at PC ranges [0x%05X, "
3048 "0x%05X) and [0x%05X, 0x%05X)",
3049 previous + 1, page + 1, previous_begin, previous_end, page_begin,
3055 return absl::OkStatus();
3062 return absl::InvalidArgumentError(
"ROM not loaded");
3064 const auto& rom_data = rom->
vector();
3065 if (
kBlocksLength + 1 >=
static_cast<int>(rom_data.size())) {
3066 return absl::OutOfRangeError(
"Blocks length out of range");
3070 std::array<int, 4> destination_pcs{};
3072 PreflightBlocksLoaderDestinations(rom_data, &destination_pcs));
3073 if (blocks_count <= 0) {
3074 return absl::OkStatus();
3076 for (
int r = 0; r < 4; ++r) {
3077 const int pc = destination_pcs[r];
3078 int off = r * kBlocksRegionSize;
3079 int len = std::min(kBlocksRegionSize, blocks_count - off);
3082 std::vector<uint8_t> chunk(rom_data.begin() + pc,
3083 rom_data.begin() + pc + len);
3088 return absl::OkStatus();
3092 const std::function<
const Room*(
int)>& room_lookup) {
3094 return absl::InvalidArgumentError(
"ROM not loaded");
3096 const auto& rom_data = rom->
vector();
3097 if (
kBlocksLength + 1 >=
static_cast<int>(rom_data.size())) {
3098 return absl::OutOfRangeError(
"Blocks length out of range");
3101 std::array<int, 4> destination_pcs{};
3103 PreflightBlocksLoaderDestinations(rom_data, &destination_pcs));
3111 const int original_count_word =
3113 const int original_byte_len = std::max(0, original_count_word);
3114 std::vector<uint8_t> original_buffer(original_byte_len, 0);
3115 for (
int r = 0; r < 4; ++r) {
3116 const int pc = destination_pcs[r];
3117 const int off = r * kBlocksRegionSize;
3118 const int len = std::min(kBlocksRegionSize, original_byte_len - off);
3121 std::copy_n(rom_data.begin() + pc, len, original_buffer.begin() + off);
3123 const int original_slot_count = original_byte_len / 4;
3134 struct EncodedBlock {
3138 std::unordered_set<uint16_t> owned_room_ids;
3139 std::unordered_set<int> claimed_load_orders;
3140 std::unordered_map<int, EncodedBlock> slot_replacements;
3141 std::vector<EncodedBlock> appended;
3142 for (
int rid = 0; rid < room_count; ++rid) {
3143 const Room* room = room_lookup(rid);
3144 if (room ==
nullptr)
3148 return absl::FailedPreconditionError(absl::StrFormat(
3149 "Room 0x%03X has unsaved pushable-block edits, but its block "
3150 "table is not loaded. Load the room's blocks before saving.",
3155 owned_room_ids.insert(
static_cast<uint16_t
>(rid));
3160 ValidateSpecialObjectDrawLayerSelector(obj, rid,
"Pushable block"));
3162 encoded_entry.
room_id =
static_cast<uint16_t
>(rid);
3163 encoded_entry.
px = obj.x();
3164 encoded_entry.
py = obj.y();
3165 encoded_entry.
draw_layer = obj.GetLayerValue();
3169 const EncodedBlock encoded_block{encoded, &obj};
3170 const int load_order = obj.block_load_order();
3171 if (load_order >= 0 && !claimed_load_orders.insert(load_order).second) {
3172 return absl::FailedPreconditionError(absl::StrFormat(
3173 "Room 0x%03X has multiple pushable blocks claiming non-new "
3174 "load-order slot %d",
3178 appended.push_back(encoded_block);
3179 }
else if (load_order >= 0 && load_order < original_slot_count) {
3180 const int original_offset = load_order * 4;
3181 const uint16_t original_room_id =
3182 static_cast<uint16_t
>(original_buffer[original_offset] |
3183 (original_buffer[original_offset + 1] << 8));
3184 if (original_room_id !=
static_cast<uint16_t
>(rid)) {
3189 appended.push_back(encoded_block);
3192 slot_replacements.emplace(load_order, encoded_block);
3196 appended.push_back(encoded_block);
3203 std::vector<uint8_t> output;
3204 output.reserve(original_byte_len + appended.size() * 4);
3205 std::vector<std::pair<const RoomObject*, int>> load_order_updates;
3206 load_order_updates.reserve(slot_replacements.size() + appended.size());
3207 const auto append_encoded_block =
3208 [&output, &load_order_updates](
const EncodedBlock& block) {
3209 const int output_slot =
static_cast<int>(output.size() / 4);
3210 output.push_back(block.bytes.b1);
3211 output.push_back(block.bytes.b2);
3212 output.push_back(block.bytes.b3);
3213 output.push_back(block.bytes.b4);
3214 load_order_updates.emplace_back(block.source_object, output_slot);
3216 for (
int slot = 0; slot < original_slot_count; ++slot) {
3217 const uint8_t b1 = original_buffer[slot * 4 + 0];
3218 const uint8_t b2 = original_buffer[slot * 4 + 1];
3219 const uint16_t slot_room_id =
static_cast<uint16_t
>(b1 | (b2 << 8));
3220 if (owned_room_ids.contains(slot_room_id)) {
3221 const auto it = slot_replacements.find(slot);
3222 if (it == slot_replacements.end()) {
3227 append_encoded_block(it->second);
3230 output.push_back(b1);
3231 output.push_back(b2);
3232 output.push_back(original_buffer[slot * 4 + 2]);
3233 output.push_back(original_buffer[slot * 4 + 3]);
3239 for (
const auto& block : appended) {
3240 append_encoded_block(block);
3249 if (output.empty()) {
3250 return absl::FailedPreconditionError(
3251 "Pushable-block table cannot be empty: ALTTP's runtime scan reads one "
3252 "entry before comparing the byte-length limit. Keep at least one "
3253 "pushable block, or patch the runtime loop before removing the last "
3258 const int kMaxEntries = (4 * kBlocksRegionSize) / 4;
3259 if (
static_cast<int>(output.size() / 4) > kMaxEntries) {
3260 return absl::FailedPreconditionError(absl::StrCat(
3261 "Pushable-block table overflow: ", output.size() / 4,
3262 " entries exceeds the vanilla cap of ", kMaxEntries,
3263 " (expand layout requires repointing all 4 LDA.l operand slots; "
3264 "out of scope for this encoder)."));
3271 const int total_bytes =
static_cast<int>(output.size());
3272 struct BlockWriteDestination {
3277 std::vector<BlockWriteDestination> write_destinations;
3278 write_destinations.reserve(4);
3279 for (
int r = 0; r < 4; ++r) {
3280 const int off = r * kBlocksRegionSize;
3281 const int len = std::min(kBlocksRegionSize, total_bytes - off);
3284 write_destinations.push_back({destination_pcs[r], off, len});
3289 for (
const auto&
destination : write_destinations) {
3290 std::vector<uint8_t> chunk(
3304 for (
const auto& [
object, load_order] : load_order_updates) {
3305 const_cast<RoomObject*
>(object)->set_block_load_order(load_order);
3307 for (
int room_id = 0; room_id < room_count; ++room_id) {
3308 if (
const Room* room = room_lookup(room_id);
3310 const_cast<Room*
>(room)->ClearBlocksDirty();
3313 return absl::OkStatus();
3316template <
typename RoomLookup>
3318 RoomLookup&& room_lookup) {
3320 return absl::InvalidArgumentError(
"ROM not loaded");
3327 const auto& rom_data = rom->
vector();
3332 if (!has_ptr_table) {
3333 for (
int room_id = 0; room_id < room_count; ++room_id) {
3334 const Room* room = room_lookup(room_id);
3336 return absl::FailedPreconditionError(
3337 "Custom collision region not present in this ROM");
3340 return absl::OkStatus();
3343 if (!has_data_region) {
3344 for (
int room_id = 0; room_id < room_count; ++room_id) {
3345 const Room* room = room_lookup(room_id);
3347 return absl::FailedPreconditionError(
3348 "Custom collision data region not present in this ROM");
3351 return absl::OkStatus();
3360 "CustomCollisionPointers"));
3364 "CustomCollisionData"));
3368 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3369 const Room* room = room_lookup(room_id);
3374 const int actual_room_id = room->
id();
3376 if (ptr_offset + 2 >=
static_cast<int>(rom_data.size())) {
3377 return absl::OutOfRangeError(
"Custom collision pointer out of range");
3385 const_cast<Room*
>(room)->ClearCustomCollisionDirty();
3401 const_cast<Room*
>(room)->ClearCustomCollisionDirty();
3407 const_cast<Room*
>(room)->ClearCustomCollisionDirty();
3410 return absl::OkStatus();
3415 rom,
static_cast<int>(rooms.size()),
3416 [&rooms](
int room_id) { return &rooms[room_id]; });
3420 const std::function<
Room*(
int)>& room_lookup) {
3424absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
3426 if (rom ==
nullptr || !rom->
is_loaded()) {
3427 return absl::InvalidArgumentError(
"ROM not loaded");
3429 const auto& rom_data = rom->
vector();
3432 return absl::OutOfRangeError(
"Chest pointers out of range");
3435 const uint32_t data_pointer =
3439 const uint32_t data_pc =
SnesToPc(data_pointer);
3440 if (data_pc > rom_data.size() ||
3442 rom_data.size() -
static_cast<size_t>(data_pc)) {
3443 return absl::OutOfRangeError(
"Chest data region out of range");
3445 const uint32_t data_end =
3447 const auto overlaps = [](uint32_t begin, uint32_t end, uint32_t other_begin,
3448 uint32_t other_end) {
3449 return begin < other_end && other_begin < end;
3455 return absl::FailedPreconditionError(
3456 "Chest data region overlaps chest metadata operands");
3459 return std::vector<std::pair<uint32_t, uint32_t>>{
3462 {data_pc, data_end},
3472 uint16_t
room_id()
const {
return word & 0x7FFF; }
3478 const std::vector<uint8_t>& rom_data,
int cpos,
int byte_length) {
3479 std::vector<PhysicalChestRecord> records;
3481 records.reserve(record_count);
3482 for (
int i = 0; i < record_count; ++i) {
3488 const uint16_t word =
3489 (
static_cast<uint16_t
>(rom_data[off + 1]) << 8) | rom_data[off];
3497 bytes->push_back(word & 0xFF);
3498 bytes->push_back((word >> 8) & 0xFF);
3499 bytes->push_back(item);
3504 const uint16_t word =
static_cast<uint16_t
>(room_id) |
3505 (
chest.size ?
static_cast<uint16_t
>(0x8000) : 0);
3515 if (ptr_off < 0 || ptr_off + 1 >=
static_cast<int>(rom_data.size())) {
3518 const uint16_t item_ptr =
3519 (
static_cast<uint16_t
>(rom_data[ptr_off + 1]) << 8) | rom_data[ptr_off];
3520 if (item_ptr < 0x8000) {
3523 const int item_addr =
static_cast<int>(
SnesToPc(0x010000 | item_ptr));
3524 return item_addr >= 0 && item_addr < static_cast<int>(rom_data.size())
3530 const std::vector<uint8_t>& rom_data,
int room_id) {
3532 static_cast<int>(rom_data.size())) {
3533 return absl::OutOfRangeError(
"Room items pointer table out of range");
3536 return absl::OutOfRangeError(
"Room ID out of range");
3543 const int hard_end =
3547 return absl::FailedPreconditionError(
3548 "Room pot item pointer is null, invalid, or outside the item region");
3555template <
typename RoomLookup>
3557 RoomLookup&& room_lookup) {
3558 if (rom ==
nullptr || !rom->
is_loaded()) {
3559 return absl::InvalidArgumentError(
"ROM not loaded");
3561 const auto& rom_data = rom->
vector();
3564 return absl::OutOfRangeError(
"Chest pointers out of range");
3568 bool any_dirty =
false;
3569 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3570 const Room* room = room_lookup(room_id);
3572 dirty_rooms[room_id] = room;
3577 return absl::OkStatus();
3586 return absl::FailedPreconditionError(
3587 absl::StrFormat(
"Chest table byte length %d is invalid (capacity %d)",
3590 const int cpos =
static_cast<int>(write_ranges[1].first);
3591 const auto physical_records =
3592 ParsePhysicalRomChests(rom_data, cpos, byte_length);
3593 if (physical_records.size() !=
3595 return absl::OutOfRangeError(
"Chest data region is truncated");
3599 for (
const PhysicalChestRecord& record : physical_records) {
3601 ++old_counts[record.room_id()];
3605 size_t final_record_count = physical_records.size();
3606 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3607 if (dirty_rooms[room_id] ==
nullptr) {
3610 final_record_count -= old_counts[room_id];
3611 final_record_count += dirty_rooms[room_id]->GetChests().size();
3614 return absl::ResourceExhaustedError(absl::StrFormat(
3615 "Chest table has %d records; capacity is %d",
3619 std::vector<uint8_t> bytes;
3622 for (
const PhysicalChestRecord& record : physical_records) {
3623 const uint16_t room_id = record.room_id();
3624 const Room* dirty_room =
3626 if (dirty_room ==
nullptr) {
3627 AppendChestRecord(&bytes, record.word, record.item);
3631 const size_t occurrence = seen_counts[room_id]++;
3632 const auto& replacements = dirty_room->
GetChests();
3633 if (occurrence < replacements.size()) {
3634 AppendEditedChestRecord(&bytes, room_id, replacements[occurrence]);
3641 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3642 const Room* dirty_room = dirty_rooms[room_id];
3643 if (dirty_room ==
nullptr) {
3646 const auto& replacements = dirty_room->
GetChests();
3647 for (
size_t i = seen_counts[room_id]; i < replacements.size(); ++i) {
3648 AppendEditedChestRecord(&bytes, room_id, replacements[i]);
3653 return absl::InternalError(
"Chest save plan size mismatch");
3656 const bool length_changed = byte_length !=
static_cast<int>(bytes.size());
3657 const bool data_changed =
3658 !std::equal(bytes.begin(), bytes.end(), rom_data.begin() + cpos);
3661 "ChestTableLength"));
3669 if (length_changed) {
3671 static_cast<uint16_t
>(bytes.size())));
3673 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3674 if (dirty_rooms[room_id] !=
nullptr) {
3675 const_cast<Room*
>(dirty_rooms[room_id])->ClearChestsDirty();
3678 return absl::OkStatus();
3683 [&rooms](
int room_id) { return &rooms[room_id]; });
3687 const std::function<
const Room*(
int)>& room_lookup) {
3691template <
typename RoomLookup>
3693 Rom* rom,
int room_count, RoomLookup&& room_lookup,
3696 return absl::InvalidArgumentError(
"ROM not loaded");
3698 const auto& rom_data = rom->
vector();
3700 static_cast<int>(rom_data.size())) {
3701 return absl::OutOfRangeError(
"Room items pointer table out of range");
3705 if (repack_layout !=
nullptr) {
3706 std::vector<DungeonStreamReplacement> replacements;
3707 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3708 const Room* room = room_lookup(room_id);
3714 replacement.
room_id =
static_cast<uint32_t
>(room_id);
3718 replacement.
encoded_stream.push_back((item.position >> 8) & 0xFF);
3723 replacements.push_back(std::move(replacement));
3725 if (replacements.empty()) {
3726 return absl::OkStatus();
3735 if (
const Room* room = room_lookup(replacement.room_id);
3737 const_cast<Room*
>(room)->ClearPotItemsDirty();
3740 return absl::OkStatus();
3743 struct PendingPotItemWrite {
3746 std::vector<uint8_t> bytes;
3751 std::vector<PendingPotItemWrite> pending_writes;
3752 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3753 const Room* room = room_lookup(room_id);
3759 GetPotItemStreamInfo(rom_data, room_id));
3760 if (stream_info.shared) {
3761 return absl::FailedPreconditionError(absl::StrFormat(
3762 "Room %d pot item stream at PC 0x%06X is shared; repacking is "
3764 room_id, stream_info.address));
3766 if (stream_info.capacity() <= 0) {
3767 return absl::FailedPreconditionError(absl::StrFormat(
3768 "Room %d pot item stream has no safe physical boundary", room_id));
3771 PendingPotItemWrite pending;
3772 pending.room_id = room_id;
3773 pending.address = stream_info.address;
3775 pending.bytes.push_back(pi.position & 0xFF);
3776 pending.bytes.push_back((pi.position >> 8) & 0xFF);
3777 pending.bytes.push_back(pi.item);
3779 pending.bytes.push_back(0xFF);
3780 pending.bytes.push_back(0xFF);
3781 if (
static_cast<int>(pending.bytes.size()) > stream_info.capacity()) {
3782 return absl::ResourceExhaustedError(absl::StrFormat(
3783 "Room %d pot item data too large! Size: %d, Available: %d", room_id,
3784 static_cast<int>(pending.bytes.size()), stream_info.capacity()));
3786 pending_writes.push_back(std::move(pending));
3789 for (
const auto& pending : pending_writes) {
3790 const bool data_changed =
3791 !std::equal(pending.bytes.begin(), pending.bytes.end(),
3792 rom_data.begin() + pending.address);
3798 for (
const auto& pending : pending_writes) {
3799 if (
const Room* room = room_lookup(pending.room_id); room !=
nullptr) {
3800 const_cast<Room*
>(room)->ClearPotItemsDirty();
3803 return absl::OkStatus();
3808 [&rooms](
int room_id) { return &rooms[room_id]; });
3814 rom,
static_cast<int>(rooms.size()),
3815 [&rooms](
int room_id) { return &rooms[room_id]; }, repack_layout);
3819 Rom* rom,
int room_count,
3820 const std::function<
const Room*(
int)>& room_lookup) {
3825 const std::function<
const Room*(
int)>& room_lookup,
3831 auto rom_data =
rom()->vector();
3852 const int kRegionSize = 0x80;
3855 std::vector<uint8_t> blocks_data(blocks_count, 0);
3856 for (
int r = 0; r < 4; ++r) {
3857 const int slot = kPointerSlots[r];
3858 if (slot + 2 >=
static_cast<int>(rom_data.size())) {
3859 LOG_WARN(
"Room",
"LoadBlocks: pointer slot %d out of range", r);
3862 const absl::Status operand_status =
3863 ValidateBlocksLoaderPointerOperand(rom_data, slot);
3864 if (!operand_status.ok()) {
3866 std::string(operand_status.message()).c_str());
3870 (rom_data[slot + 2] << 16) | (rom_data[slot + 1] << 8) | rom_data[slot];
3872 const int off = r * kRegionSize;
3873 const int len = std::min(kRegionSize, blocks_count - off);
3876 if (pc < 0 || pc + len >
static_cast<int>(rom_data.size())) {
3877 LOG_WARN(
"Room",
"LoadBlocks: region %d data out of range", r);
3880 std::copy_n(rom_data.begin() + pc, len, blocks_data.begin() + off);
3889 return (obj.options() & ObjectOption::Block) !=
3890 ObjectOption::Nothing;
3902 for (
int i = 0; i + 3 < blocks_count; i += 4) {
3904 blocks_data[i + 2], blocks_data[i + 3]};
3920 LOG_DEBUG(
"Room",
"Loaded block at (%d,%d) draw_layer=%d behavior_layer=%d",
3929 auto rom_data =
rom()->vector();
3945 int ptr_addr = table_addr + (
room_id_ * 2);
3946 if (ptr_addr + 1 >=
static_cast<int>(rom_data.size()))
3949 uint16_t item_ptr = (rom_data[ptr_addr + 1] << 8) | rom_data[ptr_addr];
3952 int item_addr =
SnesToPc(0x010000 | item_ptr);
3955 while (item_addr + 2 <
static_cast<int>(rom_data.size())) {
3957 uint16_t position = (rom_data[item_addr + 1] << 8) | rom_data[item_addr];
3960 if (position == 0xFFFF)
3964 uint8_t item_type = rom_data[item_addr + 2];
3968 pot_item.
item = item_type;
3978 auto rom_data =
rom()->vector();
3989 const int max_offset = rom_data[
kPitCount];
3990 const int pit_entries = max_offset / 2 + 1;
3992 const int pit_ptr = (rom_data[
kPitPointer + 2] << 16) |
3996 "LoadPits: room_id=%d, RoomsWithPitDamage entries=%d, "
3997 "table_snes=0x%06X",
4006 LOG_DEBUG(
"Room",
"Per-room pit destination: target=%d, target_layer=%d",
4021 for (
const auto& sprite :
sprites_) {
4022 if (sprite.IsOverlord()) {
4032 for (
const auto& door :
doors_) {
4034 const bool is_special = [&]() ->
bool {
4035 switch (door.type) {
4066 auto options = obj.options();
4079 if (obj.id_ == 0x11E || obj.id_ == 0x11F) {
4084 if (obj.id_ >= 0xF83 && obj.id_ <= 0xF8F) {
4091 if ((obj.id_ >= 0x130 && obj.id_ <= 0x135) || obj.id_ == 0x139 ||
4092 obj.id_ == 0x13A || obj.id_ == 0x13B) {
4096 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 ClearBG1RevealMask()
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.
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 SetBG1RevealMaskSource(gfx::BG1RevealMaskSource source)
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_room_event_indices=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)
void SetCurrentRenderPalette(const std::vector< SDL_Color > &palette)
absl::Status SaveToRom(Rom *rom) const
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_block_behavior_layer(uint8_t layer)
static constexpr int kBlockLoadOrderNew
void set_block_load_order(int order)
void set_options(ObjectOption options)
bool ValidateObject(const RoomObject &object) const
SaveDirtyState save_dirty_state_
std::vector< RoomObject > tile_objects_
gfx::BackgroundBuffer object_bg1_buffer_
void SetTag2Direct(TagKey tag2)
bool HasExceededLimits() const
Check if any object limits are exceeded.
void ClearObjectStreamHeaderDirty()
uint8_t render_entrance_blockset_
const CustomCollisionMap & custom_collision() const
absl::Status UpdateObject(size_t index, const RoomObject &object)
void ClearSaveDirtyState()
void SetStair4Target(uint8_t target)
void SetPitsTarget(uint8_t target)
void SetIsLight(bool is_light)
void EnsureSpritesLoaded()
gfx::BackgroundBuffer bg2_buffer_
uint8_t resolved_main_blockset_
const std::vector< chest_data > & GetChests() const
uint8_t cached_floor2_graphics_
std::vector< zelda3::Sprite > sprites_
CustomCollisionMap custom_collision_
void MarkSaveDirtyForTileObject(const RoomObject &object)
void SetLoaded(bool loaded)
void ClearObjectStreamDirty()
void ClearCustomCollisionDirty()
void CopyRoomGraphicsToBuffer()
bool custom_collision_dirty() const
zelda3_version_pointers version_constants() const
std::vector< Door > doors_
void SetStaircaseRoom(int index, uint8_t room)
static constexpr uint8_t kObjectHeaderFloor1Dirty
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_
static constexpr uint8_t kObjectHeaderFloor2Dirty
void ReloadGraphics(std::optional< uint8_t > entrance_blockset=std::nullopt)
bool pot_items_dirty() const
void SetTag1Direct(TagKey tag1)
void SetHolewarp(uint8_t hw)
void SetStair2Target(uint8_t target)
bool object_stream_dirty() const
bool sprites_dirty() const
void SetCollision(CollisionKey collision)
bool object_stream_header_dirty() const
gfx::BackgroundBuffer bg1_buffer_
void SetStaircasePlane(int index, uint8_t plane)
absl::Status SaveObjects(const DungeonStreamLayout *layout=nullptr)
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 &&)
bool chests_dirty() const
uint64_t composite_signature_
CollisionKey collision() const
void LoadRoomGraphics(std::optional< uint8_t > entrance_blockset=std::nullopt)
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)
absl::Status SaveObjectStreamHeader(const DungeonStreamLayout *layout=nullptr)
gfx::BackgroundBuffer object_bg2_buffer_
void ClearWaterFillDirty()
std::array< uint8_t, 16 > blocks_
void SetTag1(TagKey tag1)
void SetBackgroundTileset(uint8_t tileset)
void SetStair3TargetLayer(uint8_t layer)
void SetRenderEntranceBlockset(uint8_t entrance_blockset)
absl::Status SaveSprites(const DungeonStreamLayout *layout=nullptr)
void PrepareForRender(std::optional< uint8_t > entrance_blockset=std::nullopt)
const std::vector< RoomObject > & GetTileObjects() const
void SetLayerMerging(LayerMergeType merging)
void SetPitsTargetLayer(uint8_t layer)
bool blocks_dirty() const
void SetSpriteTileset(uint8_t tileset)
void SetStair1Target(uint8_t target)
void SetBg2(background2 bg2)
static constexpr uint8_t kObjectHeaderLayoutDirty
bool torches_dirty() const
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_
bool AreTorchesLoaded() const
uint8_t cached_spriteset_
std::array< uint8_t, 0x10000 > current_gfx16_
std::vector< staircase > z3_staircases_
std::vector< PotItem > pot_items_
bool AreBlocksLoaded() const
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,...)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
absl::Status GetObjectPointerTablePc(const std::vector< uint8_t > &rom_data, int *table_pc)
void AppendChestRecord(std::vector< uint8_t > *bytes, uint16_t word, uint8_t item)
constexpr int kTorchesMaxSize
absl::Status ValidateSpecialObjectDrawLayerSelector(const RoomObject &object, int room_id, const char *object_type)
int ReadRoomPotItemAddressPc(const std::vector< uint8_t > &rom_data, int room_id)
absl::Status RelocateDungeonStream(Rom *rom, int room_id, DungeonStreamKind expected_kind, const DungeonStreamLayout &layout, std::vector< uint8_t > encoded_stream)
bool RoomUsesTrackCornerAliases(const std::vector< RoomObject > &objects)
const LayerMergeType & LayerMergeFromHeaderByte(uint8_t byte0)
std::vector< TorchSegment > ParseRomTorchSegments(const std::vector< uint8_t > &rom_data, int bytes_count)
absl::Status GetSpritePointerTablePc(const std::vector< uint8_t > &rom_data, int *table_pc)
absl::StatusOr< bool > DungeonStreamRequiresCopyOnWrite(const Rom &rom, int room_id, DungeonStreamKind expected_kind, const DungeonStreamLayout &layout, size_t replacement_size)
int ReadRoomObjectAddressPc(const std::vector< uint8_t > &rom_data, int table_pc, int room_id)
int ReadRoomSpriteAddressPc(const std::vector< uint8_t > &rom_data, int table_pc, int room_id)
absl::Status PreflightBlocksLoaderDestinations(const std::vector< uint8_t > &rom_data, std::array< int, 4 > *destination_pcs)
absl::StatusOr< PhysicalStreamInfo > GetObjectStreamInfo(const std::vector< uint8_t > &rom_data, int room_id)
absl::StatusOr< PhysicalStreamInfo > GetSpriteStreamInfo(const std::vector< uint8_t > &rom_data, int room_id)
PhysicalStreamInfo AnalyzePhysicalStream(const std::vector< int > &room_addresses, int room_id, int known_region_end=-1)
constexpr int kBlocksRegionSize
std::vector< uint8_t > EncodeTorchSegmentForRoom(int room_id, const Room &room)
bool HalfOpenRangesOverlap(int first_begin, int first_end, int second_begin, int second_end)
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)
absl::Status ValidateBlocksLoaderPointerOperand(const std::vector< uint8_t > &rom_data, int operand_pc)
bool IsDarkRoomHeaderByte(uint8_t byte0)
void AppendEditedChestRecord(std::vector< uint8_t > *bytes, int room_id, const chest_data &chest)
constexpr std::array< int, 4 > kBlocksPointerSlots
std::vector< PhysicalChestRecord > ParsePhysicalRomChests(const std::vector< uint8_t > &rom_data, int cpos, int byte_length)
background2 Background2FromHeaderByte(uint8_t byte0)
uint32_t ReadRoomObjectAddressSnes(const std::vector< uint8_t > &rom_data, int table_pc, int room_id)
uint8_t Layer2ModeFromHeaderByte(uint8_t byte0)
absl::StatusOr< PhysicalStreamInfo > GetPotItemStreamInfo(const std::vector< uint8_t > &rom_data, int room_id)
absl::Status ValidateLightableTorchForSave(const RoomObject &object, int room_id)
constexpr int kBlocksPointer4
constexpr int kSpritesDataEndExclusive
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.
PushableBlockBytes EncodePushableBlockEntry(const PushableBlockEntry &entry)
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)
absl::Status SaveAllPotItemsImpl(Rom *rom, int room_count, RoomLookup &&room_lookup, const DungeonStreamLayout *repack_layout=nullptr)
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)
LightableTorchEntry DecodeLightableTorchEntry(const LightableTorchBytes &bytes)
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetChestTableWriteRanges(const Rom *rom)
constexpr int kRoomItemsDataEnd
absl::Status SaveAllTorches(Rom *rom, absl::Span< const Room > rooms)
constexpr int kTileAddress
LightableTorchBytes EncodeLightableTorchEntry(const LightableTorchEntry &entry)
std::vector< SDL_Color > BuildDungeonRenderPalette(const gfx::SnesPalette &dungeon_palette, const gfx::SnesPalette *hud_palette)
absl::StatusOr< DungeonStreamWritePlan > PlanDungeonStreamRepack(const DungeonStreamInventory &inventory, const std::vector< DungeonStreamReplacement > &requested_replacements)
constexpr int kRoomsSpritePointer
constexpr int kTileAddressFloor
constexpr int GetDungeonObjectDataRegionEnd(int pc_address)
absl::Status SaveAllPits(Rom *rom)
constexpr int kChestsDataPointer1
constexpr int kBlocksLength
absl::Status SaveAllBlocks(Rom *rom)
constexpr int kBlocksPointer1
constexpr int kChestTableCapacityRecords
constexpr int kRoomItemsPointers
constexpr int kGfxBufferRoomSpriteStride
absl::StatusOr< CustomCollisionMap > LoadCustomCollisionMap(Rom *rom, int room_id)
constexpr int kChestTableRecordSize
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
absl::Status ApplyDungeonStreamWritePlan(Rom *rom, const DungeonStreamWritePlan &plan)
bool HasExceededLimits(const std::map< DungeonLimit, int > &counts)
constexpr uint32_t kDungeonPalettePointerTable
constexpr uint16_t kStairsObjects[]
constexpr int kChestTableCapacityBytes
absl::Status SaveAllChestsImpl(Rom *rom, int room_count, RoomLookup &&room_lookup)
absl::StatusOr< DungeonStreamWritePlan > PlanDungeonStreamWrites(const DungeonStreamInventory &inventory, const std::vector< DungeonStreamReplacement > &requested_replacements)
absl::StatusOr< DungeonStreamInventory > InventoryDungeonStreams(const Rom &rom, const DungeonStreamLayout &requested_layout)
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
PushableBlockEntry DecodePushableBlockEntry(const PushableBlockBytes &bytes)
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.
Room transition destination.
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::vector< DungeonStreamAliasGroup > aliases
DungeonStreamLayout layout
std::vector< DungeonStreamIssue > issues
std::vector< DungeonStreamOverlap > overlaps
std::vector< DungeonStreamRecord > streams
std::vector< DungeonStreamPcRange > data_ranges
std::vector< uint8_t > encoded_stream
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
static Door FromRomBytes(uint8_t b1, uint8_t b2)
uint8_t object_stream_header
std::vector< uint8_t > bytes
Public YAZE API umbrella header.