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);
1963 const auto& rom_data =
rom()->vector();
1969 int nbr_of_staircase = 0;
1971 int pos = objects_location;
1977 bool end_read =
false;
1981 while (!end_read && pos < (
int)
rom_->
size()) {
1983 if (pos + 1 >= (
int)
rom_->
size()) {
1988 b2 = rom_data[pos + 1];
1995 if (b1 == 0xFF && b2 == 0xFF) {
1999 "Room",
"Room %03X: Object list transition to index %d (%s)",
2001 layer == 1 ?
"BG2 overlay" : (layer == 2 ?
"BG1 overlay" :
"END"));
2011 if (b1 == 0xF0 && b2 == 0xFF) {
2018 if (pos + 2 >= (
int)
rom_->
size()) {
2022 b3 = rom_data[pos + 2];
2033 b1, b2, b3,
static_cast<uint8_t
>(layer));
2035 LOG_DEBUG(
"Room",
"Room %03X: Object 0x%03X at (%d,%d) stream=%d (%s)",
2037 layer == 0 ?
"Primary"
2038 : (layer == 1 ?
"BG2 overlay" :
"BG1 overlay"));
2042 if (r.
id_ >= 0 && r.
id_ <= 0xFFF) {
2056 "ParseDoor: room=%d b1=0x%02X b2=0x%02X pos=%d dir=%d type=%d",
2058 static_cast<int>(door.direction),
static_cast<int>(door.type));
2069 std::vector<uint8_t> bytes;
2073 std::vector<RoomObject> layer0_objects;
2074 std::vector<RoomObject> layer1_objects;
2075 std::vector<RoomObject> layer2_objects;
2088 switch (obj.GetLayerValue()) {
2090 layer0_objects.push_back(obj);
2093 layer1_objects.push_back(obj);
2096 layer2_objects.push_back(obj);
2112 for (
const auto& obj : layer0_objects) {
2113 auto encoded = obj.EncodeObjectToBytes();
2114 bytes.push_back(encoded.b1);
2115 bytes.push_back(encoded.b2);
2116 bytes.push_back(encoded.b3);
2118 bytes.push_back(0xFF);
2119 bytes.push_back(0xFF);
2122 for (
const auto& obj : layer1_objects) {
2123 auto encoded = obj.EncodeObjectToBytes();
2124 bytes.push_back(encoded.b1);
2125 bytes.push_back(encoded.b2);
2126 bytes.push_back(encoded.b3);
2128 bytes.push_back(0xFF);
2129 bytes.push_back(0xFF);
2132 for (
const auto& obj : layer2_objects) {
2133 auto encoded = obj.EncodeObjectToBytes();
2134 bytes.push_back(encoded.b1);
2135 bytes.push_back(encoded.b2);
2136 bytes.push_back(encoded.b3);
2141 bytes.push_back(0xF0);
2142 bytes.push_back(0xFF);
2143 for (
const auto& door :
doors_) {
2144 auto [b1, b2] = door.EncodeBytes();
2145 bytes.push_back(b1);
2146 bytes.push_back(b2);
2150 bytes.push_back(0xFF);
2151 bytes.push_back(0xFF);
2157 std::vector<uint8_t> bytes;
2159 for (
const auto& sprite :
sprites_) {
2167 b2 = (sprite.x() & 0x1F) | ((sprite.subtype() & 0x07) << 5);
2172 b1 = (sprite.y() & 0x1F) | ((sprite.subtype() & 0x18) << 2) |
2173 ((sprite.layer() & 0x01) << 7);
2175 bytes.push_back(b1);
2176 bytes.push_back(b2);
2177 bytes.push_back(b3);
2181 if (sprite.key_drop() == 1) {
2182 bytes.insert(bytes.end(), {0xFE, 0x00, 0xE4});
2183 }
else if (sprite.key_drop() == 2) {
2184 bytes.insert(bytes.end(), {0xFD, 0x00, 0xE4});
2189 bytes.push_back(0xFF);
2199 const auto& rom_data = rom->
vector();
2200 int sprite_pointer = 0;
2201 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
2205 const int hard_end =
2207 if (hard_end <= 0) {
2212 std::unordered_set<int> visited_addresses;
2214 int sprite_address =
2215 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
2216 if (sprite_address < kSpritesData || sprite_address >= hard_end) {
2219 if (!visited_addresses.insert(sprite_address).second) {
2224 MeasureSpriteStreamSize(rom_data, sprite_address, hard_end);
2225 int stream_end = sprite_address + stream_size;
2226 if (stream_end > max_used) {
2227 max_used = stream_end;
2235 const std::vector<uint8_t>& encoded_bytes) {
2237 return absl::InvalidArgumentError(
"ROM not loaded");
2240 return absl::OutOfRangeError(
"Room ID out of range");
2242 if (encoded_bytes.empty() || encoded_bytes.back() != 0xFF ||
2243 (encoded_bytes.size() % 3) != 1) {
2244 return absl::InvalidArgumentError(
2245 "Encoded sprite payload must be N*3 bytes plus 0xFF terminator");
2248 const auto& rom_data = rom->
vector();
2249 int sprite_pointer = 0;
2252 int old_sprite_address =
2253 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
2254 if (old_sprite_address < 0 ||
2255 old_sprite_address >=
static_cast<int>(rom_data.size())) {
2256 return absl::OutOfRangeError(
"Sprite address out of range");
2259 const uint8_t sort_mode = rom_data[old_sprite_address];
2262 const size_t required_size = 1u + encoded_bytes.size();
2264 static_cast<size_t>(write_pos) + required_size >
2266 return absl::ResourceExhaustedError(absl::StrFormat(
2267 "Not enough sprite data space. Need %d bytes at 0x%06X, "
2268 "region ends at 0x%06X",
2271 if (
static_cast<size_t>(write_pos) + required_size > rom_data.size()) {
2272 const int required_end = write_pos +
static_cast<int>(required_size);
2273 return absl::OutOfRangeError(
2274 absl::StrFormat(
"ROM too small for sprite relocation write (need "
2275 "end=0x%06X, size=0x%06X)",
2276 required_end,
static_cast<int>(rom_data.size())));
2279 std::vector<uint8_t> relocated;
2280 relocated.reserve(required_size);
2281 relocated.push_back(sort_mode);
2282 relocated.insert(relocated.end(), encoded_bytes.begin(), encoded_bytes.end());
2285 const uint32_t snes_addr =
PcToSnes(write_pos);
2286 const int ptr_off = sprite_pointer + (room_id * 2);
2290 return absl::OkStatus();
2295 std::vector<std::pair<uint32_t, uint32_t>> ranges;
2296 const auto add = [&](uint32_t address,
size_t size) {
2297 ranges.emplace_back(address, address +
static_cast<uint32_t
>(size));
2300 add(address, bytes.size());
2306 for (
const auto& write :
relocation->payload_writes) {
2307 add(write.address, write.bytes.size());
2309 for (
const auto& write :
relocation->pointer_writes) {
2310 add(write.address, write.bytes.size());
2312 for (
const auto& write :
relocation->auxiliary_pointer_writes) {
2313 add(write.address, write.bytes.size());
2330 return absl::OkStatus();
2336 if (
rom_ ==
nullptr) {
2337 return absl::InvalidArgumentError(
"ROM pointer is null");
2350 GetObjectStreamInfo(rom_data,
room_id_));
2352 bool requires_copy_on_write =
false;
2353 if (layout !=
nullptr) {
2355 DungeonStreamRequiresCopyOnWrite(
2357 encoded_bytes.size() + 2u));
2359 const int write_pos = stream_info.address + 2;
2360 const int door_list_offset =
static_cast<int>(encoded_bytes.size()) -
2361 static_cast<int>(
doors_.size()) * 2 - 2;
2362 if (door_list_offset < 0) {
2363 return absl::FailedPreconditionError(
"Invalid encoded door list offset");
2366 if (door_pointer_slot < 0 ||
2367 door_pointer_slot + 2 >=
static_cast<int>(rom_data.size())) {
2368 return absl::OutOfRangeError(
"Door pointer slot is out of range");
2373 uint32_t encoded_door_pointer =
PcToSnes(write_pos + door_list_offset);
2374 encoded_door_pointer |= source_door_pointer & 0x800000u;
2380 if (source_door_pointer == encoded_door_pointer) {
2383 source->data_pc ==
static_cast<uint32_t
>(stream_info.address) &&
2384 source->encoded_stream.size() == encoded_bytes.size() + 2u &&
2385 std::equal(encoded_bytes.begin(), encoded_bytes.end(),
2386 source->encoded_stream.begin() + 2)) {
2390 const auto relocate = [&]() -> absl::StatusOr<StreamSavePlan> {
2391 if (stream_info.address + 2 >
static_cast<int>(rom_data.size())) {
2392 return absl::OutOfRangeError(
"Object stream header is out of range");
2394 std::vector<uint8_t> replacement = {rom_data[stream_info.address],
2395 rom_data[stream_info.address + 1]};
2396 replacement.insert(replacement.end(), encoded_bytes.begin(),
2397 encoded_bytes.end());
2401 *layout, std::move(replacement)));
2405 if (stream_info.shared || requires_copy_on_write) {
2406 if (layout !=
nullptr) {
2409 return absl::FailedPreconditionError(absl::StrFormat(
2410 "Room %d object stream at PC 0x%06X is shared; repacking is required",
2413 if (stream_info.capacity() <= 2) {
2414 if (layout !=
nullptr) {
2417 return absl::FailedPreconditionError(absl::StrFormat(
2418 "Room %d object stream has no safe physical boundary",
room_id_));
2421 const int available_payload_size = stream_info.capacity() - 2;
2422 if (encoded_bytes.size() >
static_cast<size_t>(available_payload_size)) {
2423 if (layout !=
nullptr) {
2426 return absl::ResourceExhaustedError(absl::StrFormat(
2427 "Room %d object data too large! Size: %d, Available: %d",
room_id_,
2428 static_cast<int>(encoded_bytes.size()), available_payload_size));
2433 static_cast<uint32_t
>(door_pointer_slot), encoded_door_pointer);
2437absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
2449 return absl::OkStatus();
2455 if (
rom_ ==
nullptr) {
2456 return absl::InvalidArgumentError(
"ROM pointer is null");
2462 return absl::InvalidArgumentError(
2463 "Dungeon floor graphics values must be in range 0..15");
2466 return absl::InvalidArgumentError(
2467 "Dungeon layout ID must be in range 0..7");
2472 GetObjectStreamInfo(rom_data,
room_id_));
2473 if (stream_info.address < 0 ||
2474 stream_info.address + 1 >=
static_cast<int>(rom_data.size())) {
2475 return absl::OutOfRangeError(
"Object stream header is out of range");
2479 auto patch_header = [&](std::vector<uint8_t>* stream) -> absl::Status {
2480 if (stream ==
nullptr || stream->size() < 2) {
2481 return absl::DataLossError(
2482 "Object stream is missing its two-byte header");
2485 (*stream)[0] =
static_cast<uint8_t
>(((*stream)[0] & 0xF0) |
2493 (*stream)[1] =
static_cast<uint8_t
>(((*stream)[1] & 0xE3) |
2496 return absl::OkStatus();
2499 bool requires_copy_on_write = stream_info.shared;
2500 std::vector<uint8_t> replacement;
2501 if (layout !=
nullptr) {
2503 return absl::InvalidArgumentError(
2504 "Object-stream header save requires an object stream layout");
2508 if (!inventory.
ok()) {
2509 return absl::FailedPreconditionError(absl::StrFormat(
2510 "Dungeon stream inventory has %zu issue(s); refusing object "
2512 inventory.
issues.size()));
2516 return absl::OutOfRangeError(
2517 "Room ID is outside the dungeon stream layout");
2520 bool layout_requires_copy_on_write =
false;
2522 DungeonStreamRequiresCopyOnWrite(
2524 replacement.size()));
2525 requires_copy_on_write =
2526 requires_copy_on_write || layout_requires_copy_on_write;
2529 std::vector<uint8_t> header = {rom_data[stream_info.address],
2530 rom_data[stream_info.address + 1]};
2532 if (header[0] == rom_data[stream_info.address] &&
2533 header[1] == rom_data[stream_info.address + 1]) {
2537 if (requires_copy_on_write) {
2538 if (layout ==
nullptr) {
2539 return absl::FailedPreconditionError(absl::StrFormat(
2540 "Room %d object stream at PC 0x%06X is shared; a copy-on-write "
2541 "manifest is required to save its header",
2548 *layout, std::move(replacement)));
2553 plan.
direct_writes.emplace_back(stream_info.address, std::move(header));
2557absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
2572 return absl::OkStatus();
2578 if (
rom_ ==
nullptr) {
2579 return absl::InvalidArgumentError(
"ROM pointer is null");
2587 return absl::OutOfRangeError(
"Room ID out of range");
2591 GetSpriteStreamInfo(rom_data,
room_id_));
2593 bool requires_copy_on_write =
false;
2594 if (layout !=
nullptr) {
2596 DungeonStreamRequiresCopyOnWrite(
2598 encoded_bytes.size() + 1u));
2604 const uint64_t source_payload_address =
2605 static_cast<uint64_t
>(stream_info.address) + 1u;
2606 if (source_payload_address <= rom_data.size() &&
2607 encoded_bytes.size() <= rom_data.size() - source_payload_address) {
2610 source->data_pc ==
static_cast<uint32_t
>(stream_info.address) &&
2611 source->encoded_stream.size() == encoded_bytes.size() + 1u &&
2612 std::equal(encoded_bytes.begin(), encoded_bytes.end(),
2613 source->encoded_stream.begin() + 1)) {
2617 const auto relocate = [&]() -> absl::StatusOr<StreamSavePlan> {
2618 std::vector<uint8_t> replacement = {rom_data[stream_info.address]};
2619 replacement.insert(replacement.end(), encoded_bytes.begin(),
2620 encoded_bytes.end());
2624 *layout, std::move(replacement)));
2628 if (stream_info.shared || requires_copy_on_write) {
2629 if (layout !=
nullptr) {
2632 return absl::FailedPreconditionError(absl::StrFormat(
2633 "Room %d sprite stream at PC 0x%06X is shared; repacking is required",
2636 if (stream_info.capacity() <= 1) {
2637 if (layout !=
nullptr) {
2640 return absl::FailedPreconditionError(absl::StrFormat(
2641 "Room %d sprite stream has no safe physical boundary",
room_id_));
2644 const int available_payload_size = stream_info.capacity() - 1;
2645 const int payload_address = stream_info.address + 1;
2646 if (payload_address < 0 ||
2647 payload_address >=
static_cast<int>(
rom_->
size())) {
2648 return absl::OutOfRangeError(absl::StrFormat(
2649 "Room %d has invalid sprite payload address",
room_id_));
2652 if (
static_cast<int>(encoded_bytes.size()) > available_payload_size) {
2653 if (layout !=
nullptr) {
2656 return absl::ResourceExhaustedError(absl::StrFormat(
2657 "Room %d sprite data too large! Size: %d, Available: %d; repacking "
2659 room_id_,
static_cast<int>(encoded_bytes.size()),
2660 available_payload_size));
2663 plan.
direct_writes.emplace_back(payload_address, encoded_bytes);
2667absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
2679 return absl::OkStatus();
2683 if (
rom_ ==
nullptr) {
2684 return absl::InvalidArgumentError(
"ROM pointer is null");
2687 const auto& rom_data =
rom()->vector();
2690 return absl::OutOfRangeError(
"Room header pointer out of range");
2694 return absl::OutOfRangeError(
"Room header pointer bank out of range");
2700 header_pointer =
SnesToPc(header_pointer);
2702 int table_offset = header_pointer + (
room_id_ * 2);
2703 if (table_offset < 0 ||
2704 table_offset + 1 >=
static_cast<int>(rom_data.size())) {
2705 return absl::OutOfRangeError(
"Room header table offset out of range");
2709 (rom_data[table_offset + 1] << 8) + rom_data[table_offset];
2710 int header_location =
SnesToPc(address);
2712 if (header_location < 0 ||
2713 header_location + 13 >=
static_cast<int>(rom_data.size())) {
2714 return absl::OutOfRangeError(
"Room header location out of range");
2721 if (
bg2() != background2::DarkRoom) {
2722 layer2_mode_for_save =
static_cast<uint8_t
>(
bg2()) & 0x07;
2724 const bool dark_room =
2726 uint8_t byte0 =
static_cast<uint8_t
>(
2727 (layer2_mode_for_save << 5) |
2728 ((
static_cast<uint8_t
>(
collision()) & 0x07) << 2) |
2729 (rom_data[header_location] & 0x02) | (dark_room ? 1 : 0));
2737 const uint8_t byte8 =
static_cast<uint8_t
>(
2738 (rom_data[header_location + 8] & 0xFC) | (
staircase_plane(3) & 0x03));
2759 if (msg_addr < 0 || msg_addr + 1 >=
static_cast<int>(rom_data.size())) {
2760 return absl::OutOfRangeError(
"Message ID address out of range");
2766 return absl::OkStatus();
2776 return absl::InvalidArgumentError(
"Invalid object parameters");
2784 return absl::OkStatus();
2789 return absl::OutOfRangeError(
"Object index out of range");
2797 return absl::OkStatus();
2802 return absl::OutOfRangeError(
"Object index out of range");
2806 return absl::InvalidArgumentError(
"Invalid object parameters");
2814 return absl::OkStatus();
2820 if (obj.x() == x && obj.y() == y && obj.GetLayerValue() == layer) {
2824 return absl::NotFoundError(
"No object found at position");
2829 if (
object.x() < 0 ||
object.x() > 63)
2831 if (
object.y() < 0 ||
object.y() > 63)
2835 if (
object.GetLayerValue() < 0 ||
object.GetLayerValue() > 2)
2839 if (
object.id_ < 0 || object.id_ > 0xFFF)
2843 if (
object.id_ < 0x100 &&
object.size() > 15)
2850 int& nbr_of_staircase) {
2853 static const auto kStaircaseLabels = [] {
2854 std::array<std::string, 256> labels;
2855 for (
size_t room = 0; room < labels.size(); ++room) {
2856 labels[room] = absl::StrCat(
"To ", room);
2864 if (nbr_of_staircase < 4) {
2888 const auto& rom_data =
rom()->vector();
2896 int sprite_pointer = 0;
2897 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
2901 int sprite_address =
2902 ReadRoomSpriteAddressPc(rom_data, sprite_pointer,
room_id_);
2903 if (sprite_address < 0 ||
2904 sprite_address + 1 >=
static_cast<int>(rom_data.size())) {
2909 sprite_address += 1;
2911 while (sprite_address + 2 <
static_cast<int>(rom_data.size())) {
2912 uint8_t b1 = rom_data[sprite_address];
2913 uint8_t b2 = rom_data[sprite_address + 1];
2914 uint8_t b3 = rom_data[sprite_address + 2];
2920 sprites_.emplace_back(b3, (b2 & 0x1F), (b1 & 0x1F),
2921 ((b2 & 0xE0) >> 5) + ((b1 & 0x60) >> 2),
2928 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1E &&
2934 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1D &&
2941 sprite_address += 3;
2953 const auto& rom_data =
rom()->vector();
2959 const int cpos =
static_cast<int>(
SnesToPc(
2963 const size_t byte_length =
2966 const size_t bounded_byte_length = std::min<size_t>(
2967 byte_length, cpos >= 0 && cpos <
static_cast<int>(rom_data.size())
2968 ? rom_data.size() -
static_cast<size_t>(cpos)
2970 const size_t record_count = std::min<size_t>(
2973 for (
size_t i = 0; i < record_count; ++i) {
2974 const size_t offset =
2976 if ((((rom_data[offset + 1] << 8) + rom_data[offset]) & 0x7FFF) ==
2980 if ((((rom_data[offset + 1] << 8) + rom_data[offset]) & 0x8000) ==
2992 auto rom_data =
rom()->vector();
3003 "LoadDoors for room %d - doors are loaded via object stream",
3008 auto rom_data =
rom()->vector();
3021 return (obj.options() & ObjectOption::Torch) !=
3022 ObjectOption::Nothing;
3027 for (
int i = 0; i < bytes_count; i += 2) {
3028 if (i + 1 >= bytes_count)
3035 if (b1 == 0xFF && b2 == 0xFF) {
3040 uint16_t torch_room_id = (b2 << 8) | b1;
3044 while (i < bytes_count) {
3045 if (i + 1 >= bytes_count)
3052 if (b1 == 0xFF && b2 == 0xFF) {
3060 torch_obj.SetRom(
rom_);
3062 torch_obj.set_torch_reserved_bit(entry.
reserved);
3063 torch_obj.lit_ = entry.
lit;
3068 "Room",
"Loaded torch at (%d,%d) draw_layer=%d reserved=%d lit=%d",
3077 while (i < bytes_count) {
3078 if (i + 1 >= bytes_count)
3082 if (b1 == 0xFF && b2 == 0xFF) {
3097 uint16_t room_id = 0;
3103 const std::vector<uint8_t>& rom_data,
int bytes_count) {
3104 std::vector<TorchSegment> segments;
3109 if (b1 == 0xFF && b2 == 0xFF) {
3115 padding.
bytes = {0xFF, 0xFF};
3116 segments.push_back(std::move(padding));
3120 uint16_t room_id = (b2 << 8) | b1;
3127 seg.
bytes.push_back(b1);
3128 seg.
bytes.push_back(b2);
3133 if (b1 == 0xFF && b2 == 0xFF) {
3134 seg.
bytes.push_back(0xFF);
3135 seg.
bytes.push_back(0xFF);
3139 seg.
bytes.push_back(b1);
3140 seg.
bytes.push_back(b2);
3143 segments.push_back(std::move(seg));
3149 std::vector<uint8_t> bytes;
3154 if (bytes.empty()) {
3155 bytes.push_back(room_id & 0xFF);
3156 bytes.push_back((room_id >> 8) & 0xFF);
3159 .px =
static_cast<uint8_t
>(obj.x()),
3160 .py =
static_cast<uint8_t
>(obj.y()),
3161 .draw_layer =
static_cast<uint8_t
>(obj.GetLayerValue() & 1),
3162 .reserved = obj.torch_reserved_bit(),
3165 bytes.push_back(encoded.
low);
3166 bytes.push_back(encoded.
high);
3168 if (!bytes.empty()) {
3169 bytes.push_back(0xFF);
3170 bytes.push_back(0xFF);
3177 const char* object_type) {
3178 const uint8_t selector =
object.GetLayerValue();
3179 if (selector <= 1) {
3180 return absl::OkStatus();
3182 return absl::InvalidArgumentError(absl::StrFormat(
3183 "%s in room 0x%03X has invalid special draw-layer selector %d; "
3185 "(upper/BG1) or 1 (lower/BG2)",
3186 object_type, room_id, selector));
3193 if (
object.x() <= 0x3E &&
object.y() <= 0x3E) {
3194 return absl::OkStatus();
3196 return absl::InvalidArgumentError(absl::StrFormat(
3197 "Torch in room 0x%03X has invalid position (%d,%d); expected x/y in "
3199 room_id,
object.x(),
object.y()));
3209 bool any_owned_room =
false;
3210 bool needs_write =
false;
3213template <
typename RoomLookup>
3215 RoomLookup&& room_lookup) {
3216 if (!
rom || !
rom->is_loaded()) {
3217 return absl::InvalidArgumentError(
"ROM not loaded");
3220 const auto& rom_data =
rom->vector();
3223 return absl::OutOfRangeError(
"Torch length pointer is out of range");
3231 static_cast<uint64_t
>(
kTorchData) + existing_count > rom_data.size()) {
3232 return absl::OutOfRangeError(
"Torch data is out of range");
3237 auto& bytes = plan.
bytes;
3238 const int room_limit = std::clamp(room_count, 0,
kNumberOfRooms);
3240 std::vector<bool> owned_rooms(room_limit,
false);
3241 std::vector<bool> seen_original_room(room_limit,
false);
3242 std::vector<bool> emitted_owned_room(room_limit,
false);
3243 std::vector<std::vector<uint8_t>> replacements(room_limit);
3244 bool any_owned_room =
false;
3245 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3246 const Room* room = room_lookup(room_id);
3247 const bool room_owned =
3257 owned_rooms[room_id] =
true;
3258 any_owned_room =
true;
3262 if (!any_owned_room) {
3267 for (
const auto& segment : rom_segments) {
3268 if (segment.room_id < room_limit) {
3269 seen_original_room[segment.room_id] =
true;
3270 if (owned_rooms[segment.room_id]) {
3271 if (!emitted_owned_room[segment.room_id]) {
3272 bytes.insert(bytes.end(), replacements[segment.room_id].begin(),
3273 replacements[segment.room_id].end());
3274 emitted_owned_room[segment.room_id] =
true;
3279 bytes.insert(bytes.end(), segment.bytes.begin(), segment.bytes.end());
3282 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3283 if (owned_rooms[room_id] && !seen_original_room[room_id] &&
3284 !replacements[room_id].empty()) {
3285 bytes.insert(bytes.end(), replacements[room_id].begin(),
3286 replacements[room_id].end());
3291 return absl::ResourceExhaustedError(
3292 absl::StrFormat(
"Torch data too large: %d bytes (max %d)", bytes.size(),
3295 if (
static_cast<uint64_t
>(
kTorchData) + bytes.size() > rom_data.size()) {
3296 return absl::OutOfRangeError(
"Torch save data is out of range");
3299 const uint16_t current_len =
3303 current_len != bytes.size() ||
3304 !std::equal(bytes.begin(), bytes.end(), rom_data.begin() +
kTorchData);
3308template <
typename RoomLookup>
3310 RoomLookup&& room_lookup) {
3312 if (!plan.any_owned_room) {
3313 return absl::OkStatus();
3316 if (plan.needs_write) {
3318 static_cast<uint16_t
>(plan.bytes.size())));
3321 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
3322 if (
const Room* room = room_lookup(room_id);
3327 return absl::OkStatus();
3333 return SaveAllTorchesImpl(
rom,
static_cast<int>(rooms.size()),
3334 [&rooms](
int room_id) { return &rooms[room_id]; });
3338 Rom*
rom,
int room_count,
3339 const std::function<
const Room*(
int)>& room_lookup) {
3340 return SaveAllTorchesImpl(
rom, room_count, room_lookup);
3343absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
3345 const std::function<
const Room*(
int)>& room_lookup) {
3347 BuildTorchSavePlan(
rom, room_count, room_lookup));
3348 if (!plan.needs_write) {
3349 return std::vector<std::pair<uint32_t, uint32_t>>{};
3351 return std::vector<std::pair<uint32_t, uint32_t>>{
3364 if (pit_damage_table !=
nullptr && pit_damage_table->
dirty()) {
3367 return absl::OkStatus();
3369 if (!
rom || !
rom->is_loaded()) {
3370 return absl::InvalidArgumentError(
"ROM not loaded");
3372 const auto& rom_data =
rom->vector();
3373 if (kPitCount < 0 || kPitCount >=
static_cast<int>(rom_data.size()) ||
3374 kPitPointer + 2 >=
static_cast<int>(rom_data.size())) {
3375 return absl::OutOfRangeError(
"Pit count/pointer out of range");
3381 int data_len = max_offset + 2;
3382 int pit_ptr_snes = (rom_data[
kPitPointer + 2] << 16) |
3384 int pit_data_pc =
SnesToPc(pit_ptr_snes);
3385 if (pit_data_pc < 0 ||
3386 pit_data_pc + data_len >
static_cast<int>(rom_data.size())) {
3387 return absl::OutOfRangeError(
"Pit data region out of range");
3389 std::vector<uint8_t> data(rom_data.begin() + pit_data_pc,
3390 rom_data.begin() + pit_data_pc + data_len);
3395 return rom->WriteVector(pit_data_pc, data);
3406 return first_begin < second_end && second_begin < first_end;
3410 const std::vector<uint8_t>& rom_data,
int operand_pc) {
3411 if (operand_pc <= 0 || operand_pc + 5 >=
static_cast<int>(rom_data.size())) {
3412 return absl::OutOfRangeError(
"Blocks pointer operand out of range");
3426 if (rom_data[operand_pc - 1] != 0xBF || rom_data[operand_pc + 3] != 0x9D) {
3427 return absl::FailedPreconditionError(absl::StrFormat(
3428 "Blocks pointer operand at PC 0x%05X is not in the expected "
3429 "LDA.l ...,X / STA.w loader sequence",
3432 return absl::OkStatus();
3436 const std::vector<uint8_t>& rom_data, std::array<int, 4>* destination_pcs) {
3439 return absl::OutOfRangeError(
"Blocks length out of range");
3445 const int snes = (rom_data[operand_pc + 2] << 16) |
3446 (rom_data[operand_pc + 1] << 8) | rom_data[operand_pc];
3447 const int data_pc =
SnesToPc(snes);
3450 return absl::OutOfRangeError(absl::StrFormat(
3451 "Blocks data region out of range for loader page %d", page + 1));
3453 (*destination_pcs)[page] = data_pc;
3457 for (
size_t page = 0; page < destination_pcs->size(); ++page) {
3458 const int page_begin = (*destination_pcs)[page];
3461 kLengthMetadataEnd)) {
3462 return absl::FailedPreconditionError(absl::StrFormat(
3463 "Blocks data page %d at PC [0x%05X, 0x%05X) overlaps block-table "
3464 "length metadata [0x%05X, 0x%05X)",
3465 page + 1, page_begin, page_end,
kBlocksLength, kLengthMetadataEnd));
3476 return absl::FailedPreconditionError(absl::StrFormat(
3477 "Blocks data page %d at PC [0x%05X, 0x%05X) overlaps loader %d "
3478 "opcode/operand metadata [0x%05X, 0x%05X)",
3479 page + 1, page_begin, page_end, loader + 1, loader_begin,
3484 for (
size_t previous = 0; previous < page; ++previous) {
3485 const int previous_begin = (*destination_pcs)[previous];
3489 return absl::FailedPreconditionError(absl::StrFormat(
3490 "Blocks data pages %d and %d overlap at PC ranges [0x%05X, "
3491 "0x%05X) and [0x%05X, 0x%05X)",
3492 previous + 1, page + 1, previous_begin, previous_end, page_begin,
3498 return absl::OkStatus();
3509 const std::array<int, 4>& destination_pcs,
int byte_length) {
3511 byte_length % 4 != 0) {
3512 return absl::FailedPreconditionError(
3513 "Pushable-block length must contain whole four-byte records within "
3514 "the 0x200-byte WRAM buffer");
3516 for (
size_t page = 0; page < destination_pcs.size(); ++page) {
3521 const int begin = destination_pcs[page];
3522 const int end = begin + length;
3525 return absl::FailedPreconditionError(absl::StrFormat(
3526 "Pushable-block ROM capacity exceeded: loader page %d payload "
3527 "[0x%05X, 0x%05X) overlaps torch data [0x%05X, 0x%05X). "
3528 "Repoint the block data to independent ROM storage before growing "
3534 return absl::FailedPreconditionError(absl::StrFormat(
3535 "Blocks loader page %d payload overlaps torch length metadata",
3539 return absl::OkStatus();
3545 if (!
rom || !
rom->is_loaded()) {
3546 return absl::InvalidArgumentError(
"ROM not loaded");
3548 const auto& rom_data =
rom->vector();
3549 if (
kBlocksLength + 1 >=
static_cast<int>(rom_data.size())) {
3550 return absl::OutOfRangeError(
"Blocks length out of range");
3554 std::array<int, 4> destination_pcs{};
3556 PreflightBlocksLoaderDestinations(rom_data, &destination_pcs));
3558 PreflightBlocksPayloadDestinations(destination_pcs, blocks_count));
3559 if (blocks_count <= 0) {
3560 return absl::OkStatus();
3562 for (
int r = 0; r < 4; ++r) {
3563 const int pc = destination_pcs[r];
3564 int off = r * kBlocksRegionSize;
3565 int len = std::min(kBlocksRegionSize, blocks_count - off);
3568 std::vector<uint8_t> chunk(rom_data.begin() + pc,
3569 rom_data.begin() + pc + len);
3574 return absl::OkStatus();
3592 const Rom*
rom,
int room_count,
3593 const std::function<
const Room*(
int)>& room_lookup) {
3594 if (!
rom || !
rom->is_loaded()) {
3595 return absl::InvalidArgumentError(
"ROM not loaded");
3597 const auto& rom_data =
rom->vector();
3598 if (
kBlocksLength + 1 >=
static_cast<int>(rom_data.size())) {
3599 return absl::OutOfRangeError(
"Blocks length out of range");
3602 std::array<int, 4> destination_pcs{};
3612 const int original_count_word =
3614 const int original_byte_len = original_count_word;
3617 std::vector<uint8_t> original_buffer(original_byte_len, 0);
3618 for (
int r = 0; r < 4; ++r) {
3619 const int pc = destination_pcs[r];
3624 std::copy_n(rom_data.begin() + pc, len, original_buffer.begin() + off);
3626 const int original_slot_count = original_byte_len / 4;
3637 struct EncodedBlock {
3641 std::unordered_set<uint16_t> owned_room_ids;
3642 std::unordered_map<int, EncodedBlock> slot_replacements;
3643 std::vector<EncodedBlock> appended;
3644 for (
int rid = 0; rid < room_count; ++rid) {
3645 const Room* room = room_lookup(rid);
3646 if (room ==
nullptr)
3650 return absl::FailedPreconditionError(absl::StrFormat(
3651 "Room 0x%03X has unsaved pushable-block edits, but its block "
3652 "table is not loaded. Load the room's blocks before saving.",
3657 owned_room_ids.insert(
static_cast<uint16_t
>(rid));
3661 std::unordered_set<int> claimed_load_orders;
3668 encoded_entry.
room_id =
static_cast<uint16_t
>(rid);
3669 encoded_entry.
px = obj.x();
3670 encoded_entry.
py = obj.y();
3671 encoded_entry.
draw_layer = obj.GetLayerValue();
3675 const EncodedBlock encoded_block{encoded, &obj};
3676 const int load_order = obj.block_load_order();
3677 if (load_order >= 0 && !claimed_load_orders.insert(load_order).second) {
3678 return absl::FailedPreconditionError(absl::StrFormat(
3679 "Room 0x%03X has multiple pushable blocks claiming non-new "
3680 "load-order slot %d",
3684 appended.push_back(encoded_block);
3685 }
else if (load_order >= 0 && load_order < original_slot_count) {
3686 const int original_offset = load_order * 4;
3687 const uint16_t original_room_id =
3688 static_cast<uint16_t
>(original_buffer[original_offset] |
3689 (original_buffer[original_offset + 1] << 8));
3690 if (original_room_id !=
static_cast<uint16_t
>(rid)) {
3695 appended.push_back(encoded_block);
3698 slot_replacements.emplace(load_order, encoded_block);
3702 appended.push_back(encoded_block);
3709 std::vector<uint8_t> output;
3710 output.reserve(original_byte_len + appended.size() * 4);
3711 std::vector<std::pair<const RoomObject*, int>> load_order_updates;
3712 load_order_updates.reserve(slot_replacements.size() + appended.size());
3713 const auto append_encoded_block =
3714 [&output, &load_order_updates](
const EncodedBlock& block) {
3715 const int output_slot =
static_cast<int>(output.size() / 4);
3716 output.push_back(block.bytes.b1);
3717 output.push_back(block.bytes.b2);
3718 output.push_back(block.bytes.b3);
3719 output.push_back(block.bytes.b4);
3720 load_order_updates.emplace_back(block.source_object, output_slot);
3722 for (
int slot = 0; slot < original_slot_count; ++slot) {
3723 const uint8_t b1 = original_buffer[slot * 4 + 0];
3724 const uint8_t b2 = original_buffer[slot * 4 + 1];
3725 const uint16_t slot_room_id =
static_cast<uint16_t
>(b1 | (b2 << 8));
3726 if (owned_room_ids.contains(slot_room_id)) {
3727 const auto it = slot_replacements.find(slot);
3728 if (it == slot_replacements.end()) {
3733 append_encoded_block(it->second);
3736 output.push_back(b1);
3737 output.push_back(b2);
3738 output.push_back(original_buffer[slot * 4 + 2]);
3739 output.push_back(original_buffer[slot * 4 + 3]);
3745 for (
const auto& block : appended) {
3746 append_encoded_block(block);
3755 if (output.empty()) {
3756 return absl::FailedPreconditionError(
3757 "Pushable-block table cannot be empty: ALTTP's runtime scan reads one "
3758 "entry before comparing the byte-length limit. Keep at least one "
3759 "pushable block, or patch the runtime loop before removing the last "
3765 if (
static_cast<int>(output.size() / 4) > kMaxEntries) {
3766 return absl::FailedPreconditionError(absl::StrCat(
3767 "Pushable-block table overflow: ", output.size() / 4,
3768 " entries exceeds the runtime WRAM cap of ", kMaxEntries,
3769 " (a larger table requires runtime buffer and loader changes)."));
3776 const int total_bytes =
static_cast<int>(output.size());
3781 write_destinations.reserve(4);
3782 for (
int r = 0; r < 4; ++r) {
3787 write_destinations.push_back({destination_pcs[r], off, len});
3790 plan.
bytes = std::move(output);
3798 const std::function<
const Room*(
int)>& room_lookup) {
3803 for (
const auto&
destination : plan.destinations) {
3804 std::vector<uint8_t> chunk(
3818 for (
const auto& [
object, load_order] : plan.load_order_updates) {
3819 const_cast<RoomObject*
>(object)->set_block_load_order(load_order);
3821 for (
int room_id = 0; room_id < room_count; ++room_id) {
3822 if (
const Room* room = room_lookup(room_id);
3827 return absl::OkStatus();
3830absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
3832 const std::function<
const Room*(
int)>& room_lookup) {
3834 BuildBlockSavePlan(
rom, room_count, room_lookup));
3835 std::vector<std::pair<uint32_t, uint32_t>> ranges;
3836 ranges.reserve(plan.destinations.size() + 1);
3837 for (
const auto&
destination : plan.destinations) {
3844template <
typename RoomLookup>
3846 RoomLookup&& room_lookup) {
3847 if (!
rom || !
rom->is_loaded()) {
3848 return absl::InvalidArgumentError(
"ROM not loaded");
3855 const auto& rom_data =
rom->vector();
3860 if (!has_ptr_table) {
3861 for (
int room_id = 0; room_id < room_count; ++room_id) {
3862 const Room* room = room_lookup(room_id);
3864 return absl::FailedPreconditionError(
3865 "Custom collision region not present in this ROM");
3868 return absl::OkStatus();
3871 if (!has_data_region) {
3872 for (
int room_id = 0; room_id < room_count; ++room_id) {
3873 const Room* room = room_lookup(room_id);
3875 return absl::FailedPreconditionError(
3876 "Custom collision data region not present in this ROM");
3879 return absl::OkStatus();
3888 "CustomCollisionPointers"));
3892 "CustomCollisionData"));
3896 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3897 const Room* room = room_lookup(room_id);
3902 const int actual_room_id = room->
id();
3904 if (ptr_offset + 2 >=
static_cast<int>(rom_data.size())) {
3905 return absl::OutOfRangeError(
"Custom collision pointer out of range");
3931 return absl::OkStatus();
3936 rom,
static_cast<int>(rooms.size()),
3937 [&rooms](
int room_id) { return &rooms[room_id]; });
3941 const std::function<
Room*(
int)>& room_lookup) {
3945absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
3947 if (
rom ==
nullptr || !
rom->is_loaded()) {
3948 return absl::InvalidArgumentError(
"ROM not loaded");
3950 const auto& rom_data =
rom->vector();
3953 return absl::OutOfRangeError(
"Chest pointers out of range");
3956 const uint32_t data_pointer =
3960 const uint32_t data_pc =
SnesToPc(data_pointer);
3961 if (data_pc > rom_data.size() ||
3963 rom_data.size() -
static_cast<size_t>(data_pc)) {
3964 return absl::OutOfRangeError(
"Chest data region out of range");
3966 const uint32_t data_end =
3968 const auto overlaps = [](uint32_t begin, uint32_t end, uint32_t other_begin,
3969 uint32_t other_end) {
3970 return begin < other_end && other_begin < end;
3976 return absl::FailedPreconditionError(
3977 "Chest data region overlaps chest metadata operands");
3980 return std::vector<std::pair<uint32_t, uint32_t>>{
3983 {data_pc, data_end},
3993 uint16_t
room_id()
const {
return word & 0x7FFF; }
3999 const std::vector<uint8_t>& rom_data,
int cpos,
int byte_length) {
4000 std::vector<PhysicalChestRecord> records;
4002 records.reserve(record_count);
4003 for (
int i = 0; i < record_count; ++i) {
4009 const uint16_t word =
4010 (
static_cast<uint16_t
>(rom_data[off + 1]) << 8) | rom_data[off];
4018 bytes->push_back(word & 0xFF);
4019 bytes->push_back((word >> 8) & 0xFF);
4020 bytes->push_back(item);
4025 const uint16_t word =
static_cast<uint16_t
>(room_id) |
4026 (
chest.size ?
static_cast<uint16_t
>(0x8000) : 0);
4031 absl::Span<const uint8_t> replacement,
4032 std::vector<ChestWriteRun>* writes) {
4034 while (cursor < replacement.size()) {
4035 if (replacement[cursor] == expected[cursor]) {
4039 const size_t begin = cursor;
4042 }
while (cursor < replacement.size() &&
4043 replacement[cursor] != expected[cursor]);
4046 run.
pc = pc +
static_cast<uint32_t
>(begin);
4048 expected.begin() + cursor);
4050 replacement.begin() + cursor);
4051 if (!writes->empty() && writes->back().end() == run.
pc) {
4052 writes->back().expected_bytes.insert(writes->back().expected_bytes.end(),
4055 writes->back().replacement_bytes.insert(
4059 writes->push_back(std::move(run));
4065 std::sort(writes->begin(), writes->end(),
4067 return lhs.pc < rhs.pc;
4069 std::vector<ChestWriteRun> merged;
4070 merged.reserve(writes->size());
4072 if (!merged.empty() && merged.back().end() == write.pc) {
4073 merged.back().expected_bytes.insert(merged.back().expected_bytes.end(),
4074 write.expected_bytes.begin(),
4075 write.expected_bytes.end());
4076 merged.back().replacement_bytes.insert(
4077 merged.back().replacement_bytes.end(),
4078 write.replacement_bytes.begin(), write.replacement_bytes.end());
4081 merged.push_back(std::move(write));
4083 *writes = std::move(merged);
4087 std::vector<uint8_t> bytes;
4096 const Rom*
rom,
int room_count,
4097 const std::function<
const Room*(
int)>& room_lookup) {
4098 if (
rom ==
nullptr || !
rom->is_loaded()) {
4099 return absl::InvalidArgumentError(
"ROM not loaded");
4101 const auto& rom_data =
rom->vector();
4104 return absl::OutOfRangeError(
"Chest pointers out of range");
4110 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
4111 const Room* room = room_lookup(room_id);
4113 dirty_rooms[room_id] = room;
4124 plan.
data_pc = potential_ranges[1].first;
4131 rom_data.begin() + plan.
data_pc,
4135 return absl::FailedPreconditionError(absl::StrFormat(
4136 "Chest table byte length %d is invalid (capacity %d)",
4142 if (physical_records.size() !=
4144 return absl::OutOfRangeError(
"Chest data region is truncated");
4150 ++old_counts[record.room_id()];
4154 size_t final_record_count = physical_records.size();
4155 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
4156 if (dirty_rooms[room_id] ==
nullptr) {
4159 final_record_count -= old_counts[room_id];
4160 final_record_count += dirty_rooms[room_id]->GetChests().size();
4163 return absl::ResourceExhaustedError(absl::StrFormat(
4164 "Chest table has %d records; capacity is %d",
4168 std::vector<uint8_t> replacement_bytes;
4172 const uint16_t room_id = record.room_id();
4173 const Room* dirty_room =
4175 if (dirty_room ==
nullptr) {
4180 const size_t occurrence = seen_counts[room_id]++;
4181 const auto& replacements = dirty_room->
GetChests();
4182 if (occurrence < replacements.size()) {
4184 replacements[occurrence]);
4191 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
4192 const Room* dirty_room = dirty_rooms[room_id];
4193 if (dirty_room ==
nullptr) {
4196 const auto& replacements = dirty_room->
GetChests();
4197 for (
size_t i = seen_counts[room_id]; i < replacements.size(); ++i) {
4203 return absl::InternalError(
"Chest save plan size mismatch");
4206 const std::array<uint8_t, 2> expected_length = {
4209 const uint16_t replacement_length =
4210 static_cast<uint16_t
>(replacement_bytes.size());
4211 const std::array<uint8_t, 2> encoded_replacement_length = {
4212 static_cast<uint8_t
>(replacement_length & 0xFF),
4213 static_cast<uint8_t
>((replacement_length >> 8) & 0xFF)};
4215 encoded_replacement_length, &plan.
writes);
4218 .subspan(0, replacement_bytes.size()),
4219 replacement_bytes, &plan.
writes);
4230 if (ptr_off < 0 || ptr_off + 1 >=
static_cast<int>(rom_data.size())) {
4233 const uint16_t item_ptr =
4234 (
static_cast<uint16_t
>(rom_data[ptr_off + 1]) << 8) | rom_data[ptr_off];
4235 if (item_ptr < 0x8000) {
4238 const int item_addr =
static_cast<int>(
SnesToPc(0x010000 | item_ptr));
4239 return item_addr >= 0 && item_addr < static_cast<int>(rom_data.size())
4245 const std::vector<uint8_t>& rom_data,
int room_id) {
4247 static_cast<int>(rom_data.size())) {
4248 return absl::OutOfRangeError(
"Room items pointer table out of range");
4251 return absl::OutOfRangeError(
"Room ID out of range");
4258 const int hard_end =
4262 return absl::FailedPreconditionError(
4263 "Room pot item pointer is null, invalid, or outside the item region");
4271 const Rom*
rom,
int room_count,
4272 const std::function<
const Room*(
int)>& room_lookup) {
4273 return BuildChestSavePlanImpl(
rom, room_count, room_lookup);
4276absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
4278 const std::function<
const Room*(
int)>& room_lookup) {
4286 const std::function<
const Room*(
int)>& room_lookup) {
4287 if (
rom ==
nullptr || !
rom->is_loaded()) {
4288 return absl::InvalidArgumentError(
"ROM not loaded");
4293 if (canonical_plan != plan) {
4294 return absl::FailedPreconditionError(
4295 "Chest save plan does not match canonical serialization");
4297 return absl::OkStatus();
4300 const auto& rom_data =
rom->vector();
4303 plan.
data_pc > rom_data.size() ||
4305 return absl::FailedPreconditionError(
4306 "Chest save plan source is no longer addressable");
4310 return absl::FailedPreconditionError(
4311 "Chest save plan is stale: data pointer changed");
4313 const uint16_t current_length =
4317 return absl::FailedPreconditionError(
4318 "Chest save plan is stale: runtime length changed");
4322 rom_data.begin() + plan.
data_pc)) {
4323 return absl::FailedPreconditionError(
4324 "Chest save plan is stale: table bytes changed");
4327 const Room* room = room_lookup(state.room_id);
4329 EncodeChestRoomState(state.room_id, *room) != state.encoded_chests) {
4330 return absl::FailedPreconditionError(absl::StrFormat(
4331 "Chest save plan is stale for room 0x%03X", state.room_id));
4336 if (canonical_plan != plan) {
4337 return absl::FailedPreconditionError(
4338 "Chest save plan does not match canonical serialization");
4354 return absl::OkStatus();
4357template <
typename RoomLookup>
4359 RoomLookup&& room_lookup) {
4360 const std::function<
const Room*(int)> lookup =
4361 std::forward<RoomLookup>(room_lookup);
4369 [&rooms](
int room_id) { return &rooms[room_id]; });
4373 const std::function<
const Room*(
int)>& room_lookup) {
4377template <
typename RoomLookup>
4379 Rom*
rom,
int room_count, RoomLookup&& room_lookup,
4381 if (!
rom || !
rom->is_loaded()) {
4382 return absl::InvalidArgumentError(
"ROM not loaded");
4384 const auto& rom_data =
rom->vector();
4386 static_cast<int>(rom_data.size())) {
4387 return absl::OutOfRangeError(
"Room items pointer table out of range");
4391 if (repack_layout !=
nullptr) {
4392 std::vector<DungeonStreamReplacement> replacements;
4393 for (
int room_id = 0; room_id < room_limit; ++room_id) {
4394 const Room* room = room_lookup(room_id);
4400 replacement.
room_id =
static_cast<uint32_t
>(room_id);
4404 replacement.
encoded_stream.push_back((item.position >> 8) & 0xFF);
4409 replacements.push_back(std::move(replacement));
4411 if (replacements.empty()) {
4412 return absl::OkStatus();
4421 if (
const Room* room = room_lookup(replacement.room_id);
4426 return absl::OkStatus();
4429 struct PendingPotItemWrite {
4432 std::vector<uint8_t> bytes;
4437 std::vector<PendingPotItemWrite> pending_writes;
4438 for (
int room_id = 0; room_id < room_limit; ++room_id) {
4439 const Room* room = room_lookup(room_id);
4445 GetPotItemStreamInfo(rom_data, room_id));
4446 if (stream_info.shared) {
4447 return absl::FailedPreconditionError(absl::StrFormat(
4448 "Room %d pot item stream at PC 0x%06X is shared; repacking is "
4450 room_id, stream_info.address));
4452 if (stream_info.capacity() <= 0) {
4453 return absl::FailedPreconditionError(absl::StrFormat(
4454 "Room %d pot item stream has no safe physical boundary", room_id));
4457 PendingPotItemWrite pending;
4458 pending.room_id = room_id;
4459 pending.address = stream_info.address;
4461 pending.bytes.push_back(pi.position & 0xFF);
4462 pending.bytes.push_back((pi.position >> 8) & 0xFF);
4463 pending.bytes.push_back(pi.item);
4465 pending.bytes.push_back(0xFF);
4466 pending.bytes.push_back(0xFF);
4467 if (
static_cast<int>(pending.bytes.size()) > stream_info.capacity()) {
4468 return absl::ResourceExhaustedError(absl::StrFormat(
4469 "Room %d pot item data too large! Size: %d, Available: %d", room_id,
4470 static_cast<int>(pending.bytes.size()), stream_info.capacity()));
4472 pending_writes.push_back(std::move(pending));
4475 for (
const auto& pending : pending_writes) {
4476 const bool data_changed =
4477 !std::equal(pending.bytes.begin(), pending.bytes.end(),
4478 rom_data.begin() + pending.address);
4484 for (
const auto& pending : pending_writes) {
4485 if (
const Room* room = room_lookup(pending.room_id); room !=
nullptr) {
4489 return absl::OkStatus();
4494 [&rooms](
int room_id) { return &rooms[room_id]; });
4500 rom,
static_cast<int>(rooms.size()),
4501 [&rooms](
int room_id) { return &rooms[room_id]; }, repack_layout);
4505 Rom*
rom,
int room_count,
4506 const std::function<
const Room*(
int)>& room_lookup) {
4511 const std::function<
const Room*(
int)>& room_lookup,
4517 const auto& rom_data =
rom()->vector();
4538 const int kRegionSize = 0x80;
4541 std::vector<uint8_t> blocks_data(blocks_count, 0);
4542 for (
int r = 0; r < 4; ++r) {
4543 const int slot = kPointerSlots[r];
4544 if (slot + 2 >=
static_cast<int>(rom_data.size())) {
4545 LOG_WARN(
"Room",
"LoadBlocks: pointer slot %d out of range", r);
4548 const absl::Status operand_status =
4549 ValidateBlocksLoaderPointerOperand(rom_data, slot);
4550 if (!operand_status.ok()) {
4552 std::string(operand_status.message()).c_str());
4556 (rom_data[slot + 2] << 16) | (rom_data[slot + 1] << 8) | rom_data[slot];
4558 const int off = r * kRegionSize;
4559 const int len = std::min(kRegionSize, blocks_count - off);
4562 if (pc < 0 || pc + len >
static_cast<int>(rom_data.size())) {
4563 LOG_WARN(
"Room",
"LoadBlocks: region %d data out of range", r);
4566 std::copy_n(rom_data.begin() + pc, len, blocks_data.begin() + off);
4575 return (obj.options() & ObjectOption::Block) !=
4576 ObjectOption::Nothing;
4588 for (
int i = 0; i + 3 < blocks_count; i += 4) {
4590 blocks_data[i + 2], blocks_data[i + 3]};
4606 LOG_DEBUG(
"Room",
"Loaded block at (%d,%d) draw_layer=%d behavior_layer=%d",
4615 auto rom_data =
rom()->vector();
4631 int ptr_addr = table_addr + (
room_id_ * 2);
4632 if (ptr_addr + 1 >=
static_cast<int>(rom_data.size()))
4635 uint16_t item_ptr = (rom_data[ptr_addr + 1] << 8) | rom_data[ptr_addr];
4638 int item_addr =
SnesToPc(0x010000 | item_ptr);
4641 while (item_addr + 2 <
static_cast<int>(rom_data.size())) {
4643 uint16_t position = (rom_data[item_addr + 1] << 8) | rom_data[item_addr];
4646 if (position == 0xFFFF)
4650 uint8_t item_type = rom_data[item_addr + 2];
4654 pot_item.
item = item_type;
4664 auto rom_data =
rom()->vector();
4675 const int max_offset = rom_data[
kPitCount];
4676 const int pit_entries = max_offset / 2 + 1;
4678 const int pit_ptr = (rom_data[
kPitPointer + 2] << 16) |
4682 "LoadPits: room_id=%d, RoomsWithPitDamage entries=%d, "
4683 "table_snes=0x%06X",
4692 LOG_DEBUG(
"Room",
"Per-room pit destination: target=%d, target_layer=%d",
4707 for (
const auto& sprite :
sprites_) {
4708 if (sprite.IsOverlord()) {
4718 for (
const auto& door :
doors_) {
4720 const bool is_special = [&]() ->
bool {
4721 switch (door.type) {
4752 auto options = obj.options();
4765 if (obj.id_ == 0x11E || obj.id_ == 0x11F) {
4770 if (obj.id_ >= 0xF83 && obj.id_ <= 0xF8F) {
4777 if ((obj.id_ >= 0x130 && obj.id_ <= 0x135) || obj.id_ == 0x139 ||
4778 obj.id_ == 0x13A || obj.id_ == 0x13B) {
4782 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.