13#include <unordered_map>
14#include <unordered_set>
18#include "absl/cleanup/cleanup.h"
19#include "absl/strings/str_cat.h"
20#include "absl/strings/str_format.h"
48 static std::atomic<uint64_t> revision{0};
49 return revision.fetch_add(1, std::memory_order_relaxed) + 1;
53 static std::atomic<uint64_t> revision{0};
54 return revision.fetch_add(1, std::memory_order_relaxed) + 1;
58 return static_cast<uint8_t
>((byte0 >> 5) & 0x07);
62 return (byte0 & 0x01) != 0;
73 return background2::DarkRoom;
78template <
typename WriteColor>
81 WriteColor write_color) {
82 if (hud_palette !=
nullptr) {
83 const size_t hud_count = std::min<size_t>(hud_palette->
size(), 32);
84 for (
size_t i = 0; i < hud_count; ++i) {
85 write_color(
static_cast<int>(i), (*hud_palette)[i], -1);
89 constexpr int kColorsPerRomBank = 15;
90 constexpr int kIndicesPerSdlBank = 16;
91 constexpr int kNumRomBanks = 6;
92 constexpr int kDungeonBankStart = 2;
93 for (
int rom_bank = 0; rom_bank < kNumRomBanks; ++rom_bank) {
94 const int sdl_bank = rom_bank + kDungeonBankStart;
95 for (
int color = 0; color < kColorsPerRomBank; ++color) {
96 const size_t rom_index =
97 static_cast<size_t>(rom_bank * kColorsPerRomBank + color);
98 if (rom_index >= dungeon_palette.
size()) {
101 const int dst_index = sdl_bank * kIndicesPerSdlBank + color + 1;
102 write_color(dst_index, dungeon_palette[rom_index],
103 static_cast<int>(rom_index));
109 size_t requested_index) {
113 const size_t resolved_index =
114 requested_index < group.
size() ? requested_index : 0;
119 size_t max_colors,
size_t destination_offset,
120 std::array<SDL_Color, 256>* colors) {
121 if (source ==
nullptr || colors ==
nullptr ||
122 destination_offset >= colors->
size() || source_offset >= source->
size()) {
126 const size_t count = std::min({max_colors, source->
size() - source_offset,
127 colors->size() - destination_offset});
128 for (
size_t i = 0; i < count; ++i) {
129 const auto rgb = (*source)[source_offset + i].rom_color();
130 (*colors)[destination_offset + i] = {
static_cast<Uint8
>(rgb.red),
131 static_cast<Uint8
>(rgb.green),
132 static_cast<Uint8
>(rgb.blue), 255};
144 std::vector<SDL_Color> colors(256, {0, 0, 0, 0});
145 PopulateDungeonRenderPaletteRows(
146 dungeon_palette, hud_palette,
147 [&](
int dst_index,
const gfx::SnesColor& color,
int source_index) {
148 if (dst_index < 0 || dst_index >=
static_cast<int>(colors.size())) {
151 if (addresses && source_index >= 0 && dst_index < 128) {
155 const ImVec4 rgb = color.
rgb();
156 colors[dst_index] = {
static_cast<Uint8
>(rgb.x),
157 static_cast<Uint8
>(rgb.y),
158 static_cast<Uint8
>(rgb.z), 255};
160 colors[255] = {0, 0, 0, 0};
167 constexpr int kRenderPaletteRows = 8;
168 constexpr int kColorsPerRow = 16;
171 for (
int row = 0; row < kRenderPaletteRows; ++row) {
173 for (
int color = 0; color < kColorsPerRow; ++color) {
179 PopulateDungeonRenderPaletteRows(
180 dungeon_palette, hud_palette,
182 if (dst_index < 0 || dst_index >= kRenderPaletteRows * kColorsPerRow) {
185 group.
SetColor(dst_index / kColorsPerRow, dst_index % kColorsPerRow,
202 constexpr size_t kCgramRowSize = 16;
203 constexpr size_t kHalfPaletteColorCount = 7;
204 constexpr size_t kFullPaletteColorCount = 15;
205 constexpr size_t kDefaultOverworldEnvironmentPalette = 0x07;
206 constexpr size_t kDefaultUnderworldEnvironmentPalette = 0x0A;
208 std::array<SDL_Color, 256> colors{};
209 if (game_data ==
nullptr) {
216 std::array<uint8_t, 4> palette_set{};
222 CopyPaletteToCgram(ResolvePaletteOrFirst(groups.sprites_aux1, palette_set[1]),
223 0, kHalfPaletteColorCount, 8 * kCgramRowSize + 1, &colors);
224 CopyPaletteToCgram(ResolvePaletteOrFirst(groups.sprites_aux2,
225 kDefaultOverworldEnvironmentPalette),
226 0, kHalfPaletteColorCount, 8 * kCgramRowSize + 9, &colors);
233 const auto* global = ResolvePaletteOrFirst(groups.global_sprites, 0);
234 for (
size_t row = 9; row <= 12; ++row) {
235 CopyPaletteToCgram(global, (row - 9) * kFullPaletteColorCount,
236 kFullPaletteColorCount, row * kCgramRowSize + 1,
240 CopyPaletteToCgram(ResolvePaletteOrFirst(groups.sprites_aux3, palette_set[2]),
241 0, kHalfPaletteColorCount, 13 * kCgramRowSize + 1,
243 CopyPaletteToCgram(ResolvePaletteOrFirst(groups.sprites_aux3, palette_set[3]),
244 0, kHalfPaletteColorCount, 14 * kCgramRowSize + 1,
247 ResolvePaletteOrFirst(groups.sprites_aux2,
248 kDefaultUnderworldEnvironmentPalette),
249 0, kHalfPaletteColorCount, 14 * kCgramRowSize + 9, &colors);
250 CopyPaletteToCgram(ResolvePaletteOrFirst(groups.armors, 0), 0,
251 kFullPaletteColorCount, 15 * kCgramRowSize + 1, &colors);
259 PopulateDungeonRenderPaletteRows(
260 dungeon_palette, hud_palette,
262 if (dst_index < 0 || dst_index >=
static_cast<int>(cgram.size())) {
265 cgram[dst_index] = color.
snes();
276 "Light Torch to See Floor",
281 "NW Kill Enemy to Open",
282 "NE Kill Enemy to Open",
283 "SW Kill Enemy to Open",
284 "SE Kill Enemy to Open",
285 "W Kill Enemy to Open",
286 "E Kill Enemy to Open",
287 "N Kill Enemy to Open",
288 "S Kill Enemy to Open",
289 "Clear Quadrant to Open",
290 "Clear Full Tile to Open",
291 "NW Push Block to Open",
292 "NE Push Block to Open",
293 "SW Push Block to Open",
294 "SE Push Block to Open",
295 "W Push Block to Open",
296 "E Push Block to Open",
297 "N Push Block to Open",
298 "S Push Block to Open",
299 "Push Block to Open",
300 "Pull Lever to Open",
301 "Collect Prize to Open",
302 "Hold Switch Open Door",
303 "Toggle Switch to Open Door",
312 "Push Switch Exploding Wall",
314 "Open Chest (Holes 0)",
317 "Defeat Boss for Dungeon Prize",
318 "SE Kill Enemy to Push Block",
319 "Trigger Switch Chest",
320 "Pull Lever Exploding Wall",
321 "NW Kill Enemy for Chest",
322 "NE Kill Enemy for Chest",
323 "SW Kill Enemy for Chest",
324 "SE Kill Enemy for Chest",
325 "W Kill Enemy for Chest",
326 "E Kill Enemy for Chest",
327 "N Kill Enemy for Chest",
328 "S Kill Enemy for Chest",
329 "Clear Quadrant for Chest",
330 "Clear Full Tile for Chest",
331 "Light Torches to Open",
339 "Open Chest for Holes 8",
340 "Push Block for Chest",
341 "Clear Room for Triforce Door",
342 "Light Torches for Chest",
349 int physical_end = -1;
353 return physical_end > address ? physical_end - address : 0;
359 int known_region_end = -1) {
361 if (room_id < 0 || room_id >=
static_cast<int>(room_addresses.size())) {
365 info.
address = room_addresses[room_id];
370 int next_address = std::numeric_limits<int>::max();
371 for (
int other_room_id = 0;
372 other_room_id < static_cast<int>(room_addresses.size());
374 if (other_room_id == room_id || room_addresses[other_room_id] < 0) {
377 const int other_address = room_addresses[other_room_id];
378 if (other_address == info.
address) {
380 }
else if (other_address > info.
address) {
381 next_address = std::min(next_address, other_address);
385 if (known_region_end > info.
address) {
386 next_address = std::min(next_address, known_region_end);
388 if (next_address == std::numeric_limits<int>::max()) {
396 constexpr int kLoRomBankSize = 0x8000;
397 const int bank_end = ((info.
address / kLoRomBankSize) + 1) * kLoRomBankSize;
398 if (next_address > bank_end) {
407 if (table_pc ==
nullptr) {
408 return absl::InvalidArgumentError(
"table_pc pointer is null");
411 return absl::OutOfRangeError(
412 "Object pointer table address is out of range");
415 const uint32_t table_snes =
419 const int pc =
static_cast<int>(
SnesToPc(table_snes));
420 if (pc < 0 || pc + (
kNumberOfRooms * 3) >
static_cast<int>(rom_data.size())) {
421 return absl::OutOfRangeError(
"Object pointer table is out of range");
425 return absl::OkStatus();
429 int table_pc,
int room_id) {
433 const int ptr_off = table_pc + (room_id * 3);
434 if (ptr_off < 0 || ptr_off + 2 >=
static_cast<int>(rom_data.size())) {
437 return (
static_cast<uint32_t
>(rom_data[ptr_off + 2]) << 16) |
438 (
static_cast<uint32_t
>(rom_data[ptr_off + 1]) << 8) |
445 if ((snes & 0xFFFF) < 0x8000) {
448 const int pc =
static_cast<int>(
SnesToPc(snes));
449 return pc >= 0 && pc < static_cast<int>(rom_data.size()) ? pc : -1;
453 const std::vector<uint8_t>& rom_data,
int room_id) {
455 return absl::OutOfRangeError(
"Room ID out of range");
467 return absl::OutOfRangeError(
"Object stream pointer is out of range");
474 if (table_pc ==
nullptr) {
475 return absl::InvalidArgumentError(
"table_pc pointer is null");
478 return absl::OutOfRangeError(
479 "Sprite pointer table address is out of range");
485 if (pc < 0 || pc + (
kNumberOfRooms * 2) >
static_cast<int>(rom_data.size())) {
486 return absl::OutOfRangeError(
"Sprite pointer table is out of range");
490 return absl::OkStatus();
498 const int ptr_off = table_pc + (room_id * 2);
499 if (ptr_off < 0 || ptr_off + 1 >=
static_cast<int>(rom_data.size())) {
503 const uint16_t pointer =
504 (
static_cast<uint16_t
>(rom_data[ptr_off + 1]) << 8) | rom_data[ptr_off];
505 if (pointer < 0x8000) {
508 const int sprite_address =
static_cast<int>(
SnesToPc((0x09 << 16) | pointer));
509 return sprite_address >= 0 &&
510 sprite_address < static_cast<int>(rom_data.size())
516 const std::vector<uint8_t>& rom_data,
int room_id) {
518 return absl::OutOfRangeError(
"Room ID out of range");
531 return absl::OutOfRangeError(
"Sprite stream pointer is out of range");
537 int sprite_address,
int hard_end) {
538 if (sprite_address < 0 || sprite_address >= hard_end ||
539 sprite_address >=
static_cast<int>(rom_data.size())) {
543 int cursor = sprite_address + 1;
544 while (cursor < hard_end) {
545 if (rom_data[cursor] == 0xFF) {
549 if (cursor + 2 >= hard_end) {
556 return std::max(0, cursor - sprite_address);
562 if (layout.
kind != expected_kind) {
563 return absl::InvalidArgumentError(absl::StrFormat(
564 "Room %d relocation layout has the wrong dungeon stream kind",
573 std::move(encoded_stream)}}));
580 if (layout.
kind != expected_kind) {
581 return absl::InvalidArgumentError(absl::StrFormat(
582 "Room %d save layout has the wrong dungeon stream kind", room_id));
584 if (room_id < 0 ||
static_cast<uint32_t
>(room_id) >= layout.
pointer_count) {
585 return absl::OutOfRangeError(
586 "Room ID is outside the dungeon stream layout");
591 if (!inventory.
ok()) {
592 return absl::FailedPreconditionError(absl::StrFormat(
593 "Dungeon stream inventory has %zu issue(s); refusing an in-place "
595 inventory.
issues.size()));
598 const auto contains_room = [room_id](
const std::vector<uint32_t>& owners) {
599 return std::find(owners.begin(), owners.end(),
600 static_cast<uint32_t
>(room_id)) != owners.end();
602 for (
const auto& alias : inventory.
aliases) {
603 if (contains_room(alias.room_ids)) {
607 for (
const auto& overlap : inventory.
overlaps) {
608 if (contains_room(overlap.first_room_ids) ||
609 contains_room(overlap.second_room_ids)) {
614 const auto& record = inventory.
streams[room_id];
615 const uint64_t replacement_end =
616 static_cast<uint64_t
>(record.data_pc) + replacement_size;
617 const uint32_t bank_end = ((record.data_pc / 0x8000u) + 1u) * 0x8000u;
618 const bool stays_in_declared_data =
619 replacement_end <= bank_end &&
622 return range.begin <= record.data_pc &&
623 replacement_end <= range.end;
625 return !stays_in_declared_data;
632 if (!rom || !rom->
is_loaded() || rom->
size() == 0 || room_id < 0 ||
637 const auto& rom_data = rom->
vector();
639 if (!GetObjectPointerTablePc(rom_data, &table_pc).ok()) {
642 room_size.room_size_pointer =
643 ReadRoomObjectAddressSnes(rom_data, table_pc, room_id);
645 auto stream_info = GetObjectStreamInfo(rom_data, room_id);
646 if (!stream_info.ok() || stream_info->shared) {
649 room_size.room_size = stream_info->capacity();
681 Room room(room_id, rom);
697 header_pointer =
SnesToPc(header_pointer);
706 int table_offset = (header_pointer) + (room_id * 2);
707 if (table_offset < 0 || table_offset + 1 >=
static_cast<int>(rom->
size())) {
712 (rom->
data()[table_offset + 1] << 8) +
713 rom->
data()[table_offset];
715 auto header_location =
SnesToPc(address);
718 if (header_location < 0 ||
719 header_location + 13 >=
static_cast<int>(rom->
size())) {
723 const uint8_t header_byte0 = rom->
data()[header_location];
726 room.
SetBg2(Background2FromHeaderByte(header_byte0));
728 room.
SetIsLight(IsDarkRoomHeaderByte(header_byte0));
729 room.
SetIsDark(IsDarkRoomHeaderByte(header_byte0));
764 header_pointer_2 =
SnesToPc(header_pointer_2);
773 int table_offset_2 = (header_pointer_2) + (room_id * 2);
774 if (table_offset_2 < 0 ||
775 table_offset_2 + 1 >=
static_cast<int>(rom->
size())) {
780 (rom->
data()[table_offset_2 + 1] << 8) +
781 rom->
data()[table_offset_2];
784 if (msg_addr >= 0 && msg_addr + 1 <
static_cast<int>(rom->
size())) {
785 uint16_t msg_val = (rom->
data()[msg_addr + 1] << 8) | rom->
data()[msg_addr];
793 if (hpos < 0 || hpos + 14 >=
static_cast<int>(rom->
size())) {
797 uint8_t b = rom->
data()[hpos];
823 b = rom->
data()[hpos];
853 game_data_(game_data),
915 for (
int index = 0; index < 4; ++index) {
925 const int num_palettes =
static_cast<int>(group.size());
926 if (num_palettes == 0)
930 if (palette_ < game_data_->paletteset_ids.size() &&
938 if (id < 0 || id >= num_palettes)
961 const uint8_t requested_main_blockset =
969 const uint8_t main_blockset = resolution.main_blockset;
972 "Room %d: invalid main fallback blockset %d; using main group 0",
975 if (requested_main_blockset != 0xFF &&
978 "Room %d: entrance main blockset %d out of range; using %d",
979 room_id_, requested_main_blockset, main_blockset);
984 "Room %d: room_blockset=%d, main_blockset=%d, spriteset=%d, "
988 for (
int i = 0; i < 8; i++) {
990 if (i >= 3 && i <= 6 &&
blockset_ < room_gfx.size()) {
991 const uint8_t room_sheet = room_gfx[
blockset_][i - 3];
992 if (room_sheet != 0) {
998 LOG_WARN(
"Room",
"Room %d: room blockset %d out of range; skipped $0AA2",
1006 const size_t sprite_gfx_index =
static_cast<size_t>(
spriteset_) + 64;
1007 if (sprite_gfx_index < sprite_gfx.size()) {
1008 for (
int i = 0; i < 4; i++) {
1010 static_cast<uint8_t
>(sprite_gfx[sprite_gfx_index][i] + 115);
1014 "Room %d: spriteset %d out of range; clearing sprite sheets",
1016 for (
int i = 0; i < 4; i++) {
1021 LOG_DEBUG(
"Room",
"Sheet IDs BG[0-7]: %d %d %d %d %d %d %d %d",
blocks_[0],
1048 if (entrance_blockset.has_value()) {
1059 if (entrance_blockset.has_value()) {
1065 const auto previous_blocks =
blocks_;
1080 !bg2_bmp.is_active() || bg2_bmp.width() == 0) {
1098 const uint8_t active_main_blockset =
1108 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: ROM not loaded");
1113 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: GameData not set");
1117 if (gfx_buffer_data->empty()) {
1118 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: Graphics buffer is empty");
1122 LOG_DEBUG(
"Room",
"Room %d: Copying 8BPP graphics (buffer size: %zu)",
1123 room_id_, gfx_buffer_data->size());
1126 const absl::Cleanup publish_revision = [
this] {
1133 for (
int block = 0; block < 16; block++) {
1134 int sheet_id =
blocks_[block];
1137 if (sheet_id >= 223) {
1138 LOG_WARN(
"Room",
"Invalid sheet index %d for block %d", sheet_id, block);
1145 if (src ==
nullptr) {
1146 LOG_ERROR(
"Room",
"Graphics offset out of bounds: %d (size: %zu)",
1147 sheet_id * 4096, gfx_buffer_data->size());
1153 int dest_index_base = block * 4096;
1161 memcpy(dst, src, 4096);
1164 for (
int i = 0; i < 4096; ++i) {
1166 if (p != 0 && p < 8) {
1180 constexpr size_t kSheetBytes = 4096;
1187 it->second.size() == kSheetBytes) {
1188 return it->second.data();
1194 const size_t offset =
static_cast<size_t>(sheet_id) * kSheetBytes;
1195 if (offset + kSheetBytes > graphics.size()) {
1198 return graphics.data() + offset;
1206 const auto sheet =
static_cast<uint16_t
>(sheet_id);
1210 recorded = revision;
1256 bool properties_changed =
false;
1270 properties_changed =
true;
1280 properties_changed =
true;
1288 if (bg1_bmp.is_active() && bg1_bmp.width() > 0 && bg2_bmp.is_active() &&
1289 bg2_bmp.width() > 0) {
1291 "Room %d: No changes detected, skipping render",
room_id_);
1297 "Room %d: Rendering graphics (dirty_flags: g=%d o=%d l=%d t=%d)",
1316 "Room %d: floor1=%d, floor2=%d, blocks_size=%zu",
room_id_,
1324 bool need_floor_draw =
1330 if (!bg1_bmp.is_active() || bg1_bmp.width() == 0 || !bg2_bmp.is_active() ||
1331 bg2_bmp.width() == 0) {
1332 need_floor_draw =
true;
1334 "Room %d: Bitmaps not created yet, forcing floor draw",
room_id_);
1337 if (need_floor_draw) {
1339 buffer->EnsureBitmapInitialized();
1340 buffer->bitmap().Fill(255);
1341 buffer->ClearBuffer();
1354 bool need_bg_draw = was_graphics_dirty || need_floor_draw;
1366 if (was_layout_dirty || need_floor_draw) {
1375 if (dungeon_pal_group.empty())
1379 auto bg1_palette = dungeon_pal_group[palette_id];
1388 LOG_DEBUG(
"Room",
"RenderRoomGraphics: Palette ID=%d, Size=%zu", palette_id,
1389 bg1_palette.size());
1390 if (!bg1_palette.empty()) {
1391 LOG_DEBUG(
"Room",
"RenderRoomGraphics: First color: R=%d G=%d B=%d",
1392 bg1_palette[0].rom_color().red, bg1_palette[0].rom_color().green,
1393 bg1_palette[0].rom_color().blue);
1396 if (bg1_palette.size() > 0) {
1397 std::optional<gfx::SnesPalette> hud_palette_storage;
1401 hud_palette = &*hud_palette_storage;
1429 auto set_dungeon_palette = [&](
gfx::Bitmap& bmp) {
1430 bmp.SetPalette(render_palette);
1431 if (bmp.surface()) {
1433 SDL_SetColorKey(bmp.surface(), SDL_TRUE, 255);
1434 SDL_SetSurfaceBlendMode(bmp.surface(), SDL_BLENDMODE_BLEND);
1438 set_dungeon_palette(bg1_bmp);
1439 set_dungeon_palette(bg2_bmp);
1444 auto* surface = bg1_bmp.surface();
1449 "Room::RenderRoomGraphics (BG1)", palette_id,
true);
1453 "Room::RenderRoomGraphics (after SetPalette)", surface);
1456 "Room::RenderRoomGraphics", palette_id,
false,
1457 "SDL surface has no palette!");
1471 if (bg2_bmp.surface()) {
1472 SDL_SetSurfaceAlphaMod(bg2_bmp.surface(), 128);
1480 if (bg2_bmp.surface()) {
1481 SDL_SetSurfaceBlendMode(bg2_bmp.surface(), SDL_BLENDMODE_ADD);
1497 if (bitmap->texture()) {
1503 release_texture(&bg1_bmp);
1504 release_texture(&bg2_bmp);
1519 "Texture commands queued for batch processing");
1527 LOG_DEBUG(
"Room",
"ROM not loaded, aborting");
1538 if (!layout_status.ok()) {
1540 layout_status.message().data());
1546 layout_objects.size());
1547 if (layout_objects.empty()) {
1555 LOG_DEBUG(
"RenderRoomGraphics",
"GameData not set, cannot render layout");
1561 if (dungeon_pal_group.empty())
1565 auto room_palette = dungeon_pal_group[palette_id];
1580 "RenderRoomGraphics",
"Layout Draw failed: %s",
1581 std::string(status.message().data(), status.message().size()).c_str());
1583 LOG_DEBUG(
"RenderRoomGraphics",
"Layout rendered with %zu objects",
1584 layout_objects.size());
1589 LOG_DEBUG(
"[RenderObjectsToBackground]",
1590 "Starting object rendering for room %d",
room_id_);
1593 LOG_DEBUG(
"[RenderObjectsToBackground]",
"ROM not loaded, aborting");
1602 bool bitmaps_exist = bg1_bmp.is_active() && bg1_bmp.width() > 0 &&
1603 bg2_bmp.is_active() && bg2_bmp.width() > 0;
1606 LOG_DEBUG(
"[RenderObjectsToBackground]",
1607 "Room %d: Objects not dirty, skipping render",
room_id_);
1613 LOG_DEBUG(
"[RenderObjectsToBackground]",
1614 "Room %d: Emulator rendering objects",
room_id_);
1619 if (dungeon_pal_group.empty())
1623 auto room_palette = dungeon_pal_group[palette_id];
1667 int layer0_count = 0, layer1_count = 0, layer2_count = 0;
1669 switch (obj.GetLayerValue()) {
1683 "Room %03X Object Stream Summary: Main=%d, BG2Overlay=%d, BG1Overlay=%d",
1684 room_id_, layer0_count, layer1_count, layer2_count);
1702 std::vector<std::vector<RoomObject>> by_list(3);
1714 uint8_t list_index = obj.GetLayerValue();
1715 if (list_index > 2) {
1720 by_list[list_index].push_back(std::move(render_obj));
1723 absl::Status status = absl::OkStatus();
1724 bool reset_room_events_for_next_chunk =
true;
1725 for (
int pass = 0; pass < 3; ++pass) {
1726 if (by_list[pass].empty()) {
1733 reset_room_events_for_next_chunk =
false;
1734 if (!chunk_status.ok() && status.ok()) {
1735 status = chunk_status;
1742 for (
int i = 0; i < static_cast<int>(
doors_.size()); ++i) {
1743 const auto& door =
doors_[i];
1745 door_def.
type = door.type;
1767 if (pot_item.item != 0) {
1769 int tile_x = pot_item.GetTileX();
1770 int tile_y = pot_item.GetTileY();
1777 for (
const auto& sprite :
sprites_) {
1778 if (sprite.key_drop() > 0) {
1784 uint8_t key_item = (sprite.key_drop() == 1) ? 0xFD : 0xFE;
1794 constexpr uint16_t kRoomDrawObj_PushableBlock = 0x0E52;
1795 constexpr uint16_t kRoomDrawObj_TorchUnlit = 0x0EC2;
1796 constexpr uint16_t kRoomDrawObj_TorchLit = 0x0ECA;
1803 static_cast<uint16_t
>(obj.id_), obj.x_, obj.y_, obj.layer_,
1808 const uint16_t off =
1809 obj.lit_ ? kRoomDrawObj_TorchLit : kRoomDrawObj_TorchUnlit;
1814 static_cast<uint16_t
>(obj.id_), obj.x_, obj.y_, obj.layer_, off,
1822 "[RenderObjectsToBackground]",
1823 "Room %03X: ObjectDrawer failed: %s (objects left dirty for retry)",
1825 std::string(status.message().data(), status.message().size()).c_str());
1832 LOG_DEBUG(
"[RenderObjectsToBackground]",
1833 "Room %d: Objects rendered successfully",
room_id_);
1844 constexpr size_t kFrameBytes = 1024;
1847 if (animated_frame_ < 0 || animated_frame_ >= 3) {
1850 bool copied_frame =
false;
1851 const absl::Cleanup publish_revision = [
this, &copied_frame] {
1856 const auto copy_frame = [&](uint8_t sheet,
size_t destination) {
1858 if (source !=
nullptr) {
1862 copied_frame =
true;
1869 copy_frame(0x5C, 0x1C0 * 64);
1874 const auto table_snes =
1876 if (!table_snes.ok() || (*table_snes & 0xFFFF) < 0x8000 ||
1877 (*table_snes >> 16) == 0x7E || (*table_snes >> 16) == 0x7F) {
1880 const uint8_t main_group =
1887 copy_frame(*sheet, 0x1B0 * 64);
1893 const auto& rom_data =
rom()->vector();
1899 object_pointer =
SnesToPc(object_pointer);
1902 if (object_pointer < 0 || object_pointer >= (
int)
rom_->
size()) {
1906 int room_address = object_pointer + (
room_id_ * 3);
1909 if (room_address < 0 || room_address + 2 >= (
int)
rom_->
size()) {
1913 int tile_address = (rom_data[room_address + 2] << 16) +
1914 (rom_data[room_address + 1] << 8) + rom_data[room_address];
1916 int objects_location =
SnesToPc(tile_address);
1919 if (objects_location < 0 || objects_location >= (
int)
rom_->
size()) {
1924 if (objects_location + 1 < (
int)
rom_->
size()) {
1927 static_cast<uint8_t
>(rom_data[objects_location] & 0x0F);
1929 static_cast<uint8_t
>((rom_data[objects_location] >> 4) & 0x0F);
1931 "Room %d: Set floor1_graphics_=%d, floor2_graphics_=%d",
1936 static_cast<uint8_t
>((rom_data[objects_location + 1] >> 2) & 0x07);
1962 const auto& rom_data =
rom()->vector();
1968 int nbr_of_staircase = 0;
1970 int pos = objects_location;
1976 bool end_read =
false;
1980 while (!end_read && pos < (
int)
rom_->
size()) {
1982 if (pos + 1 >= (
int)
rom_->
size()) {
1987 b2 = rom_data[pos + 1];
1994 if (b1 == 0xFF && b2 == 0xFF) {
1998 "Room",
"Room %03X: Object list transition to index %d (%s)",
2000 layer == 1 ?
"BG2 overlay" : (layer == 2 ?
"BG1 overlay" :
"END"));
2010 if (b1 == 0xF0 && b2 == 0xFF) {
2017 if (pos + 2 >= (
int)
rom_->
size()) {
2021 b3 = rom_data[pos + 2];
2032 b1, b2, b3,
static_cast<uint8_t
>(layer));
2034 LOG_DEBUG(
"Room",
"Room %03X: Object 0x%03X at (%d,%d) stream=%d (%s)",
2036 layer == 0 ?
"Primary"
2037 : (layer == 1 ?
"BG2 overlay" :
"BG1 overlay"));
2041 if (r.
id_ >= 0 && r.
id_ <= 0xFFF) {
2055 "ParseDoor: room=%d b1=0x%02X b2=0x%02X pos=%d dir=%d type=%d",
2057 static_cast<int>(door.direction),
static_cast<int>(door.type));
2068 std::vector<uint8_t> bytes;
2072 std::vector<RoomObject> layer0_objects;
2073 std::vector<RoomObject> layer1_objects;
2074 std::vector<RoomObject> layer2_objects;
2087 switch (obj.GetLayerValue()) {
2089 layer0_objects.push_back(obj);
2092 layer1_objects.push_back(obj);
2095 layer2_objects.push_back(obj);
2111 for (
const auto& obj : layer0_objects) {
2112 auto encoded = obj.EncodeObjectToBytes();
2113 bytes.push_back(encoded.b1);
2114 bytes.push_back(encoded.b2);
2115 bytes.push_back(encoded.b3);
2117 bytes.push_back(0xFF);
2118 bytes.push_back(0xFF);
2121 for (
const auto& obj : layer1_objects) {
2122 auto encoded = obj.EncodeObjectToBytes();
2123 bytes.push_back(encoded.b1);
2124 bytes.push_back(encoded.b2);
2125 bytes.push_back(encoded.b3);
2127 bytes.push_back(0xFF);
2128 bytes.push_back(0xFF);
2131 for (
const auto& obj : layer2_objects) {
2132 auto encoded = obj.EncodeObjectToBytes();
2133 bytes.push_back(encoded.b1);
2134 bytes.push_back(encoded.b2);
2135 bytes.push_back(encoded.b3);
2140 bytes.push_back(0xF0);
2141 bytes.push_back(0xFF);
2142 for (
const auto& door :
doors_) {
2143 auto [b1, b2] = door.EncodeBytes();
2144 bytes.push_back(b1);
2145 bytes.push_back(b2);
2149 bytes.push_back(0xFF);
2150 bytes.push_back(0xFF);
2156 std::vector<uint8_t> bytes;
2158 for (
const auto& sprite :
sprites_) {
2166 b2 = (sprite.x() & 0x1F) | ((sprite.subtype() & 0x07) << 5);
2171 b1 = (sprite.y() & 0x1F) | ((sprite.subtype() & 0x18) << 2) |
2172 ((sprite.layer() & 0x01) << 7);
2174 bytes.push_back(b1);
2175 bytes.push_back(b2);
2176 bytes.push_back(b3);
2180 if (sprite.key_drop() == 1) {
2181 bytes.insert(bytes.end(), {0xFE, 0x00, 0xE4});
2182 }
else if (sprite.key_drop() == 2) {
2183 bytes.insert(bytes.end(), {0xFD, 0x00, 0xE4});
2188 bytes.push_back(0xFF);
2198 const auto& rom_data = rom->
vector();
2199 int sprite_pointer = 0;
2200 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
2204 const int hard_end =
2206 if (hard_end <= 0) {
2211 std::unordered_set<int> visited_addresses;
2213 int sprite_address =
2214 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
2215 if (sprite_address < kSpritesData || sprite_address >= hard_end) {
2218 if (!visited_addresses.insert(sprite_address).second) {
2223 MeasureSpriteStreamSize(rom_data, sprite_address, hard_end);
2224 int stream_end = sprite_address + stream_size;
2225 if (stream_end > max_used) {
2226 max_used = stream_end;
2234 const std::vector<uint8_t>& encoded_bytes) {
2236 return absl::InvalidArgumentError(
"ROM not loaded");
2239 return absl::OutOfRangeError(
"Room ID out of range");
2241 if (encoded_bytes.empty() || encoded_bytes.back() != 0xFF ||
2242 (encoded_bytes.size() % 3) != 1) {
2243 return absl::InvalidArgumentError(
2244 "Encoded sprite payload must be N*3 bytes plus 0xFF terminator");
2247 const auto& rom_data = rom->
vector();
2248 int sprite_pointer = 0;
2251 int old_sprite_address =
2252 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
2253 if (old_sprite_address < 0 ||
2254 old_sprite_address >=
static_cast<int>(rom_data.size())) {
2255 return absl::OutOfRangeError(
"Sprite address out of range");
2258 const uint8_t sort_mode = rom_data[old_sprite_address];
2261 const size_t required_size = 1u + encoded_bytes.size();
2263 static_cast<size_t>(write_pos) + required_size >
2265 return absl::ResourceExhaustedError(absl::StrFormat(
2266 "Not enough sprite data space. Need %d bytes at 0x%06X, "
2267 "region ends at 0x%06X",
2270 if (
static_cast<size_t>(write_pos) + required_size > rom_data.size()) {
2271 const int required_end = write_pos +
static_cast<int>(required_size);
2272 return absl::OutOfRangeError(
2273 absl::StrFormat(
"ROM too small for sprite relocation write (need "
2274 "end=0x%06X, size=0x%06X)",
2275 required_end,
static_cast<int>(rom_data.size())));
2278 std::vector<uint8_t> relocated;
2279 relocated.reserve(required_size);
2280 relocated.push_back(sort_mode);
2281 relocated.insert(relocated.end(), encoded_bytes.begin(), encoded_bytes.end());
2284 const uint32_t snes_addr =
PcToSnes(write_pos);
2285 const int ptr_off = sprite_pointer + (room_id * 2);
2289 return absl::OkStatus();
2294 std::vector<std::pair<uint32_t, uint32_t>> ranges;
2295 const auto add = [&](uint32_t address,
size_t size) {
2296 ranges.emplace_back(address, address +
static_cast<uint32_t
>(size));
2299 add(address, bytes.size());
2305 for (
const auto& write :
relocation->payload_writes) {
2306 add(write.address, write.bytes.size());
2308 for (
const auto& write :
relocation->pointer_writes) {
2309 add(write.address, write.bytes.size());
2311 for (
const auto& write :
relocation->auxiliary_pointer_writes) {
2312 add(write.address, write.bytes.size());
2329 return absl::OkStatus();
2335 if (
rom_ ==
nullptr) {
2336 return absl::InvalidArgumentError(
"ROM pointer is null");
2349 GetObjectStreamInfo(rom_data,
room_id_));
2351 bool requires_copy_on_write =
false;
2352 if (layout !=
nullptr) {
2354 DungeonStreamRequiresCopyOnWrite(
2356 encoded_bytes.size() + 2u));
2358 const int write_pos = stream_info.address + 2;
2359 const int door_list_offset =
static_cast<int>(encoded_bytes.size()) -
2360 static_cast<int>(
doors_.size()) * 2 - 2;
2361 if (door_list_offset < 0) {
2362 return absl::FailedPreconditionError(
"Invalid encoded door list offset");
2365 if (door_pointer_slot < 0 ||
2366 door_pointer_slot + 2 >=
static_cast<int>(rom_data.size())) {
2367 return absl::OutOfRangeError(
"Door pointer slot is out of range");
2372 uint32_t encoded_door_pointer =
PcToSnes(write_pos + door_list_offset);
2373 encoded_door_pointer |= source_door_pointer & 0x800000u;
2379 if (source_door_pointer == encoded_door_pointer) {
2382 source->data_pc ==
static_cast<uint32_t
>(stream_info.address) &&
2383 source->encoded_stream.size() == encoded_bytes.size() + 2u &&
2384 std::equal(encoded_bytes.begin(), encoded_bytes.end(),
2385 source->encoded_stream.begin() + 2)) {
2389 const auto relocate = [&]() -> absl::StatusOr<StreamSavePlan> {
2390 if (stream_info.address + 2 >
static_cast<int>(rom_data.size())) {
2391 return absl::OutOfRangeError(
"Object stream header is out of range");
2393 std::vector<uint8_t> replacement = {rom_data[stream_info.address],
2394 rom_data[stream_info.address + 1]};
2395 replacement.insert(replacement.end(), encoded_bytes.begin(),
2396 encoded_bytes.end());
2400 *layout, std::move(replacement)));
2404 if (stream_info.shared || requires_copy_on_write) {
2405 if (layout !=
nullptr) {
2408 return absl::FailedPreconditionError(absl::StrFormat(
2409 "Room %d object stream at PC 0x%06X is shared; repacking is required",
2412 if (stream_info.capacity() <= 2) {
2413 if (layout !=
nullptr) {
2416 return absl::FailedPreconditionError(absl::StrFormat(
2417 "Room %d object stream has no safe physical boundary",
room_id_));
2420 const int available_payload_size = stream_info.capacity() - 2;
2421 if (encoded_bytes.size() >
static_cast<size_t>(available_payload_size)) {
2422 if (layout !=
nullptr) {
2425 return absl::ResourceExhaustedError(absl::StrFormat(
2426 "Room %d object data too large! Size: %d, Available: %d",
room_id_,
2427 static_cast<int>(encoded_bytes.size()), available_payload_size));
2432 static_cast<uint32_t
>(door_pointer_slot), encoded_door_pointer);
2436absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
2448 return absl::OkStatus();
2454 if (
rom_ ==
nullptr) {
2455 return absl::InvalidArgumentError(
"ROM pointer is null");
2461 return absl::InvalidArgumentError(
2462 "Dungeon floor graphics values must be in range 0..15");
2465 return absl::InvalidArgumentError(
2466 "Dungeon layout ID must be in range 0..7");
2471 GetObjectStreamInfo(rom_data,
room_id_));
2472 if (stream_info.address < 0 ||
2473 stream_info.address + 1 >=
static_cast<int>(rom_data.size())) {
2474 return absl::OutOfRangeError(
"Object stream header is out of range");
2478 auto patch_header = [&](std::vector<uint8_t>* stream) -> absl::Status {
2479 if (stream ==
nullptr || stream->size() < 2) {
2480 return absl::DataLossError(
2481 "Object stream is missing its two-byte header");
2484 (*stream)[0] =
static_cast<uint8_t
>(((*stream)[0] & 0xF0) |
2492 (*stream)[1] =
static_cast<uint8_t
>(((*stream)[1] & 0xE3) |
2495 return absl::OkStatus();
2498 bool requires_copy_on_write = stream_info.shared;
2499 std::vector<uint8_t> replacement;
2500 if (layout !=
nullptr) {
2502 return absl::InvalidArgumentError(
2503 "Object-stream header save requires an object stream layout");
2507 if (!inventory.
ok()) {
2508 return absl::FailedPreconditionError(absl::StrFormat(
2509 "Dungeon stream inventory has %zu issue(s); refusing object "
2511 inventory.
issues.size()));
2515 return absl::OutOfRangeError(
2516 "Room ID is outside the dungeon stream layout");
2519 bool layout_requires_copy_on_write =
false;
2521 DungeonStreamRequiresCopyOnWrite(
2523 replacement.size()));
2524 requires_copy_on_write =
2525 requires_copy_on_write || layout_requires_copy_on_write;
2528 std::vector<uint8_t> header = {rom_data[stream_info.address],
2529 rom_data[stream_info.address + 1]};
2531 if (header[0] == rom_data[stream_info.address] &&
2532 header[1] == rom_data[stream_info.address + 1]) {
2536 if (requires_copy_on_write) {
2537 if (layout ==
nullptr) {
2538 return absl::FailedPreconditionError(absl::StrFormat(
2539 "Room %d object stream at PC 0x%06X is shared; a copy-on-write "
2540 "manifest is required to save its header",
2547 *layout, std::move(replacement)));
2552 plan.
direct_writes.emplace_back(stream_info.address, std::move(header));
2556absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
2571 return absl::OkStatus();
2577 if (
rom_ ==
nullptr) {
2578 return absl::InvalidArgumentError(
"ROM pointer is null");
2586 return absl::OutOfRangeError(
"Room ID out of range");
2590 GetSpriteStreamInfo(rom_data,
room_id_));
2592 bool requires_copy_on_write =
false;
2593 if (layout !=
nullptr) {
2595 DungeonStreamRequiresCopyOnWrite(
2597 encoded_bytes.size() + 1u));
2603 const uint64_t source_payload_address =
2604 static_cast<uint64_t
>(stream_info.address) + 1u;
2605 if (source_payload_address <= rom_data.size() &&
2606 encoded_bytes.size() <= rom_data.size() - source_payload_address) {
2609 source->data_pc ==
static_cast<uint32_t
>(stream_info.address) &&
2610 source->encoded_stream.size() == encoded_bytes.size() + 1u &&
2611 std::equal(encoded_bytes.begin(), encoded_bytes.end(),
2612 source->encoded_stream.begin() + 1)) {
2616 const auto relocate = [&]() -> absl::StatusOr<StreamSavePlan> {
2617 std::vector<uint8_t> replacement = {rom_data[stream_info.address]};
2618 replacement.insert(replacement.end(), encoded_bytes.begin(),
2619 encoded_bytes.end());
2623 *layout, std::move(replacement)));
2627 if (stream_info.shared || requires_copy_on_write) {
2628 if (layout !=
nullptr) {
2631 return absl::FailedPreconditionError(absl::StrFormat(
2632 "Room %d sprite stream at PC 0x%06X is shared; repacking is required",
2635 if (stream_info.capacity() <= 1) {
2636 if (layout !=
nullptr) {
2639 return absl::FailedPreconditionError(absl::StrFormat(
2640 "Room %d sprite stream has no safe physical boundary",
room_id_));
2643 const int available_payload_size = stream_info.capacity() - 1;
2644 const int payload_address = stream_info.address + 1;
2645 if (payload_address < 0 ||
2646 payload_address >=
static_cast<int>(
rom_->
size())) {
2647 return absl::OutOfRangeError(absl::StrFormat(
2648 "Room %d has invalid sprite payload address",
room_id_));
2651 if (
static_cast<int>(encoded_bytes.size()) > available_payload_size) {
2652 if (layout !=
nullptr) {
2655 return absl::ResourceExhaustedError(absl::StrFormat(
2656 "Room %d sprite data too large! Size: %d, Available: %d; repacking "
2658 room_id_,
static_cast<int>(encoded_bytes.size()),
2659 available_payload_size));
2662 plan.
direct_writes.emplace_back(payload_address, encoded_bytes);
2666absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
2678 return absl::OkStatus();
2682 if (
rom_ ==
nullptr) {
2683 return absl::InvalidArgumentError(
"ROM pointer is null");
2686 const auto& rom_data =
rom()->vector();
2689 return absl::OutOfRangeError(
"Room header pointer out of range");
2693 return absl::OutOfRangeError(
"Room header pointer bank out of range");
2699 header_pointer =
SnesToPc(header_pointer);
2701 int table_offset = header_pointer + (
room_id_ * 2);
2702 if (table_offset < 0 ||
2703 table_offset + 1 >=
static_cast<int>(rom_data.size())) {
2704 return absl::OutOfRangeError(
"Room header table offset out of range");
2708 (rom_data[table_offset + 1] << 8) + rom_data[table_offset];
2709 int header_location =
SnesToPc(address);
2711 if (header_location < 0 ||
2712 header_location + 13 >=
static_cast<int>(rom_data.size())) {
2713 return absl::OutOfRangeError(
"Room header location out of range");
2720 if (
bg2() != background2::DarkRoom) {
2721 layer2_mode_for_save =
static_cast<uint8_t
>(
bg2()) & 0x07;
2723 const bool dark_room =
2725 uint8_t byte0 =
static_cast<uint8_t
>(
2726 (layer2_mode_for_save << 5) |
2727 ((
static_cast<uint8_t
>(
collision()) & 0x07) << 2) |
2728 (rom_data[header_location] & 0x02) | (dark_room ? 1 : 0));
2736 const uint8_t byte8 =
static_cast<uint8_t
>(
2737 (rom_data[header_location + 8] & 0xFC) | (
staircase_plane(3) & 0x03));
2758 if (msg_addr < 0 || msg_addr + 1 >=
static_cast<int>(rom_data.size())) {
2759 return absl::OutOfRangeError(
"Message ID address out of range");
2765 return absl::OkStatus();
2775 return absl::InvalidArgumentError(
"Invalid object parameters");
2783 return absl::OkStatus();
2788 return absl::OutOfRangeError(
"Object index out of range");
2796 return absl::OkStatus();
2801 return absl::OutOfRangeError(
"Object index out of range");
2805 return absl::InvalidArgumentError(
"Invalid object parameters");
2813 return absl::OkStatus();
2819 if (obj.x() == x && obj.y() == y && obj.GetLayerValue() == layer) {
2823 return absl::NotFoundError(
"No object found at position");
2828 if (
object.x() < 0 ||
object.x() > 63)
2830 if (
object.y() < 0 ||
object.y() > 63)
2834 if (
object.GetLayerValue() < 0 ||
object.GetLayerValue() > 2)
2838 if (
object.id_ < 0 || object.id_ > 0xFFF)
2842 if (
object.id_ < 0x100 &&
object.size() > 15)
2849 int& nbr_of_staircase) {
2852 static const auto kStaircaseLabels = [] {
2853 std::array<std::string, 256> labels;
2854 for (
size_t room = 0; room < labels.size(); ++room) {
2855 labels[room] = absl::StrCat(
"To ", room);
2863 if (nbr_of_staircase < 4) {
2887 const auto& rom_data =
rom()->vector();
2895 int sprite_pointer = 0;
2896 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
2900 int sprite_address =
2901 ReadRoomSpriteAddressPc(rom_data, sprite_pointer,
room_id_);
2902 if (sprite_address < 0 ||
2903 sprite_address + 1 >=
static_cast<int>(rom_data.size())) {
2908 sprite_address += 1;
2910 while (sprite_address + 2 <
static_cast<int>(rom_data.size())) {
2911 uint8_t b1 = rom_data[sprite_address];
2912 uint8_t b2 = rom_data[sprite_address + 1];
2913 uint8_t b3 = rom_data[sprite_address + 2];
2919 sprites_.emplace_back(b3, (b2 & 0x1F), (b1 & 0x1F),
2920 ((b2 & 0xE0) >> 5) + ((b1 & 0x60) >> 2),
2927 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1E &&
2933 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1D &&
2940 sprite_address += 3;
2952 const auto& rom_data =
rom()->vector();
2958 const int cpos =
static_cast<int>(
SnesToPc(
2962 const size_t byte_length =
2965 const size_t bounded_byte_length = std::min<size_t>(
2966 byte_length, cpos >= 0 && cpos <
static_cast<int>(rom_data.size())
2967 ? rom_data.size() -
static_cast<size_t>(cpos)
2969 const size_t record_count = std::min<size_t>(
2972 for (
size_t i = 0; i < record_count; ++i) {
2973 const size_t offset =
2975 if ((((rom_data[offset + 1] << 8) + rom_data[offset]) & 0x7FFF) ==
2979 if ((((rom_data[offset + 1] << 8) + rom_data[offset]) & 0x8000) ==
2991 auto rom_data =
rom()->vector();
3002 "LoadDoors for room %d - doors are loaded via object stream",
3007 auto rom_data =
rom()->vector();
3020 return (obj.options() & ObjectOption::Torch) !=
3021 ObjectOption::Nothing;
3026 for (
int i = 0; i < bytes_count; i += 2) {
3027 if (i + 1 >= bytes_count)
3034 if (b1 == 0xFF && b2 == 0xFF) {
3039 uint16_t torch_room_id = (b2 << 8) | b1;
3043 while (i < bytes_count) {
3044 if (i + 1 >= bytes_count)
3051 if (b1 == 0xFF && b2 == 0xFF) {
3059 torch_obj.SetRom(
rom_);
3061 torch_obj.set_torch_reserved_bit(entry.
reserved);
3062 torch_obj.lit_ = entry.
lit;
3067 "Room",
"Loaded torch at (%d,%d) draw_layer=%d reserved=%d lit=%d",
3076 while (i < bytes_count) {
3077 if (i + 1 >= bytes_count)
3081 if (b1 == 0xFF && b2 == 0xFF) {
3096 uint16_t room_id = 0;
3102 const std::vector<uint8_t>& rom_data,
int bytes_count) {
3103 std::vector<TorchSegment> segments;
3108 if (b1 == 0xFF && b2 == 0xFF) {
3114 padding.
bytes = {0xFF, 0xFF};
3115 segments.push_back(std::move(padding));
3119 uint16_t room_id = (b2 << 8) | b1;
3126 seg.
bytes.push_back(b1);
3127 seg.
bytes.push_back(b2);
3132 if (b1 == 0xFF && b2 == 0xFF) {
3133 seg.
bytes.push_back(0xFF);
3134 seg.
bytes.push_back(0xFF);
3138 seg.
bytes.push_back(b1);
3139 seg.
bytes.push_back(b2);
3142 segments.push_back(std::move(seg));
3148 std::vector<uint8_t> bytes;
3153 if (bytes.empty()) {
3154 bytes.push_back(room_id & 0xFF);
3155 bytes.push_back((room_id >> 8) & 0xFF);
3158 .px =
static_cast<uint8_t
>(obj.x()),
3159 .py =
static_cast<uint8_t
>(obj.y()),
3160 .draw_layer =
static_cast<uint8_t
>(obj.GetLayerValue() & 1),
3161 .reserved = obj.torch_reserved_bit(),
3164 bytes.push_back(encoded.
low);
3165 bytes.push_back(encoded.
high);
3167 if (!bytes.empty()) {
3168 bytes.push_back(0xFF);
3169 bytes.push_back(0xFF);
3176 const char* object_type) {
3177 const uint8_t selector =
object.GetLayerValue();
3178 if (selector <= 1) {
3179 return absl::OkStatus();
3181 return absl::InvalidArgumentError(absl::StrFormat(
3182 "%s in room 0x%03X has invalid special draw-layer selector %d; "
3184 "(upper/BG1) or 1 (lower/BG2)",
3185 object_type, room_id, selector));
3192 if (
object.x() <= 0x3E &&
object.y() <= 0x3E) {
3193 return absl::OkStatus();
3195 return absl::InvalidArgumentError(absl::StrFormat(
3196 "Torch in room 0x%03X has invalid position (%d,%d); expected x/y in "
3198 room_id,
object.x(),
object.y()));
3208 bool any_owned_room =
false;
3209 bool needs_write =
false;
3212template <
typename RoomLookup>
3214 RoomLookup&& room_lookup) {
3215 if (!
rom || !
rom->is_loaded()) {
3216 return absl::InvalidArgumentError(
"ROM not loaded");
3219 const auto& rom_data =
rom->vector();
3222 return absl::OutOfRangeError(
"Torch length pointer is out of range");
3230 static_cast<uint64_t
>(
kTorchData) + existing_count > rom_data.size()) {
3231 return absl::OutOfRangeError(
"Torch data is out of range");
3236 auto& bytes = plan.
bytes;
3237 const int room_limit = std::clamp(room_count, 0,
kNumberOfRooms);
3239 std::vector<bool> owned_rooms(room_limit,
false);
3240 std::vector<bool> seen_original_room(room_limit,
false);
3241 std::vector<bool> emitted_owned_room(room_limit,
false);
3242 std::vector<std::vector<uint8_t>> replacements(room_limit);
3243 bool any_owned_room =
false;
3244 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3245 const Room* room = room_lookup(room_id);
3246 const bool room_owned =
3256 owned_rooms[room_id] =
true;
3257 any_owned_room =
true;
3261 if (!any_owned_room) {
3266 for (
const auto& segment : rom_segments) {
3267 if (segment.room_id < room_limit) {
3268 seen_original_room[segment.room_id] =
true;
3269 if (owned_rooms[segment.room_id]) {
3270 if (!emitted_owned_room[segment.room_id]) {
3271 bytes.insert(bytes.end(), replacements[segment.room_id].begin(),
3272 replacements[segment.room_id].end());
3273 emitted_owned_room[segment.room_id] =
true;
3278 bytes.insert(bytes.end(), segment.bytes.begin(), segment.bytes.end());
3281 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3282 if (owned_rooms[room_id] && !seen_original_room[room_id] &&
3283 !replacements[room_id].empty()) {
3284 bytes.insert(bytes.end(), replacements[room_id].begin(),
3285 replacements[room_id].end());
3290 return absl::ResourceExhaustedError(
3291 absl::StrFormat(
"Torch data too large: %d bytes (max %d)", bytes.size(),
3294 if (
static_cast<uint64_t
>(
kTorchData) + bytes.size() > rom_data.size()) {
3295 return absl::OutOfRangeError(
"Torch save data is out of range");
3298 const uint16_t current_len =
3302 current_len != bytes.size() ||
3303 !std::equal(bytes.begin(), bytes.end(), rom_data.begin() +
kTorchData);
3307template <
typename RoomLookup>
3309 RoomLookup&& room_lookup) {
3311 if (!plan.any_owned_room) {
3312 return absl::OkStatus();
3315 if (plan.needs_write) {
3317 static_cast<uint16_t
>(plan.bytes.size())));
3320 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
3321 if (
const Room* room = room_lookup(room_id);
3326 return absl::OkStatus();
3332 return SaveAllTorchesImpl(
rom,
static_cast<int>(rooms.size()),
3333 [&rooms](
int room_id) { return &rooms[room_id]; });
3337 Rom*
rom,
int room_count,
3338 const std::function<
const Room*(
int)>& room_lookup) {
3339 return SaveAllTorchesImpl(
rom, room_count, room_lookup);
3342absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
3344 const std::function<
const Room*(
int)>& room_lookup) {
3346 BuildTorchSavePlan(
rom, room_count, room_lookup));
3347 if (!plan.needs_write) {
3348 return std::vector<std::pair<uint32_t, uint32_t>>{};
3350 return std::vector<std::pair<uint32_t, uint32_t>>{
3363 if (pit_damage_table !=
nullptr && pit_damage_table->
dirty()) {
3366 return absl::OkStatus();
3368 if (!
rom || !
rom->is_loaded()) {
3369 return absl::InvalidArgumentError(
"ROM not loaded");
3371 const auto& rom_data =
rom->vector();
3372 if (kPitCount < 0 || kPitCount >=
static_cast<int>(rom_data.size()) ||
3373 kPitPointer + 2 >=
static_cast<int>(rom_data.size())) {
3374 return absl::OutOfRangeError(
"Pit count/pointer out of range");
3380 int data_len = max_offset + 2;
3381 int pit_ptr_snes = (rom_data[
kPitPointer + 2] << 16) |
3383 int pit_data_pc =
SnesToPc(pit_ptr_snes);
3384 if (pit_data_pc < 0 ||
3385 pit_data_pc + data_len >
static_cast<int>(rom_data.size())) {
3386 return absl::OutOfRangeError(
"Pit data region out of range");
3388 std::vector<uint8_t> data(rom_data.begin() + pit_data_pc,
3389 rom_data.begin() + pit_data_pc + data_len);
3394 return rom->WriteVector(pit_data_pc, data);
3405 return first_begin < second_end && second_begin < first_end;
3409 const std::vector<uint8_t>& rom_data,
int operand_pc) {
3410 if (operand_pc <= 0 || operand_pc + 5 >=
static_cast<int>(rom_data.size())) {
3411 return absl::OutOfRangeError(
"Blocks pointer operand out of range");
3425 if (rom_data[operand_pc - 1] != 0xBF || rom_data[operand_pc + 3] != 0x9D) {
3426 return absl::FailedPreconditionError(absl::StrFormat(
3427 "Blocks pointer operand at PC 0x%05X is not in the expected "
3428 "LDA.l ...,X / STA.w loader sequence",
3431 return absl::OkStatus();
3435 const std::vector<uint8_t>& rom_data, std::array<int, 4>* destination_pcs) {
3438 return absl::OutOfRangeError(
"Blocks length out of range");
3444 const int snes = (rom_data[operand_pc + 2] << 16) |
3445 (rom_data[operand_pc + 1] << 8) | rom_data[operand_pc];
3446 const int data_pc =
SnesToPc(snes);
3449 return absl::OutOfRangeError(absl::StrFormat(
3450 "Blocks data region out of range for loader page %d", page + 1));
3452 (*destination_pcs)[page] = data_pc;
3456 for (
size_t page = 0; page < destination_pcs->size(); ++page) {
3457 const int page_begin = (*destination_pcs)[page];
3460 kLengthMetadataEnd)) {
3461 return absl::FailedPreconditionError(absl::StrFormat(
3462 "Blocks data page %d at PC [0x%05X, 0x%05X) overlaps block-table "
3463 "length metadata [0x%05X, 0x%05X)",
3464 page + 1, page_begin, page_end,
kBlocksLength, kLengthMetadataEnd));
3475 return absl::FailedPreconditionError(absl::StrFormat(
3476 "Blocks data page %d at PC [0x%05X, 0x%05X) overlaps loader %d "
3477 "opcode/operand metadata [0x%05X, 0x%05X)",
3478 page + 1, page_begin, page_end, loader + 1, loader_begin,
3483 for (
size_t previous = 0; previous < page; ++previous) {
3484 const int previous_begin = (*destination_pcs)[previous];
3488 return absl::FailedPreconditionError(absl::StrFormat(
3489 "Blocks data pages %d and %d overlap at PC ranges [0x%05X, "
3490 "0x%05X) and [0x%05X, 0x%05X)",
3491 previous + 1, page + 1, previous_begin, previous_end, page_begin,
3497 return absl::OkStatus();
3508 const std::array<int, 4>& destination_pcs,
int byte_length) {
3510 byte_length % 4 != 0) {
3511 return absl::FailedPreconditionError(
3512 "Pushable-block length must contain whole four-byte records within "
3513 "the 0x200-byte WRAM buffer");
3515 for (
size_t page = 0; page < destination_pcs.size(); ++page) {
3520 const int begin = destination_pcs[page];
3521 const int end = begin + length;
3524 return absl::FailedPreconditionError(absl::StrFormat(
3525 "Pushable-block ROM capacity exceeded: loader page %d payload "
3526 "[0x%05X, 0x%05X) overlaps torch data [0x%05X, 0x%05X). "
3527 "Repoint the block data to independent ROM storage before growing "
3533 return absl::FailedPreconditionError(absl::StrFormat(
3534 "Blocks loader page %d payload overlaps torch length metadata",
3538 return absl::OkStatus();
3544 if (!
rom || !
rom->is_loaded()) {
3545 return absl::InvalidArgumentError(
"ROM not loaded");
3547 const auto& rom_data =
rom->vector();
3548 if (
kBlocksLength + 1 >=
static_cast<int>(rom_data.size())) {
3549 return absl::OutOfRangeError(
"Blocks length out of range");
3553 std::array<int, 4> destination_pcs{};
3555 PreflightBlocksLoaderDestinations(rom_data, &destination_pcs));
3557 PreflightBlocksPayloadDestinations(destination_pcs, blocks_count));
3558 if (blocks_count <= 0) {
3559 return absl::OkStatus();
3561 for (
int r = 0; r < 4; ++r) {
3562 const int pc = destination_pcs[r];
3563 int off = r * kBlocksRegionSize;
3564 int len = std::min(kBlocksRegionSize, blocks_count - off);
3567 std::vector<uint8_t> chunk(rom_data.begin() + pc,
3568 rom_data.begin() + pc + len);
3573 return absl::OkStatus();
3591 const Rom*
rom,
int room_count,
3592 const std::function<
const Room*(
int)>& room_lookup) {
3593 if (!
rom || !
rom->is_loaded()) {
3594 return absl::InvalidArgumentError(
"ROM not loaded");
3596 const auto& rom_data =
rom->vector();
3597 if (
kBlocksLength + 1 >=
static_cast<int>(rom_data.size())) {
3598 return absl::OutOfRangeError(
"Blocks length out of range");
3601 std::array<int, 4> destination_pcs{};
3611 const int original_count_word =
3613 const int original_byte_len = original_count_word;
3616 std::vector<uint8_t> original_buffer(original_byte_len, 0);
3617 for (
int r = 0; r < 4; ++r) {
3618 const int pc = destination_pcs[r];
3623 std::copy_n(rom_data.begin() + pc, len, original_buffer.begin() + off);
3625 const int original_slot_count = original_byte_len / 4;
3636 struct EncodedBlock {
3640 std::unordered_set<uint16_t> owned_room_ids;
3641 std::unordered_map<int, EncodedBlock> slot_replacements;
3642 std::vector<EncodedBlock> appended;
3643 for (
int rid = 0; rid < room_count; ++rid) {
3644 const Room* room = room_lookup(rid);
3645 if (room ==
nullptr)
3649 return absl::FailedPreconditionError(absl::StrFormat(
3650 "Room 0x%03X has unsaved pushable-block edits, but its block "
3651 "table is not loaded. Load the room's blocks before saving.",
3656 owned_room_ids.insert(
static_cast<uint16_t
>(rid));
3660 std::unordered_set<int> claimed_load_orders;
3667 encoded_entry.
room_id =
static_cast<uint16_t
>(rid);
3668 encoded_entry.
px = obj.x();
3669 encoded_entry.
py = obj.y();
3670 encoded_entry.
draw_layer = obj.GetLayerValue();
3674 const EncodedBlock encoded_block{encoded, &obj};
3675 const int load_order = obj.block_load_order();
3676 if (load_order >= 0 && !claimed_load_orders.insert(load_order).second) {
3677 return absl::FailedPreconditionError(absl::StrFormat(
3678 "Room 0x%03X has multiple pushable blocks claiming non-new "
3679 "load-order slot %d",
3683 appended.push_back(encoded_block);
3684 }
else if (load_order >= 0 && load_order < original_slot_count) {
3685 const int original_offset = load_order * 4;
3686 const uint16_t original_room_id =
3687 static_cast<uint16_t
>(original_buffer[original_offset] |
3688 (original_buffer[original_offset + 1] << 8));
3689 if (original_room_id !=
static_cast<uint16_t
>(rid)) {
3694 appended.push_back(encoded_block);
3697 slot_replacements.emplace(load_order, encoded_block);
3701 appended.push_back(encoded_block);
3708 std::vector<uint8_t> output;
3709 output.reserve(original_byte_len + appended.size() * 4);
3710 std::vector<std::pair<const RoomObject*, int>> load_order_updates;
3711 load_order_updates.reserve(slot_replacements.size() + appended.size());
3712 const auto append_encoded_block =
3713 [&output, &load_order_updates](
const EncodedBlock& block) {
3714 const int output_slot =
static_cast<int>(output.size() / 4);
3715 output.push_back(block.bytes.b1);
3716 output.push_back(block.bytes.b2);
3717 output.push_back(block.bytes.b3);
3718 output.push_back(block.bytes.b4);
3719 load_order_updates.emplace_back(block.source_object, output_slot);
3721 for (
int slot = 0; slot < original_slot_count; ++slot) {
3722 const uint8_t b1 = original_buffer[slot * 4 + 0];
3723 const uint8_t b2 = original_buffer[slot * 4 + 1];
3724 const uint16_t slot_room_id =
static_cast<uint16_t
>(b1 | (b2 << 8));
3725 if (owned_room_ids.contains(slot_room_id)) {
3726 const auto it = slot_replacements.find(slot);
3727 if (it == slot_replacements.end()) {
3732 append_encoded_block(it->second);
3735 output.push_back(b1);
3736 output.push_back(b2);
3737 output.push_back(original_buffer[slot * 4 + 2]);
3738 output.push_back(original_buffer[slot * 4 + 3]);
3744 for (
const auto& block : appended) {
3745 append_encoded_block(block);
3754 if (output.empty()) {
3755 return absl::FailedPreconditionError(
3756 "Pushable-block table cannot be empty: ALTTP's runtime scan reads one "
3757 "entry before comparing the byte-length limit. Keep at least one "
3758 "pushable block, or patch the runtime loop before removing the last "
3764 if (
static_cast<int>(output.size() / 4) > kMaxEntries) {
3765 return absl::FailedPreconditionError(absl::StrCat(
3766 "Pushable-block table overflow: ", output.size() / 4,
3767 " entries exceeds the runtime WRAM cap of ", kMaxEntries,
3768 " (a larger table requires runtime buffer and loader changes)."));
3775 const int total_bytes =
static_cast<int>(output.size());
3780 write_destinations.reserve(4);
3781 for (
int r = 0; r < 4; ++r) {
3786 write_destinations.push_back({destination_pcs[r], off, len});
3789 plan.
bytes = std::move(output);
3797 const std::function<
const Room*(
int)>& room_lookup) {
3802 for (
const auto&
destination : plan.destinations) {
3803 std::vector<uint8_t> chunk(
3817 for (
const auto& [
object, load_order] : plan.load_order_updates) {
3818 const_cast<RoomObject*
>(object)->set_block_load_order(load_order);
3820 for (
int room_id = 0; room_id < room_count; ++room_id) {
3821 if (
const Room* room = room_lookup(room_id);
3826 return absl::OkStatus();
3829absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
3831 const std::function<
const Room*(
int)>& room_lookup) {
3833 BuildBlockSavePlan(
rom, room_count, room_lookup));
3834 std::vector<std::pair<uint32_t, uint32_t>> ranges;
3835 ranges.reserve(plan.destinations.size() + 1);
3836 for (
const auto&
destination : plan.destinations) {
3843template <
typename RoomLookup>
3845 RoomLookup&& room_lookup) {
3846 if (!
rom || !
rom->is_loaded()) {
3847 return absl::InvalidArgumentError(
"ROM not loaded");
3854 const auto& rom_data =
rom->vector();
3859 if (!has_ptr_table) {
3860 for (
int room_id = 0; room_id < room_count; ++room_id) {
3861 const Room* room = room_lookup(room_id);
3863 return absl::FailedPreconditionError(
3864 "Custom collision region not present in this ROM");
3867 return absl::OkStatus();
3870 if (!has_data_region) {
3871 for (
int room_id = 0; room_id < room_count; ++room_id) {
3872 const Room* room = room_lookup(room_id);
3874 return absl::FailedPreconditionError(
3875 "Custom collision data region not present in this ROM");
3878 return absl::OkStatus();
3887 "CustomCollisionPointers"));
3891 "CustomCollisionData"));
3895 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3896 const Room* room = room_lookup(room_id);
3901 const int actual_room_id = room->
id();
3903 if (ptr_offset + 2 >=
static_cast<int>(rom_data.size())) {
3904 return absl::OutOfRangeError(
"Custom collision pointer out of range");
3930 return absl::OkStatus();
3935 rom,
static_cast<int>(rooms.size()),
3936 [&rooms](
int room_id) { return &rooms[room_id]; });
3940 const std::function<
Room*(
int)>& room_lookup) {
3944absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
3946 if (
rom ==
nullptr || !
rom->is_loaded()) {
3947 return absl::InvalidArgumentError(
"ROM not loaded");
3949 const auto& rom_data =
rom->vector();
3952 return absl::OutOfRangeError(
"Chest pointers out of range");
3955 const uint32_t data_pointer =
3959 const uint32_t data_pc =
SnesToPc(data_pointer);
3960 if (data_pc > rom_data.size() ||
3962 rom_data.size() -
static_cast<size_t>(data_pc)) {
3963 return absl::OutOfRangeError(
"Chest data region out of range");
3965 const uint32_t data_end =
3967 const auto overlaps = [](uint32_t begin, uint32_t end, uint32_t other_begin,
3968 uint32_t other_end) {
3969 return begin < other_end && other_begin < end;
3975 return absl::FailedPreconditionError(
3976 "Chest data region overlaps chest metadata operands");
3979 return std::vector<std::pair<uint32_t, uint32_t>>{
3982 {data_pc, data_end},
3992 uint16_t
room_id()
const {
return word & 0x7FFF; }
3998 const std::vector<uint8_t>& rom_data,
int cpos,
int byte_length) {
3999 std::vector<PhysicalChestRecord> records;
4001 records.reserve(record_count);
4002 for (
int i = 0; i < record_count; ++i) {
4008 const uint16_t word =
4009 (
static_cast<uint16_t
>(rom_data[off + 1]) << 8) | rom_data[off];
4017 bytes->push_back(word & 0xFF);
4018 bytes->push_back((word >> 8) & 0xFF);
4019 bytes->push_back(item);
4024 const uint16_t word =
static_cast<uint16_t
>(room_id) |
4025 (
chest.size ?
static_cast<uint16_t
>(0x8000) : 0);
4030 absl::Span<const uint8_t> replacement,
4031 std::vector<ChestWriteRun>* writes) {
4033 while (cursor < replacement.size()) {
4034 if (replacement[cursor] == expected[cursor]) {
4038 const size_t begin = cursor;
4041 }
while (cursor < replacement.size() &&
4042 replacement[cursor] != expected[cursor]);
4045 run.
pc = pc +
static_cast<uint32_t
>(begin);
4047 expected.begin() + cursor);
4049 replacement.begin() + cursor);
4050 if (!writes->empty() && writes->back().end() == run.
pc) {
4051 writes->back().expected_bytes.insert(writes->back().expected_bytes.end(),
4054 writes->back().replacement_bytes.insert(
4058 writes->push_back(std::move(run));
4064 std::sort(writes->begin(), writes->end(),
4066 return lhs.pc < rhs.pc;
4068 std::vector<ChestWriteRun> merged;
4069 merged.reserve(writes->size());
4071 if (!merged.empty() && merged.back().end() == write.pc) {
4072 merged.back().expected_bytes.insert(merged.back().expected_bytes.end(),
4073 write.expected_bytes.begin(),
4074 write.expected_bytes.end());
4075 merged.back().replacement_bytes.insert(
4076 merged.back().replacement_bytes.end(),
4077 write.replacement_bytes.begin(), write.replacement_bytes.end());
4080 merged.push_back(std::move(write));
4082 *writes = std::move(merged);
4086 std::vector<uint8_t> bytes;
4095 const Rom*
rom,
int room_count,
4096 const std::function<
const Room*(
int)>& room_lookup) {
4097 if (
rom ==
nullptr || !
rom->is_loaded()) {
4098 return absl::InvalidArgumentError(
"ROM not loaded");
4100 const auto& rom_data =
rom->vector();
4103 return absl::OutOfRangeError(
"Chest pointers out of range");
4109 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
4110 const Room* room = room_lookup(room_id);
4112 dirty_rooms[room_id] = room;
4123 plan.
data_pc = potential_ranges[1].first;
4130 rom_data.begin() + plan.
data_pc,
4134 return absl::FailedPreconditionError(absl::StrFormat(
4135 "Chest table byte length %d is invalid (capacity %d)",
4141 if (physical_records.size() !=
4143 return absl::OutOfRangeError(
"Chest data region is truncated");
4149 ++old_counts[record.room_id()];
4153 size_t final_record_count = physical_records.size();
4154 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
4155 if (dirty_rooms[room_id] ==
nullptr) {
4158 final_record_count -= old_counts[room_id];
4159 final_record_count += dirty_rooms[room_id]->GetChests().size();
4162 return absl::ResourceExhaustedError(absl::StrFormat(
4163 "Chest table has %d records; capacity is %d",
4167 std::vector<uint8_t> replacement_bytes;
4171 const uint16_t room_id = record.room_id();
4172 const Room* dirty_room =
4174 if (dirty_room ==
nullptr) {
4179 const size_t occurrence = seen_counts[room_id]++;
4180 const auto& replacements = dirty_room->
GetChests();
4181 if (occurrence < replacements.size()) {
4183 replacements[occurrence]);
4190 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
4191 const Room* dirty_room = dirty_rooms[room_id];
4192 if (dirty_room ==
nullptr) {
4195 const auto& replacements = dirty_room->
GetChests();
4196 for (
size_t i = seen_counts[room_id]; i < replacements.size(); ++i) {
4202 return absl::InternalError(
"Chest save plan size mismatch");
4205 const std::array<uint8_t, 2> expected_length = {
4208 const uint16_t replacement_length =
4209 static_cast<uint16_t
>(replacement_bytes.size());
4210 const std::array<uint8_t, 2> encoded_replacement_length = {
4211 static_cast<uint8_t
>(replacement_length & 0xFF),
4212 static_cast<uint8_t
>((replacement_length >> 8) & 0xFF)};
4214 encoded_replacement_length, &plan.
writes);
4217 .subspan(0, replacement_bytes.size()),
4218 replacement_bytes, &plan.
writes);
4229 if (ptr_off < 0 || ptr_off + 1 >=
static_cast<int>(rom_data.size())) {
4232 const uint16_t item_ptr =
4233 (
static_cast<uint16_t
>(rom_data[ptr_off + 1]) << 8) | rom_data[ptr_off];
4234 if (item_ptr < 0x8000) {
4237 const int item_addr =
static_cast<int>(
SnesToPc(0x010000 | item_ptr));
4238 return item_addr >= 0 && item_addr < static_cast<int>(rom_data.size())
4244 const std::vector<uint8_t>& rom_data,
int room_id) {
4246 static_cast<int>(rom_data.size())) {
4247 return absl::OutOfRangeError(
"Room items pointer table out of range");
4250 return absl::OutOfRangeError(
"Room ID out of range");
4257 const int hard_end =
4261 return absl::FailedPreconditionError(
4262 "Room pot item pointer is null, invalid, or outside the item region");
4270 const Rom*
rom,
int room_count,
4271 const std::function<
const Room*(
int)>& room_lookup) {
4272 return BuildChestSavePlanImpl(
rom, room_count, room_lookup);
4275absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
4277 const std::function<
const Room*(
int)>& room_lookup) {
4285 const std::function<
const Room*(
int)>& room_lookup) {
4286 if (
rom ==
nullptr || !
rom->is_loaded()) {
4287 return absl::InvalidArgumentError(
"ROM not loaded");
4292 if (canonical_plan != plan) {
4293 return absl::FailedPreconditionError(
4294 "Chest save plan does not match canonical serialization");
4296 return absl::OkStatus();
4299 const auto& rom_data =
rom->vector();
4302 plan.
data_pc > rom_data.size() ||
4304 return absl::FailedPreconditionError(
4305 "Chest save plan source is no longer addressable");
4309 return absl::FailedPreconditionError(
4310 "Chest save plan is stale: data pointer changed");
4312 const uint16_t current_length =
4316 return absl::FailedPreconditionError(
4317 "Chest save plan is stale: runtime length changed");
4321 rom_data.begin() + plan.
data_pc)) {
4322 return absl::FailedPreconditionError(
4323 "Chest save plan is stale: table bytes changed");
4326 const Room* room = room_lookup(state.room_id);
4328 EncodeChestRoomState(state.room_id, *room) != state.encoded_chests) {
4329 return absl::FailedPreconditionError(absl::StrFormat(
4330 "Chest save plan is stale for room 0x%03X", state.room_id));
4335 if (canonical_plan != plan) {
4336 return absl::FailedPreconditionError(
4337 "Chest save plan does not match canonical serialization");
4353 return absl::OkStatus();
4356template <
typename RoomLookup>
4358 RoomLookup&& room_lookup) {
4359 const std::function<
const Room*(int)> lookup =
4360 std::forward<RoomLookup>(room_lookup);
4368 [&rooms](
int room_id) { return &rooms[room_id]; });
4372 const std::function<
const Room*(
int)>& room_lookup) {
4376template <
typename RoomLookup>
4378 Rom*
rom,
int room_count, RoomLookup&& room_lookup,
4380 if (!
rom || !
rom->is_loaded()) {
4381 return absl::InvalidArgumentError(
"ROM not loaded");
4383 const auto& rom_data =
rom->vector();
4385 static_cast<int>(rom_data.size())) {
4386 return absl::OutOfRangeError(
"Room items pointer table out of range");
4390 if (repack_layout !=
nullptr) {
4391 std::vector<DungeonStreamReplacement> replacements;
4392 for (
int room_id = 0; room_id < room_limit; ++room_id) {
4393 const Room* room = room_lookup(room_id);
4399 replacement.
room_id =
static_cast<uint32_t
>(room_id);
4403 replacement.
encoded_stream.push_back((item.position >> 8) & 0xFF);
4408 replacements.push_back(std::move(replacement));
4410 if (replacements.empty()) {
4411 return absl::OkStatus();
4420 if (
const Room* room = room_lookup(replacement.room_id);
4425 return absl::OkStatus();
4428 struct PendingPotItemWrite {
4431 std::vector<uint8_t> bytes;
4436 std::vector<PendingPotItemWrite> pending_writes;
4437 for (
int room_id = 0; room_id < room_limit; ++room_id) {
4438 const Room* room = room_lookup(room_id);
4444 GetPotItemStreamInfo(rom_data, room_id));
4445 if (stream_info.shared) {
4446 return absl::FailedPreconditionError(absl::StrFormat(
4447 "Room %d pot item stream at PC 0x%06X is shared; repacking is "
4449 room_id, stream_info.address));
4451 if (stream_info.capacity() <= 0) {
4452 return absl::FailedPreconditionError(absl::StrFormat(
4453 "Room %d pot item stream has no safe physical boundary", room_id));
4456 PendingPotItemWrite pending;
4457 pending.room_id = room_id;
4458 pending.address = stream_info.address;
4460 pending.bytes.push_back(pi.position & 0xFF);
4461 pending.bytes.push_back((pi.position >> 8) & 0xFF);
4462 pending.bytes.push_back(pi.item);
4464 pending.bytes.push_back(0xFF);
4465 pending.bytes.push_back(0xFF);
4466 if (
static_cast<int>(pending.bytes.size()) > stream_info.capacity()) {
4467 return absl::ResourceExhaustedError(absl::StrFormat(
4468 "Room %d pot item data too large! Size: %d, Available: %d", room_id,
4469 static_cast<int>(pending.bytes.size()), stream_info.capacity()));
4471 pending_writes.push_back(std::move(pending));
4474 for (
const auto& pending : pending_writes) {
4475 const bool data_changed =
4476 !std::equal(pending.bytes.begin(), pending.bytes.end(),
4477 rom_data.begin() + pending.address);
4483 for (
const auto& pending : pending_writes) {
4484 if (
const Room* room = room_lookup(pending.room_id); room !=
nullptr) {
4488 return absl::OkStatus();
4493 [&rooms](
int room_id) { return &rooms[room_id]; });
4499 rom,
static_cast<int>(rooms.size()),
4500 [&rooms](
int room_id) { return &rooms[room_id]; }, repack_layout);
4504 Rom*
rom,
int room_count,
4505 const std::function<
const Room*(
int)>& room_lookup) {
4510 const std::function<
const Room*(
int)>& room_lookup,
4516 const auto& rom_data =
rom()->vector();
4537 const int kRegionSize = 0x80;
4540 std::vector<uint8_t> blocks_data(blocks_count, 0);
4541 for (
int r = 0; r < 4; ++r) {
4542 const int slot = kPointerSlots[r];
4543 if (slot + 2 >=
static_cast<int>(rom_data.size())) {
4544 LOG_WARN(
"Room",
"LoadBlocks: pointer slot %d out of range", r);
4547 const absl::Status operand_status =
4548 ValidateBlocksLoaderPointerOperand(rom_data, slot);
4549 if (!operand_status.ok()) {
4551 std::string(operand_status.message()).c_str());
4555 (rom_data[slot + 2] << 16) | (rom_data[slot + 1] << 8) | rom_data[slot];
4557 const int off = r * kRegionSize;
4558 const int len = std::min(kRegionSize, blocks_count - off);
4561 if (pc < 0 || pc + len >
static_cast<int>(rom_data.size())) {
4562 LOG_WARN(
"Room",
"LoadBlocks: region %d data out of range", r);
4565 std::copy_n(rom_data.begin() + pc, len, blocks_data.begin() + off);
4574 return (obj.options() & ObjectOption::Block) !=
4575 ObjectOption::Nothing;
4587 for (
int i = 0; i + 3 < blocks_count; i += 4) {
4589 blocks_data[i + 2], blocks_data[i + 3]};
4605 LOG_DEBUG(
"Room",
"Loaded block at (%d,%d) draw_layer=%d behavior_layer=%d",
4614 auto rom_data =
rom()->vector();
4630 int ptr_addr = table_addr + (
room_id_ * 2);
4631 if (ptr_addr + 1 >=
static_cast<int>(rom_data.size()))
4634 uint16_t item_ptr = (rom_data[ptr_addr + 1] << 8) | rom_data[ptr_addr];
4637 int item_addr =
SnesToPc(0x010000 | item_ptr);
4640 while (item_addr + 2 <
static_cast<int>(rom_data.size())) {
4642 uint16_t position = (rom_data[item_addr + 1] << 8) | rom_data[item_addr];
4645 if (position == 0xFFFF)
4649 uint8_t item_type = rom_data[item_addr + 2];
4653 pot_item.
item = item_type;
4663 auto rom_data =
rom()->vector();
4674 const int max_offset = rom_data[
kPitCount];
4675 const int pit_entries = max_offset / 2 + 1;
4677 const int pit_ptr = (rom_data[
kPitPointer + 2] << 16) |
4681 "LoadPits: room_id=%d, RoomsWithPitDamage entries=%d, "
4682 "table_snes=0x%06X",
4691 LOG_DEBUG(
"Room",
"Per-room pit destination: target=%d, target_layer=%d",
4706 for (
const auto& sprite :
sprites_) {
4707 if (sprite.IsOverlord()) {
4717 for (
const auto& door :
doors_) {
4719 const bool is_special = [&]() ->
bool {
4720 switch (door.type) {
4751 auto options = obj.options();
4764 if (obj.id_ == 0x11E || obj.id_ == 0x11F) {
4769 if (obj.id_ >= 0xF83 && obj.id_ <= 0xF8F) {
4776 if ((obj.id_ >= 0x130 && obj.id_ <= 0x135) || obj.id_ == 0x139 ||
4777 obj.id_ == 0x13A || obj.id_ == 0x13B) {
4781 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)
absl::StatusOr< uint8_t > ReadByte(int offset) const
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)
absl::StatusOr< uint32_t > ReadLong(int offset) const
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).
void AddColor(const SnesColor &color)
absl::Status Allow(uint32_t start, uint32_t end, std::string_view label)
Editor implementation of DungeonState.
uint64_t Revision(uint16_t sheet) const
Draws dungeon objects to background buffers using game patterns.
void SetRoomFloorGraphics(uint8_t floor1, uint8_t floor2)
void SetRoomTag2(int tag2)
void SetBG1RevealMaskSource(gfx::BG1RevealMaskSource source)
void DrawDoor(const DoorDef &door, int door_index, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr, gfx::BackgroundBuffer *layout_bg2=nullptr)
Draw a door to background buffers.
void DrawChestHoleOverlay(int tag1, int tag2, const DungeonState *state, gfx::BackgroundBuffer &bg1)
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, gfx::BackgroundBuffer *layout_bg2=nullptr)
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 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 LogSurfaceState(const std::string &location, SDL_Surface *surface)
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
absl::Status Draw(int room_id, const uint8_t *gfx_data, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, DungeonState *state, uint8_t floor1_graphics, uint8_t floor2_graphics) const
const std::vector< RoomObject > & GetObjects() const
absl::Status LoadLayout(int layout_id, util::StopToken stop_token={})
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_
std::vector< SDL_Color > rendered_palette_
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
uint8_t staircase_plane_[4]
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()
uint64_t graphics_revision_
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 RenderComposite(const RoomLayerManager &layer_mgr, gfx::Bitmap &output)
void ClearObjectStreamDirty()
void ClearCustomCollisionDirty()
void CopyRoomGraphicsToBuffer()
const uint8_t * GraphicsSheetSource(int sheet_id) const
bool custom_collision_dirty() const
RoomLayerRegisters GameLayerRegisters(uint16_t room_flags=0) const
gfx::SnesPalette rendered_dungeon_palette_
uint64_t composite_rendered_source_revision_
zelda3_version_pointers version_constants() const
void set_floor2(uint8_t value)
void RestoreMetadataSnapshot(const MetadataSnapshot &snapshot)
void ClearPotItemsDirty()
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 set_floor1(uint8_t value)
bool SourceSheetsChanged() const
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)
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetObjectSaveWriteRanges(const DungeonStreamLayout *layout=nullptr) const
uint8_t cached_floor1_graphics_
bool custom_collision_dirty_
static constexpr uint8_t kObjectHeaderFloor2Dirty
void ReloadGraphics(std::optional< uint8_t > entrance_blockset=std::nullopt)
absl::Status ApplyStreamSavePlan(const StreamSavePlan &plan)
bool pot_items_dirty() const
void RecordSourceSheet(int sheet_id)
void SetTag1Direct(TagKey tag1)
void SetLayoutId(uint8_t id)
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
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetObjectStreamHeaderSaveWriteRanges(const DungeonStreamLayout *layout=nullptr) 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()
PaletteAddressMap rendered_palette_addresses_
absl::Status SaveRoomHeader()
gfx::Bitmap composite_bitmap_
std::map< uint16_t, std::vector< uint8_t > > graphics_sheet_overrides_
Room & operator=(Room &&)
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetSpriteSaveWriteRanges(const DungeonStreamLayout *layout=nullptr) const
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()
absl::StatusOr< StreamSavePlan > PlanObjectSave(const DungeonStreamLayout *layout) const
absl::StatusOr< StreamSavePlan > PlanSpriteSave(const DungeonStreamLayout *layout) const
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)
int BackgroundPaletteOffset(int slot) const
absl::StatusOr< StreamSavePlan > PlanObjectStreamHeaderSave(const DungeonStreamLayout *layout) const
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()
uint64_t composite_source_revision_
void SetSpriteset(uint8_t ss)
void MarkCompositeDirty()
Mark composite bitmap as needing regeneration.
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_
std::vector< std::pair< uint16_t, uint64_t > > source_sheet_revisions_
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
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
MetadataSnapshot CaptureMetadataSnapshot() 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)
struct destination destination
Room transition destination.
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)
absl::StatusOr< DungeonStreamWritePlan > PlanRelocateDungeonStream(const Rom &rom, int room_id, DungeonStreamKind expected_kind, const DungeonStreamLayout &layout, std::vector< uint8_t > encoded_stream)
uint64_t NextRoomCompositeRevision()
constexpr int kTorchesMaxSize
const gfx::SnesPalette * ResolvePaletteOrFirst(const gfx::PaletteGroup &group, size_t requested_index)
absl::Status PreflightBlocksPayloadDestinations(const std::array< int, 4 > &destination_pcs, int byte_length)
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::StatusOr< TorchSavePlan > BuildTorchSavePlan(const Rom *rom, int room_count, RoomLookup &&room_lookup)
void SortAndCoalesceChestRuns(std::vector< ChestWriteRun > *writes)
const LayerMergeType & LayerMergeFromHeaderByte(uint8_t byte0)
void AppendChangedChestRuns(uint32_t pc, absl::Span< const uint8_t > expected, absl::Span< const uint8_t > replacement, std::vector< ChestWriteRun > *writes)
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)
absl::Status SaveAllTorchesImpl(Rom *rom, int room_count, RoomLookup &&room_lookup)
int ReadRoomObjectAddressPc(const std::vector< uint8_t > &rom_data, int table_pc, int room_id)
uint64_t NextRoomGraphicsRevision()
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)
std::vector< uint8_t > EncodeChestRoomState(int room_id, const Room &room)
absl::StatusOr< PhysicalStreamInfo > GetSpriteStreamInfo(const std::vector< uint8_t > &rom_data, int room_id)
absl::StatusOr< BlockSavePlan > BuildBlockSavePlan(const Rom *rom, int room_count, const std::function< const Room *(int)> &room_lookup)
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)
absl::StatusOr< ChestSavePlan > BuildChestSavePlanImpl(const Rom *rom, int room_count, const std::function< const Room *(int)> &room_lookup)
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)
void CopyPaletteToCgram(const gfx::SnesPalette *source, size_t source_offset, size_t max_colors, size_t destination_offset, std::array< SDL_Color, 256 > *colors)
constexpr int kBlocksPointer4
constexpr int kSpritesDataEndExclusive
absl::Status SaveAllChests(Rom *rom, absl::Span< const Room > rooms)
constexpr int kDoorPointers
const std::string RoomTag[65]
absl::Status WriteTrackCollision(Rom *rom, int room_id, const CustomCollisionMap &map)
@ 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)
absl::StatusOr< ChestSavePlan > BuildChestSavePlan(const Rom *rom, int room_count, const std::function< const Room *(int)> &room_lookup)
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)
gfx::PaletteGroup BuildDungeonRenderPaletteGroup(const gfx::SnesPalette &dungeon_palette, const gfx::SnesPalette *hud_palette)
RoomObject::LayerType MapRoomObjectListIndexToDrawLayer(uint8_t list_index)
absl::Status SaveAllPotItems(Rom *rom, absl::Span< const Room > rooms)
gfx::PaletteGroup BuildDungeonRenderPaletteGroupFromGameData(const gfx::SnesPalette &dungeon_palette, const GameData *game_data)
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)
absl::StatusOr< DungeonStreamWritePlan > PlanDungeonStreamRepack(const DungeonStreamInventory &inventory, const std::vector< DungeonStreamReplacement > &requested_replacements)
int DungeonBackgroundPaletteOffset(uint8_t main_blockset, int destination_slot)
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
std::vector< SDL_Color > BuildDungeonRenderPalette(const gfx::SnesPalette &dungeon_palette, const gfx::SnesPalette *hud_palette, PaletteAddressMap *addresses, int palette_id)
constexpr int kRoomItemsPointers
absl::StatusOr< CustomCollisionMap > LoadCustomCollisionMap(Rom *rom, int room_id)
constexpr int kChestTableRecordSize
constexpr bool HasCustomCollisionPointerTable(std::size_t rom_size)
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetDirtyChestWriteRanges(const Rom *rom, int room_count, const std::function< const Room *(int)> &room_lookup)
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)
RoomMainBlocksetResolution ResolveRoomMainBlockset(int room_id, uint8_t header_blockset, uint8_t requested_main_blockset, const GameData &game_data)
constexpr uint32_t kDungeonPalettePointerTable
bool UsesRoomObjectStream(const RoomObject &object)
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::StatusOr< DungeonStreamRecord > ReadDungeonObjectStream(const Rom &rom, int room_id)
constexpr int kNumberOfRooms
constexpr bool IsStatefulChestObjectId(int object_id)
absl::Status ApplyChestSavePlan(Rom *rom, const ChestSavePlan &plan, const std::function< const Room *(int)> &room_lookup)
const std::string RoomEffect[8]
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetTorchSaveWriteRanges(const Rom *rom, int room_count, const std::function< const Room *(int)> &room_lookup)
constexpr int kRoomHeaderPointer
constexpr bool HasCustomCollisionDataRegion(std::size_t rom_size)
constexpr int kBlocksPointer3
constexpr int kRoomHeaderPointerBank
absl::Status ValidateRoomObjectStreamEntryForSave(const RoomObject &object)
constexpr int kCustomCollisionRoomPointers
std::array< SDL_Color, 256 > BuildDungeonSpriteRenderPalette(const Room &room, const GameData *game_data)
std::map< DungeonLimit, int > CreateLimitCounter()
absl::Status SaveAllCollision(Rom *rom, absl::Span< Room > rooms)
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetBlockSaveWriteRanges(const Rom *rom, int room_count, const std::function< const Room *(int)> &room_lookup)
PushableBlockEntry DecodePushableBlockEntry(const PushableBlockBytes &bytes)
RoomLayerRegisters DeriveRoomLayerRegisters(uint8_t bgact, bool dark, int effect, int tag2, const std::vector< RoomObject > &objects, uint16_t room_flags)
constexpr int kBlocksPointer2
constexpr int kRoomObjectPointer
std::array< std::optional< PaletteColorAddress >, 128 > PaletteAddressMap
absl::StatusOr< DungeonStreamRecord > ReadDungeonSpriteStream(const Rom &rom, int room_id)
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.
bool SetColor(int palette_index, int color_index, const SnesColor &color)
Set a specific color in a palette.
const SnesPalette & palette_ref(int i) const
void AddPalette(SnesPalette pal)
std::vector< uint8_t > original_capacity_bytes
std::vector< std::pair< uint32_t, uint32_t > > write_ranges() const
uint16_t original_byte_length
std::vector< ChestWriteRun > writes
std::array< uint8_t, 3 > pointer_operand
std::vector< ChestDirtyRoomState > dirty_rooms
std::vector< uint8_t > expected_bytes
std::vector< uint8_t > replacement_bytes
bool HasOverrides() const
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
Legacy name for sheet_store.mutable_pixels().
GraphicsSheetStore sheet_store
static Door FromRomBytes(uint8_t b1, uint8_t b2)
uint8_t object_stream_header
std::vector< std::pair< uint32_t, uint32_t > > write_ranges() const
std::optional< std::pair< uint32_t, uint32_t > > direct_long_write
std::optional< DungeonStreamWritePlan > relocation
std::vector< std::pair< uint32_t, std::vector< uint8_t > > > direct_writes
std::vector< BlockWriteDestination > destinations
std::vector< std::pair< const RoomObject *, int > > load_order_updates
std::vector< uint8_t > bytes
std::vector< uint8_t > bytes
std::vector< uint8_t > bytes
Public YAZE API umbrella header.