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]);
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]);
108 size_t requested_index) {
112 const size_t resolved_index =
113 requested_index < group.
size() ? requested_index : 0;
118 size_t max_colors,
size_t destination_offset,
119 std::array<SDL_Color, 256>* colors) {
120 if (source ==
nullptr || colors ==
nullptr ||
121 destination_offset >= colors->
size() || source_offset >= source->
size()) {
125 const size_t count = std::min({max_colors, source->
size() - source_offset,
126 colors->size() - destination_offset});
127 for (
size_t i = 0; i < count; ++i) {
128 const auto rgb = (*source)[source_offset + i].rom_color();
129 (*colors)[destination_offset + i] = {
static_cast<Uint8
>(rgb.red),
130 static_cast<Uint8
>(rgb.green),
131 static_cast<Uint8
>(rgb.blue), 255};
140 std::vector<SDL_Color> colors(256, {0, 0, 0, 0});
141 PopulateDungeonRenderPaletteRows(
142 dungeon_palette, hud_palette,
144 if (dst_index < 0 || dst_index >=
static_cast<int>(colors.size())) {
147 const ImVec4 rgb = color.
rgb();
148 colors[dst_index] = {
static_cast<Uint8
>(rgb.x),
149 static_cast<Uint8
>(rgb.y),
150 static_cast<Uint8
>(rgb.z), 255};
152 colors[255] = {0, 0, 0, 0};
159 constexpr int kRenderPaletteRows = 8;
160 constexpr int kColorsPerRow = 16;
163 for (
int row = 0; row < kRenderPaletteRows; ++row) {
165 for (
int color = 0; color < kColorsPerRow; ++color) {
171 PopulateDungeonRenderPaletteRows(
172 dungeon_palette, hud_palette,
174 if (dst_index < 0 || dst_index >= kRenderPaletteRows * kColorsPerRow) {
177 group.
SetColor(dst_index / kColorsPerRow, dst_index % kColorsPerRow,
194 constexpr size_t kCgramRowSize = 16;
195 constexpr size_t kHalfPaletteColorCount = 7;
196 constexpr size_t kFullPaletteColorCount = 15;
197 constexpr size_t kDefaultOverworldEnvironmentPalette = 0x07;
198 constexpr size_t kDefaultUnderworldEnvironmentPalette = 0x0A;
200 std::array<SDL_Color, 256> colors{};
201 if (game_data ==
nullptr) {
208 std::array<uint8_t, 4> palette_set{};
214 CopyPaletteToCgram(ResolvePaletteOrFirst(groups.sprites_aux1, palette_set[1]),
215 0, kHalfPaletteColorCount, 8 * kCgramRowSize + 1, &colors);
216 CopyPaletteToCgram(ResolvePaletteOrFirst(groups.sprites_aux2,
217 kDefaultOverworldEnvironmentPalette),
218 0, kHalfPaletteColorCount, 8 * kCgramRowSize + 9, &colors);
225 const auto* global = ResolvePaletteOrFirst(groups.global_sprites, 0);
226 for (
size_t row = 9; row <= 12; ++row) {
227 CopyPaletteToCgram(global, (row - 9) * kFullPaletteColorCount,
228 kFullPaletteColorCount, row * kCgramRowSize + 1,
232 CopyPaletteToCgram(ResolvePaletteOrFirst(groups.sprites_aux3, palette_set[2]),
233 0, kHalfPaletteColorCount, 13 * kCgramRowSize + 1,
235 CopyPaletteToCgram(ResolvePaletteOrFirst(groups.sprites_aux3, palette_set[3]),
236 0, kHalfPaletteColorCount, 14 * kCgramRowSize + 1,
239 ResolvePaletteOrFirst(groups.sprites_aux2,
240 kDefaultUnderworldEnvironmentPalette),
241 0, kHalfPaletteColorCount, 14 * kCgramRowSize + 9, &colors);
242 CopyPaletteToCgram(ResolvePaletteOrFirst(groups.armors, 0), 0,
243 kFullPaletteColorCount, 15 * kCgramRowSize + 1, &colors);
251 PopulateDungeonRenderPaletteRows(
252 dungeon_palette, hud_palette,
254 if (dst_index < 0 || dst_index >=
static_cast<int>(cgram.size())) {
257 cgram[dst_index] = color.
snes();
268 "Light Torch to See Floor",
273 "NW Kill Enemy to Open",
274 "NE Kill Enemy to Open",
275 "SW Kill Enemy to Open",
276 "SE Kill Enemy to Open",
277 "W Kill Enemy to Open",
278 "E Kill Enemy to Open",
279 "N Kill Enemy to Open",
280 "S Kill Enemy to Open",
281 "Clear Quadrant to Open",
282 "Clear Full Tile to Open",
283 "NW Push Block to Open",
284 "NE Push Block to Open",
285 "SW Push Block to Open",
286 "SE Push Block to Open",
287 "W Push Block to Open",
288 "E Push Block to Open",
289 "N Push Block to Open",
290 "S Push Block to Open",
291 "Push Block to Open",
292 "Pull Lever to Open",
293 "Collect Prize to Open",
294 "Hold Switch Open Door",
295 "Toggle Switch to Open Door",
304 "Push Switch Exploding Wall",
306 "Open Chest (Holes 0)",
309 "Defeat Boss for Dungeon Prize",
310 "SE Kill Enemy to Push Block",
311 "Trigger Switch Chest",
312 "Pull Lever Exploding Wall",
313 "NW Kill Enemy for Chest",
314 "NE Kill Enemy for Chest",
315 "SW Kill Enemy for Chest",
316 "SE Kill Enemy for Chest",
317 "W Kill Enemy for Chest",
318 "E Kill Enemy for Chest",
319 "N Kill Enemy for Chest",
320 "S Kill Enemy for Chest",
321 "Clear Quadrant for Chest",
322 "Clear Full Tile for Chest",
323 "Light Torches to Open",
331 "Open Chest for Holes 8",
332 "Push Block for Chest",
333 "Clear Room for Triforce Door",
334 "Light Torches for Chest",
341 int physical_end = -1;
345 return physical_end > address ? physical_end - address : 0;
351 int known_region_end = -1) {
353 if (room_id < 0 || room_id >=
static_cast<int>(room_addresses.size())) {
357 info.
address = room_addresses[room_id];
362 int next_address = std::numeric_limits<int>::max();
363 for (
int other_room_id = 0;
364 other_room_id < static_cast<int>(room_addresses.size());
366 if (other_room_id == room_id || room_addresses[other_room_id] < 0) {
369 const int other_address = room_addresses[other_room_id];
370 if (other_address == info.
address) {
372 }
else if (other_address > info.
address) {
373 next_address = std::min(next_address, other_address);
377 if (known_region_end > info.
address) {
378 next_address = std::min(next_address, known_region_end);
380 if (next_address == std::numeric_limits<int>::max()) {
388 constexpr int kLoRomBankSize = 0x8000;
389 const int bank_end = ((info.
address / kLoRomBankSize) + 1) * kLoRomBankSize;
390 if (next_address > bank_end) {
399 if (table_pc ==
nullptr) {
400 return absl::InvalidArgumentError(
"table_pc pointer is null");
403 return absl::OutOfRangeError(
404 "Object pointer table address is out of range");
407 const uint32_t table_snes =
411 const int pc =
static_cast<int>(
SnesToPc(table_snes));
412 if (pc < 0 || pc + (
kNumberOfRooms * 3) >
static_cast<int>(rom_data.size())) {
413 return absl::OutOfRangeError(
"Object pointer table is out of range");
417 return absl::OkStatus();
421 int table_pc,
int room_id) {
425 const int ptr_off = table_pc + (room_id * 3);
426 if (ptr_off < 0 || ptr_off + 2 >=
static_cast<int>(rom_data.size())) {
429 return (
static_cast<uint32_t
>(rom_data[ptr_off + 2]) << 16) |
430 (
static_cast<uint32_t
>(rom_data[ptr_off + 1]) << 8) |
437 if ((snes & 0xFFFF) < 0x8000) {
440 const int pc =
static_cast<int>(
SnesToPc(snes));
441 return pc >= 0 && pc < static_cast<int>(rom_data.size()) ? pc : -1;
445 const std::vector<uint8_t>& rom_data,
int room_id) {
447 return absl::OutOfRangeError(
"Room ID out of range");
459 return absl::OutOfRangeError(
"Object stream pointer is out of range");
466 if (table_pc ==
nullptr) {
467 return absl::InvalidArgumentError(
"table_pc pointer is null");
470 return absl::OutOfRangeError(
471 "Sprite pointer table address is out of range");
477 if (pc < 0 || pc + (
kNumberOfRooms * 2) >
static_cast<int>(rom_data.size())) {
478 return absl::OutOfRangeError(
"Sprite pointer table is out of range");
482 return absl::OkStatus();
490 const int ptr_off = table_pc + (room_id * 2);
491 if (ptr_off < 0 || ptr_off + 1 >=
static_cast<int>(rom_data.size())) {
495 const uint16_t pointer =
496 (
static_cast<uint16_t
>(rom_data[ptr_off + 1]) << 8) | rom_data[ptr_off];
497 if (pointer < 0x8000) {
500 const int sprite_address =
static_cast<int>(
SnesToPc((0x09 << 16) | pointer));
501 return sprite_address >= 0 &&
502 sprite_address < static_cast<int>(rom_data.size())
508 const std::vector<uint8_t>& rom_data,
int room_id) {
510 return absl::OutOfRangeError(
"Room ID out of range");
523 return absl::OutOfRangeError(
"Sprite stream pointer is out of range");
529 int sprite_address,
int hard_end) {
530 if (sprite_address < 0 || sprite_address >= hard_end ||
531 sprite_address >=
static_cast<int>(rom_data.size())) {
535 int cursor = sprite_address + 1;
536 while (cursor < hard_end) {
537 if (rom_data[cursor] == 0xFF) {
541 if (cursor + 2 >= hard_end) {
548 return std::max(0, cursor - sprite_address);
554 std::vector<uint8_t> encoded_stream) {
555 if (layout.
kind != expected_kind) {
556 return absl::InvalidArgumentError(absl::StrFormat(
557 "Room %d relocation layout has the wrong dungeon stream kind",
566 std::move(encoded_stream)}}));
573 if (layout.
kind != expected_kind) {
574 return absl::InvalidArgumentError(absl::StrFormat(
575 "Room %d save layout has the wrong dungeon stream kind", room_id));
577 if (room_id < 0 ||
static_cast<uint32_t
>(room_id) >= layout.
pointer_count) {
578 return absl::OutOfRangeError(
579 "Room ID is outside the dungeon stream layout");
584 if (!inventory.
ok()) {
585 return absl::FailedPreconditionError(absl::StrFormat(
586 "Dungeon stream inventory has %zu issue(s); refusing an in-place "
588 inventory.
issues.size()));
591 const auto contains_room = [room_id](
const std::vector<uint32_t>& owners) {
592 return std::find(owners.begin(), owners.end(),
593 static_cast<uint32_t
>(room_id)) != owners.end();
595 for (
const auto& alias : inventory.
aliases) {
596 if (contains_room(alias.room_ids)) {
600 for (
const auto& overlap : inventory.
overlaps) {
601 if (contains_room(overlap.first_room_ids) ||
602 contains_room(overlap.second_room_ids)) {
607 const auto& record = inventory.
streams[room_id];
608 const uint64_t replacement_end =
609 static_cast<uint64_t
>(record.data_pc) + replacement_size;
610 const uint32_t bank_end = ((record.data_pc / 0x8000u) + 1u) * 0x8000u;
611 const bool stays_in_declared_data =
612 replacement_end <= bank_end &&
615 return range.begin <= record.data_pc &&
616 replacement_end <= range.end;
618 return !stays_in_declared_data;
625 if (!rom || !rom->
is_loaded() || rom->
size() == 0 || room_id < 0 ||
630 const auto& rom_data = rom->
vector();
632 if (!GetObjectPointerTablePc(rom_data, &table_pc).ok()) {
635 room_size.room_size_pointer =
636 ReadRoomObjectAddressSnes(rom_data, table_pc, room_id);
638 auto stream_info = GetObjectStreamInfo(rom_data, room_id);
639 if (!stream_info.ok() || stream_info->shared) {
642 room_size.room_size = stream_info->capacity();
674 Room room(room_id, rom);
690 header_pointer =
SnesToPc(header_pointer);
699 int table_offset = (header_pointer) + (room_id * 2);
700 if (table_offset < 0 || table_offset + 1 >=
static_cast<int>(rom->
size())) {
705 (rom->
data()[table_offset + 1] << 8) +
706 rom->
data()[table_offset];
708 auto header_location =
SnesToPc(address);
711 if (header_location < 0 ||
712 header_location + 13 >=
static_cast<int>(rom->
size())) {
716 const uint8_t header_byte0 = rom->
data()[header_location];
719 room.
SetBg2(Background2FromHeaderByte(header_byte0));
721 room.
SetIsLight(IsDarkRoomHeaderByte(header_byte0));
722 room.
SetIsDark(IsDarkRoomHeaderByte(header_byte0));
757 header_pointer_2 =
SnesToPc(header_pointer_2);
766 int table_offset_2 = (header_pointer_2) + (room_id * 2);
767 if (table_offset_2 < 0 ||
768 table_offset_2 + 1 >=
static_cast<int>(rom->
size())) {
773 (rom->
data()[table_offset_2 + 1] << 8) +
774 rom->
data()[table_offset_2];
777 if (msg_addr >= 0 && msg_addr + 1 <
static_cast<int>(rom->
size())) {
778 uint16_t msg_val = (rom->
data()[msg_addr + 1] << 8) | rom->
data()[msg_addr];
786 if (hpos < 0 || hpos + 14 >=
static_cast<int>(rom->
size())) {
790 uint8_t b = rom->
data()[hpos];
816 b = rom->
data()[hpos];
846 game_data_(game_data),
858 const int num_palettes =
static_cast<int>(group.size());
859 if (num_palettes == 0)
863 if (palette_ < game_data_->paletteset_ids.size() &&
871 if (id < 0 || id >= num_palettes)
884 const uint8_t requested_main_blockset =
886 uint8_t main_blockset = 0;
887 if (requested_main_blockset != 0xFF &&
888 requested_main_blockset < game_data_->main_blockset_ids.size()) {
889 main_blockset = requested_main_blockset;
890 }
else if (blockset_ < game_data_->main_blockset_ids.size()) {
894 "Room %d: invalid main fallback blockset %d; using main group 0",
897 if (requested_main_blockset != 0xFF &&
900 "Room %d: entrance main blockset %d out of range; using %d",
901 room_id_, requested_main_blockset, main_blockset);
906 "Room %d: room_blockset=%d, main_blockset=%d, spriteset=%d, "
910 for (
int i = 0; i < 8; i++) {
912 if (i >= 3 && i <= 6 &&
blockset_ < room_gfx.size()) {
913 const uint8_t room_sheet = room_gfx[
blockset_][i - 3];
914 if (room_sheet != 0) {
920 LOG_WARN(
"Room",
"Room %d: room blockset %d out of range; skipped $0AA2",
928 const size_t sprite_gfx_index =
static_cast<size_t>(
spriteset_) + 64;
929 if (sprite_gfx_index < sprite_gfx.size()) {
930 for (
int i = 0; i < 4; i++) {
932 static_cast<uint8_t
>(sprite_gfx[sprite_gfx_index][i] + 115);
936 "Room %d: spriteset %d out of range; clearing sprite sheets",
938 for (
int i = 0; i < 4; i++) {
970 if (entrance_blockset.has_value()) {
981 if (entrance_blockset.has_value()) {
990 !bg2_bmp.is_active() || bg2_bmp.width() == 0) {
997 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: ROM not loaded");
1002 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: GameData not set");
1006 if (gfx_buffer_data->empty()) {
1007 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: Graphics buffer is empty");
1011 LOG_DEBUG(
"Room",
"Room %d: Copying 8BPP graphics (buffer size: %zu)",
1012 room_id_, gfx_buffer_data->size());
1015 const absl::Cleanup publish_revision = [
this] {
1032 const uint8_t active_main_blockset =
1037 auto is_right_palette_background_slot = [&](
int block) ->
bool {
1038 if (block < 0 || block >= 8) {
1041 const int runtime_slot = 7 - block;
1042 if (active_main_blockset < 0x20) {
1043 return runtime_slot >= 4;
1045 return (runtime_slot == 2 || runtime_slot == 3 || runtime_slot == 4 ||
1050 for (
int block = 0; block < 16; block++) {
1051 int sheet_id =
blocks_[block];
1054 if (sheet_id >= 223) {
1055 LOG_WARN(
"Room",
"Invalid sheet index %d for block %d", sheet_id, block);
1061 int src_sheet_offset = sheet_id * 4096;
1064 if (src_sheet_offset + 4096 > gfx_buffer_data->size()) {
1065 LOG_ERROR(
"Room",
"Graphics offset out of bounds: %d (size: %zu)",
1066 src_sheet_offset, gfx_buffer_data->size());
1071 int dest_index_base = block * 4096;
1073 const uint8_t* src = gfx_buffer_data->data() + src_sheet_offset;
1078 const bool right_pal = is_right_palette_background_slot(block);
1080 memcpy(dst, src, 4096);
1083 for (
int i = 0; i < 4096; ++i) {
1085 if (p != 0 && p < 8) {
1124 bool properties_changed =
false;
1138 properties_changed =
true;
1148 properties_changed =
true;
1156 if (bg1_bmp.is_active() && bg1_bmp.width() > 0 && bg2_bmp.is_active() &&
1157 bg2_bmp.width() > 0) {
1159 "Room %d: No changes detected, skipping render",
room_id_);
1165 "Room %d: Rendering graphics (dirty_flags: g=%d o=%d l=%d t=%d)",
1184 "Room %d: floor1=%d, floor2=%d, blocks_size=%zu",
room_id_,
1192 bool need_floor_draw =
1198 if (!bg1_bmp.is_active() || bg1_bmp.width() == 0 || !bg2_bmp.is_active() ||
1199 bg2_bmp.width() == 0) {
1200 need_floor_draw =
true;
1202 "Room %d: Bitmaps not created yet, forcing floor draw",
room_id_);
1205 if (need_floor_draw) {
1207 buffer->EnsureBitmapInitialized();
1208 buffer->bitmap().Fill(255);
1209 buffer->ClearBuffer();
1222 bool need_bg_draw = was_graphics_dirty || need_floor_draw;
1234 if (was_layout_dirty || need_floor_draw) {
1243 if (dungeon_pal_group.empty())
1247 auto bg1_palette = dungeon_pal_group[palette_id];
1256 LOG_DEBUG(
"Room",
"RenderRoomGraphics: Palette ID=%d, Size=%zu", palette_id,
1257 bg1_palette.size());
1258 if (!bg1_palette.empty()) {
1259 LOG_DEBUG(
"Room",
"RenderRoomGraphics: First color: R=%d G=%d B=%d",
1260 bg1_palette[0].rom_color().red, bg1_palette[0].rom_color().green,
1261 bg1_palette[0].rom_color().blue);
1264 if (bg1_palette.size() > 0) {
1265 std::optional<gfx::SnesPalette> hud_palette_storage;
1269 hud_palette = &*hud_palette_storage;
1288 const auto render_palette =
1297 auto set_dungeon_palette = [&](
gfx::Bitmap& bmp) {
1298 bmp.SetPalette(render_palette);
1299 if (bmp.surface()) {
1301 SDL_SetColorKey(bmp.surface(), SDL_TRUE, 255);
1302 SDL_SetSurfaceBlendMode(bmp.surface(), SDL_BLENDMODE_BLEND);
1306 set_dungeon_palette(bg1_bmp);
1307 set_dungeon_palette(bg2_bmp);
1312 auto* surface = bg1_bmp.surface();
1317 "Room::RenderRoomGraphics (BG1)", palette_id,
true);
1321 "Room::RenderRoomGraphics (after SetPalette)", surface);
1324 "Room::RenderRoomGraphics", palette_id,
false,
1325 "SDL surface has no palette!");
1339 if (bg2_bmp.surface()) {
1340 SDL_SetSurfaceAlphaMod(bg2_bmp.surface(), 128);
1348 if (bg2_bmp.surface()) {
1349 SDL_SetSurfaceBlendMode(bg2_bmp.surface(), SDL_BLENDMODE_ADD);
1365 if (bitmap->texture()) {
1371 release_texture(&bg1_bmp);
1372 release_texture(&bg2_bmp);
1387 "Texture commands queued for batch processing");
1395 LOG_DEBUG(
"Room",
"ROM not loaded, aborting");
1406 if (!layout_status.ok()) {
1408 layout_status.message().data());
1414 layout_objects.size());
1415 if (layout_objects.empty()) {
1423 LOG_DEBUG(
"RenderRoomGraphics",
"GameData not set, cannot render layout");
1429 if (dungeon_pal_group.empty())
1433 auto room_palette = dungeon_pal_group[palette_id];
1448 "RenderRoomGraphics",
"Layout Draw failed: %s",
1449 std::string(status.message().data(), status.message().size()).c_str());
1451 LOG_DEBUG(
"RenderRoomGraphics",
"Layout rendered with %zu objects",
1452 layout_objects.size());
1457 LOG_DEBUG(
"[RenderObjectsToBackground]",
1458 "Starting object rendering for room %d",
room_id_);
1461 LOG_DEBUG(
"[RenderObjectsToBackground]",
"ROM not loaded, aborting");
1470 bool bitmaps_exist = bg1_bmp.is_active() && bg1_bmp.width() > 0 &&
1471 bg2_bmp.is_active() && bg2_bmp.width() > 0;
1474 LOG_DEBUG(
"[RenderObjectsToBackground]",
1475 "Room %d: Objects not dirty, skipping render",
room_id_);
1481 LOG_DEBUG(
"[RenderObjectsToBackground]",
1482 "Room %d: Emulator rendering objects",
room_id_);
1487 if (dungeon_pal_group.empty())
1491 auto room_palette = dungeon_pal_group[palette_id];
1534 int layer0_count = 0, layer1_count = 0, layer2_count = 0;
1536 switch (obj.GetLayerValue()) {
1550 "Room %03X Object Stream Summary: Main=%d, BG2Overlay=%d, BG1Overlay=%d",
1551 room_id_, layer0_count, layer1_count, layer2_count);
1569 std::vector<std::vector<RoomObject>> by_list(3);
1581 uint8_t list_index = obj.GetLayerValue();
1582 if (list_index > 2) {
1587 by_list[list_index].push_back(std::move(render_obj));
1590 absl::Status status = absl::OkStatus();
1591 bool reset_room_events_for_next_chunk =
true;
1592 for (
int pass = 0; pass < 3; ++pass) {
1593 if (by_list[pass].empty()) {
1600 reset_room_events_for_next_chunk =
false;
1601 if (!chunk_status.ok() && status.ok()) {
1602 status = chunk_status;
1609 for (
int i = 0; i < static_cast<int>(
doors_.size()); ++i) {
1610 const auto& door =
doors_[i];
1612 door_def.
type = door.type;
1630 if (pot_item.item != 0) {
1632 int tile_x = pot_item.GetTileX();
1633 int tile_y = pot_item.GetTileY();
1640 for (
const auto& sprite :
sprites_) {
1641 if (sprite.key_drop() > 0) {
1647 uint8_t key_item = (sprite.key_drop() == 1) ? 0xFD : 0xFE;
1657 constexpr uint16_t kRoomDrawObj_PushableBlock = 0x0E52;
1658 constexpr uint16_t kRoomDrawObj_TorchUnlit = 0x0EC2;
1659 constexpr uint16_t kRoomDrawObj_TorchLit = 0x0ECA;
1666 static_cast<uint16_t
>(obj.id_), obj.x_, obj.y_, obj.layer_,
1671 const uint16_t off =
1672 obj.lit_ ? kRoomDrawObj_TorchLit : kRoomDrawObj_TorchUnlit;
1677 static_cast<uint16_t
>(obj.id_), obj.x_, obj.y_, obj.layer_, off,
1685 "[RenderObjectsToBackground]",
1686 "Room %03X: ObjectDrawer failed: %s (objects left dirty for retry)",
1688 std::string(status.message().data(), status.message().size()).c_str());
1695 LOG_DEBUG(
"[RenderObjectsToBackground]",
1696 "Room %d: Objects rendered successfully",
room_id_);
1707 constexpr size_t kSheetBytes = 4096;
1708 constexpr size_t kFrameBytes = 1024;
1711 if (animated_frame_ < 0 || animated_frame_ >= 3) {
1715 bool copied_frame =
false;
1716 const absl::Cleanup publish_revision = [
this, &copied_frame] {
1721 const auto copy_frame = [&](uint8_t sheet,
size_t destination) {
1722 const size_t source = sheet * kSheetBytes +
animated_frame_ * kFrameBytes;
1723 if (source + kFrameBytes <= graphics.size()) {
1724 std::copy_n(graphics.data() + source, kFrameBytes,
1726 copied_frame =
true;
1733 copy_frame(0x5C, 0x1C0 * 64);
1738 const auto table_snes =
1740 if (!table_snes.ok() || (*table_snes & 0xFFFF) < 0x8000 ||
1741 (*table_snes >> 16) == 0x7E || (*table_snes >> 16) == 0x7F) {
1744 const uint8_t main_group =
1751 copy_frame(*sheet, 0x1B0 * 64);
1757 auto rom_data =
rom()->vector();
1763 object_pointer =
SnesToPc(object_pointer);
1766 if (object_pointer < 0 || object_pointer >= (
int)
rom_->
size()) {
1770 int room_address = object_pointer + (
room_id_ * 3);
1773 if (room_address < 0 || room_address + 2 >= (
int)
rom_->
size()) {
1777 int tile_address = (rom_data[room_address + 2] << 16) +
1778 (rom_data[room_address + 1] << 8) + rom_data[room_address];
1780 int objects_location =
SnesToPc(tile_address);
1783 if (objects_location < 0 || objects_location >= (
int)
rom_->
size()) {
1788 if (objects_location + 1 < (
int)
rom_->
size()) {
1791 static_cast<uint8_t
>(rom_data[objects_location] & 0x0F);
1793 static_cast<uint8_t
>((rom_data[objects_location] >> 4) & 0x0F);
1795 "Room %d: Set floor1_graphics_=%d, floor2_graphics_=%d",
1800 static_cast<uint8_t
>((rom_data[objects_location + 1] >> 2) & 0x07);
1822 auto rom_data =
rom()->vector();
1828 int nbr_of_staircase = 0;
1830 int pos = objects_location;
1836 bool end_read =
false;
1840 while (!end_read && pos < (
int)
rom_->
size()) {
1842 if (pos + 1 >= (
int)
rom_->
size()) {
1847 b2 = rom_data[pos + 1];
1854 if (b1 == 0xFF && b2 == 0xFF) {
1858 "Room",
"Room %03X: Object list transition to index %d (%s)",
1860 layer == 1 ?
"BG2 overlay" : (layer == 2 ?
"BG1 overlay" :
"END"));
1870 if (b1 == 0xF0 && b2 == 0xFF) {
1877 if (pos + 2 >= (
int)
rom_->
size()) {
1881 b3 = rom_data[pos + 2];
1892 b1, b2, b3,
static_cast<uint8_t
>(layer));
1894 LOG_DEBUG(
"Room",
"Room %03X: Object 0x%03X at (%d,%d) stream=%d (%s)",
1896 layer == 0 ?
"Primary"
1897 : (layer == 1 ?
"BG2 overlay" :
"BG1 overlay"));
1901 if (r.
id_ >= 0 && r.
id_ <= 0xFFF) {
1915 "ParseDoor: room=%d b1=0x%02X b2=0x%02X pos=%d dir=%d type=%d",
1917 static_cast<int>(door.direction),
static_cast<int>(door.type));
1928 std::vector<uint8_t> bytes;
1932 std::vector<RoomObject> layer0_objects;
1933 std::vector<RoomObject> layer1_objects;
1934 std::vector<RoomObject> layer2_objects;
1947 switch (obj.GetLayerValue()) {
1949 layer0_objects.push_back(obj);
1952 layer1_objects.push_back(obj);
1955 layer2_objects.push_back(obj);
1971 for (
const auto& obj : layer0_objects) {
1972 auto encoded = obj.EncodeObjectToBytes();
1973 bytes.push_back(encoded.b1);
1974 bytes.push_back(encoded.b2);
1975 bytes.push_back(encoded.b3);
1977 bytes.push_back(0xFF);
1978 bytes.push_back(0xFF);
1981 for (
const auto& obj : layer1_objects) {
1982 auto encoded = obj.EncodeObjectToBytes();
1983 bytes.push_back(encoded.b1);
1984 bytes.push_back(encoded.b2);
1985 bytes.push_back(encoded.b3);
1987 bytes.push_back(0xFF);
1988 bytes.push_back(0xFF);
1991 for (
const auto& obj : layer2_objects) {
1992 auto encoded = obj.EncodeObjectToBytes();
1993 bytes.push_back(encoded.b1);
1994 bytes.push_back(encoded.b2);
1995 bytes.push_back(encoded.b3);
2000 bytes.push_back(0xF0);
2001 bytes.push_back(0xFF);
2002 for (
const auto& door :
doors_) {
2003 auto [b1, b2] = door.EncodeBytes();
2004 bytes.push_back(b1);
2005 bytes.push_back(b2);
2009 bytes.push_back(0xFF);
2010 bytes.push_back(0xFF);
2016 std::vector<uint8_t> bytes;
2018 for (
const auto& sprite :
sprites_) {
2026 b2 = (sprite.x() & 0x1F) | ((sprite.subtype() & 0x07) << 5);
2031 b1 = (sprite.y() & 0x1F) | ((sprite.subtype() & 0x18) << 2) |
2032 ((sprite.layer() & 0x01) << 7);
2034 bytes.push_back(b1);
2035 bytes.push_back(b2);
2036 bytes.push_back(b3);
2040 if (sprite.key_drop() == 1) {
2041 bytes.insert(bytes.end(), {0xFE, 0x00, 0xE4});
2042 }
else if (sprite.key_drop() == 2) {
2043 bytes.insert(bytes.end(), {0xFD, 0x00, 0xE4});
2048 bytes.push_back(0xFF);
2058 const auto& rom_data = rom->
vector();
2059 int sprite_pointer = 0;
2060 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
2064 const int hard_end =
2066 if (hard_end <= 0) {
2071 std::unordered_set<int> visited_addresses;
2073 int sprite_address =
2074 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
2075 if (sprite_address < kSpritesData || sprite_address >= hard_end) {
2078 if (!visited_addresses.insert(sprite_address).second) {
2083 MeasureSpriteStreamSize(rom_data, sprite_address, hard_end);
2084 int stream_end = sprite_address + stream_size;
2085 if (stream_end > max_used) {
2086 max_used = stream_end;
2094 const std::vector<uint8_t>& encoded_bytes) {
2096 return absl::InvalidArgumentError(
"ROM not loaded");
2099 return absl::OutOfRangeError(
"Room ID out of range");
2101 if (encoded_bytes.empty() || encoded_bytes.back() != 0xFF ||
2102 (encoded_bytes.size() % 3) != 1) {
2103 return absl::InvalidArgumentError(
2104 "Encoded sprite payload must be N*3 bytes plus 0xFF terminator");
2107 const auto& rom_data = rom->
vector();
2108 int sprite_pointer = 0;
2111 int old_sprite_address =
2112 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
2113 if (old_sprite_address < 0 ||
2114 old_sprite_address >=
static_cast<int>(rom_data.size())) {
2115 return absl::OutOfRangeError(
"Sprite address out of range");
2118 const uint8_t sort_mode = rom_data[old_sprite_address];
2121 const size_t required_size = 1u + encoded_bytes.size();
2123 static_cast<size_t>(write_pos) + required_size >
2125 return absl::ResourceExhaustedError(absl::StrFormat(
2126 "Not enough sprite data space. Need %d bytes at 0x%06X, "
2127 "region ends at 0x%06X",
2130 if (
static_cast<size_t>(write_pos) + required_size > rom_data.size()) {
2131 const int required_end = write_pos +
static_cast<int>(required_size);
2132 return absl::OutOfRangeError(
2133 absl::StrFormat(
"ROM too small for sprite relocation write (need "
2134 "end=0x%06X, size=0x%06X)",
2135 required_end,
static_cast<int>(rom_data.size())));
2138 std::vector<uint8_t> relocated;
2139 relocated.reserve(required_size);
2140 relocated.push_back(sort_mode);
2141 relocated.insert(relocated.end(), encoded_bytes.begin(), encoded_bytes.end());
2144 const uint32_t snes_addr =
PcToSnes(write_pos);
2145 const int ptr_off = sprite_pointer + (room_id * 2);
2149 return absl::OkStatus();
2153 if (
rom_ ==
nullptr) {
2154 return absl::InvalidArgumentError(
"ROM pointer is null");
2157 return absl::OkStatus();
2166 const auto& rom_data =
rom()->vector();
2168 GetObjectStreamInfo(rom_data,
room_id_));
2170 bool requires_copy_on_write =
false;
2171 if (layout !=
nullptr) {
2173 DungeonStreamRequiresCopyOnWrite(
2175 encoded_bytes.size() + 2u));
2177 const auto relocate = [&]() -> absl::Status {
2178 if (stream_info.address + 2 >
static_cast<int>(rom_data.size())) {
2179 return absl::OutOfRangeError(
"Object stream header is out of range");
2181 std::vector<uint8_t> replacement = {rom_data[stream_info.address],
2182 rom_data[stream_info.address + 1]};
2183 replacement.insert(replacement.end(), encoded_bytes.begin(),
2184 encoded_bytes.end());
2187 std::move(replacement)));
2189 return absl::OkStatus();
2191 if (stream_info.shared || requires_copy_on_write) {
2192 if (layout !=
nullptr) {
2195 return absl::FailedPreconditionError(absl::StrFormat(
2196 "Room %d object stream at PC 0x%06X is shared; repacking is required",
2199 if (stream_info.capacity() <= 2) {
2200 if (layout !=
nullptr) {
2203 return absl::FailedPreconditionError(absl::StrFormat(
2204 "Room %d object stream has no safe physical boundary",
room_id_));
2208 const int write_pos = stream_info.address + 2;
2211 const int available_payload_size = stream_info.capacity() - 2;
2215 if (encoded_bytes.size() >
static_cast<size_t>(available_payload_size)) {
2216 if (layout !=
nullptr) {
2219 return absl::ResourceExhaustedError(absl::StrFormat(
2220 "Room %d object data too large! Size: %d, Available: %d",
room_id_,
2221 static_cast<int>(encoded_bytes.size()), available_payload_size));
2224 const int door_list_offset =
static_cast<int>(encoded_bytes.size()) -
2225 static_cast<int>(
doors_.size()) * 2 - 2;
2226 if (door_list_offset < 0) {
2227 return absl::FailedPreconditionError(
"Invalid encoded door list offset");
2230 if (door_pointer_slot < 0 ||
2231 door_pointer_slot + 2 >=
static_cast<int>(rom_data.size())) {
2232 return absl::OutOfRangeError(
"Door pointer slot is out of range");
2234 const int door_pointer_pc = write_pos + door_list_offset;
2245 uint32_t encoded_door_pointer =
PcToSnes(door_pointer_pc);
2246 encoded_door_pointer |= source_door_pointer & 0x800000u;
2251 return absl::OkStatus();
2255 if (
rom_ ==
nullptr) {
2256 return absl::InvalidArgumentError(
"ROM pointer is null");
2259 return absl::OkStatus();
2262 return absl::InvalidArgumentError(
2263 "Dungeon floor graphics values must be in range 0..15");
2266 return absl::InvalidArgumentError(
2267 "Dungeon layout ID must be in range 0..7");
2272 GetObjectStreamInfo(rom_data,
room_id_));
2273 if (stream_info.address < 0 ||
2274 stream_info.address + 1 >=
static_cast<int>(rom_data.size())) {
2275 return absl::OutOfRangeError(
"Object stream header is out of range");
2279 auto patch_header = [&](std::vector<uint8_t>* stream) -> absl::Status {
2280 if (stream ==
nullptr || stream->size() < 2) {
2281 return absl::DataLossError(
2282 "Object stream is missing its two-byte header");
2285 (*stream)[0] =
static_cast<uint8_t
>(((*stream)[0] & 0xF0) |
2293 (*stream)[1] =
static_cast<uint8_t
>(((*stream)[1] & 0xE3) |
2296 return absl::OkStatus();
2299 bool requires_copy_on_write = stream_info.shared;
2300 std::vector<uint8_t> replacement;
2301 if (layout !=
nullptr) {
2303 return absl::InvalidArgumentError(
2304 "Object-stream header save requires an object stream layout");
2308 if (!inventory.
ok()) {
2309 return absl::FailedPreconditionError(absl::StrFormat(
2310 "Dungeon stream inventory has %zu issue(s); refusing object "
2312 inventory.
issues.size()));
2316 return absl::OutOfRangeError(
2317 "Room ID is outside the dungeon stream layout");
2320 bool layout_requires_copy_on_write =
false;
2322 DungeonStreamRequiresCopyOnWrite(
2324 replacement.size()));
2325 requires_copy_on_write =
2326 requires_copy_on_write || layout_requires_copy_on_write;
2329 if (requires_copy_on_write) {
2330 if (layout ==
nullptr) {
2331 return absl::FailedPreconditionError(absl::StrFormat(
2332 "Room %d object stream at PC 0x%06X is shared; a copy-on-write "
2333 "manifest is required to save its header",
2339 std::move(replacement)));
2341 return absl::OkStatus();
2344 std::vector<uint8_t> header = {rom_data[stream_info.address],
2345 rom_data[stream_info.address + 1]};
2349 return absl::OkStatus();
2353 if (
rom_ ==
nullptr) {
2354 return absl::InvalidArgumentError(
"ROM pointer is null");
2357 return absl::OkStatus();
2360 const auto& rom_data =
rom()->vector();
2362 return absl::OutOfRangeError(
"Room ID out of range");
2366 GetSpriteStreamInfo(rom_data,
room_id_));
2368 bool requires_copy_on_write =
false;
2369 if (layout !=
nullptr) {
2371 DungeonStreamRequiresCopyOnWrite(
2373 encoded_bytes.size() + 1u));
2375 const auto relocate = [&]() -> absl::Status {
2376 std::vector<uint8_t> replacement = {rom_data[stream_info.address]};
2377 replacement.insert(replacement.end(), encoded_bytes.begin(),
2378 encoded_bytes.end());
2381 std::move(replacement)));
2383 return absl::OkStatus();
2385 if (stream_info.shared || requires_copy_on_write) {
2386 if (layout !=
nullptr) {
2389 return absl::FailedPreconditionError(absl::StrFormat(
2390 "Room %d sprite stream at PC 0x%06X is shared; repacking is required",
2393 if (stream_info.capacity() <= 1) {
2394 if (layout !=
nullptr) {
2397 return absl::FailedPreconditionError(absl::StrFormat(
2398 "Room %d sprite stream has no safe physical boundary",
room_id_));
2401 const int available_payload_size = stream_info.capacity() - 1;
2402 const int payload_address = stream_info.address + 1;
2403 if (payload_address < 0 ||
2404 payload_address >=
static_cast<int>(
rom_->
size())) {
2405 return absl::OutOfRangeError(absl::StrFormat(
2406 "Room %d has invalid sprite payload address",
room_id_));
2409 if (
static_cast<int>(encoded_bytes.size()) > available_payload_size) {
2410 if (layout !=
nullptr) {
2413 return absl::ResourceExhaustedError(absl::StrFormat(
2414 "Room %d sprite data too large! Size: %d, Available: %d; repacking "
2416 room_id_,
static_cast<int>(encoded_bytes.size()),
2417 available_payload_size));
2422 return absl::OkStatus();
2426 if (
rom_ ==
nullptr) {
2427 return absl::InvalidArgumentError(
"ROM pointer is null");
2430 const auto& rom_data =
rom()->vector();
2433 return absl::OutOfRangeError(
"Room header pointer out of range");
2437 return absl::OutOfRangeError(
"Room header pointer bank out of range");
2443 header_pointer =
SnesToPc(header_pointer);
2445 int table_offset = header_pointer + (
room_id_ * 2);
2446 if (table_offset < 0 ||
2447 table_offset + 1 >=
static_cast<int>(rom_data.size())) {
2448 return absl::OutOfRangeError(
"Room header table offset out of range");
2452 (rom_data[table_offset + 1] << 8) + rom_data[table_offset];
2453 int header_location =
SnesToPc(address);
2455 if (header_location < 0 ||
2456 header_location + 13 >=
static_cast<int>(rom_data.size())) {
2457 return absl::OutOfRangeError(
"Room header location out of range");
2464 if (
bg2() != background2::DarkRoom) {
2465 layer2_mode_for_save =
static_cast<uint8_t
>(
bg2()) & 0x07;
2467 const bool dark_room =
2469 uint8_t byte0 =
static_cast<uint8_t
>(
2470 (layer2_mode_for_save << 5) |
2471 ((
static_cast<uint8_t
>(
collision()) & 0x07) << 2) |
2472 (rom_data[header_location] & 0x02) | (dark_room ? 1 : 0));
2480 const uint8_t byte8 =
static_cast<uint8_t
>(
2481 (rom_data[header_location + 8] & 0xFC) | (
staircase_plane(3) & 0x03));
2502 if (msg_addr < 0 || msg_addr + 1 >=
static_cast<int>(rom_data.size())) {
2503 return absl::OutOfRangeError(
"Message ID address out of range");
2509 return absl::OkStatus();
2519 return absl::InvalidArgumentError(
"Invalid object parameters");
2527 return absl::OkStatus();
2532 return absl::OutOfRangeError(
"Object index out of range");
2540 return absl::OkStatus();
2545 return absl::OutOfRangeError(
"Object index out of range");
2549 return absl::InvalidArgumentError(
"Invalid object parameters");
2557 return absl::OkStatus();
2563 if (obj.x() == x && obj.y() == y && obj.GetLayerValue() == layer) {
2567 return absl::NotFoundError(
"No object found at position");
2572 if (
object.x() < 0 ||
object.x() > 63)
2574 if (
object.y() < 0 ||
object.y() > 63)
2578 if (
object.GetLayerValue() < 0 ||
object.GetLayerValue() > 2)
2582 if (
object.id_ < 0 || object.id_ > 0xFFF)
2586 if (
object.id_ < 0x100 &&
object.size() > 15)
2593 int& nbr_of_staircase) {
2597 if (nbr_of_staircase < 4) {
2620 }
else if (oid == 0xFB1) {
2630 const auto& rom_data =
rom()->vector();
2638 int sprite_pointer = 0;
2639 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
2643 int sprite_address =
2644 ReadRoomSpriteAddressPc(rom_data, sprite_pointer,
room_id_);
2645 if (sprite_address < 0 ||
2646 sprite_address + 1 >=
static_cast<int>(rom_data.size())) {
2651 sprite_address += 1;
2653 while (sprite_address + 2 <
static_cast<int>(rom_data.size())) {
2654 uint8_t b1 = rom_data[sprite_address];
2655 uint8_t b2 = rom_data[sprite_address + 1];
2656 uint8_t b3 = rom_data[sprite_address + 2];
2662 sprites_.emplace_back(b3, (b2 & 0x1F), (b1 & 0x1F),
2663 ((b2 & 0xE0) >> 5) + ((b1 & 0x60) >> 2),
2670 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1E &&
2676 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1D &&
2683 sprite_address += 3;
2695 const auto& rom_data =
rom()->vector();
2701 const int cpos =
static_cast<int>(
SnesToPc(
2705 const size_t byte_length =
2708 const size_t bounded_byte_length = std::min<size_t>(
2709 byte_length, cpos >= 0 && cpos <
static_cast<int>(rom_data.size())
2710 ? rom_data.size() -
static_cast<size_t>(cpos)
2712 const size_t record_count = std::min<size_t>(
2715 for (
size_t i = 0; i < record_count; ++i) {
2716 const size_t offset =
2718 if ((((rom_data[offset + 1] << 8) + rom_data[offset]) & 0x7FFF) ==
2722 if ((((rom_data[offset + 1] << 8) + rom_data[offset]) & 0x8000) ==
2734 auto rom_data =
rom()->vector();
2745 "LoadDoors for room %d - doors are loaded via object stream",
2750 auto rom_data =
rom()->vector();
2763 return (obj.options() & ObjectOption::Torch) !=
2764 ObjectOption::Nothing;
2769 for (
int i = 0; i < bytes_count; i += 2) {
2770 if (i + 1 >= bytes_count)
2777 if (b1 == 0xFF && b2 == 0xFF) {
2782 uint16_t torch_room_id = (b2 << 8) | b1;
2786 while (i < bytes_count) {
2787 if (i + 1 >= bytes_count)
2794 if (b1 == 0xFF && b2 == 0xFF) {
2802 torch_obj.SetRom(
rom_);
2804 torch_obj.set_torch_reserved_bit(entry.
reserved);
2805 torch_obj.lit_ = entry.
lit;
2810 "Room",
"Loaded torch at (%d,%d) draw_layer=%d reserved=%d lit=%d",
2819 while (i < bytes_count) {
2820 if (i + 1 >= bytes_count)
2824 if (b1 == 0xFF && b2 == 0xFF) {
2839 uint16_t room_id = 0;
2845 const std::vector<uint8_t>& rom_data,
int bytes_count) {
2846 std::vector<TorchSegment> segments;
2851 if (b1 == 0xFF && b2 == 0xFF) {
2857 padding.
bytes = {0xFF, 0xFF};
2858 segments.push_back(std::move(padding));
2862 uint16_t room_id = (b2 << 8) | b1;
2869 seg.
bytes.push_back(b1);
2870 seg.
bytes.push_back(b2);
2875 if (b1 == 0xFF && b2 == 0xFF) {
2876 seg.
bytes.push_back(0xFF);
2877 seg.
bytes.push_back(0xFF);
2881 seg.
bytes.push_back(b1);
2882 seg.
bytes.push_back(b2);
2885 segments.push_back(std::move(seg));
2891 std::vector<uint8_t> bytes;
2896 if (bytes.empty()) {
2897 bytes.push_back(room_id & 0xFF);
2898 bytes.push_back((room_id >> 8) & 0xFF);
2901 .px =
static_cast<uint8_t
>(obj.x()),
2902 .py =
static_cast<uint8_t
>(obj.y()),
2903 .draw_layer =
static_cast<uint8_t
>(obj.GetLayerValue() & 1),
2904 .reserved = obj.torch_reserved_bit(),
2907 bytes.push_back(encoded.
low);
2908 bytes.push_back(encoded.
high);
2910 if (!bytes.empty()) {
2911 bytes.push_back(0xFF);
2912 bytes.push_back(0xFF);
2919 const char* object_type) {
2920 const uint8_t selector =
object.GetLayerValue();
2921 if (selector <= 1) {
2922 return absl::OkStatus();
2924 return absl::InvalidArgumentError(absl::StrFormat(
2925 "%s in room 0x%03X has invalid special draw-layer selector %d; "
2927 "(upper/BG1) or 1 (lower/BG2)",
2928 object_type, room_id, selector));
2935 if (
object.x() <= 0x3E &&
object.y() <= 0x3E) {
2936 return absl::OkStatus();
2938 return absl::InvalidArgumentError(absl::StrFormat(
2939 "Torch in room 0x%03X has invalid position (%d,%d); expected x/y in "
2941 room_id,
object.x(),
object.y()));
2946template <
typename RoomLookup>
2948 RoomLookup&& room_lookup) {
2950 return absl::InvalidArgumentError(
"ROM not loaded");
2953 const auto& rom_data = rom->
vector();
2956 if (existing_count > kTorchesMaxSize) {
2957 existing_count = kTorchesMaxSize;
2959 auto rom_segments = ParseRomTorchSegments(rom_data, existing_count);
2961 std::vector<uint8_t> bytes;
2963 std::vector<bool> owned_rooms(room_limit,
false);
2964 std::vector<bool> seen_original_room(room_limit,
false);
2965 std::vector<bool> emitted_owned_room(room_limit,
false);
2966 std::vector<std::vector<uint8_t>> replacements(room_limit);
2967 bool any_owned_room =
false;
2968 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2969 const Room* room = room_lookup(room_id);
2970 const bool room_owned =
2980 owned_rooms[room_id] =
true;
2981 any_owned_room =
true;
2982 replacements[room_id] = EncodeTorchSegmentForRoom(room_id, *room);
2985 if (!any_owned_room) {
2986 return absl::OkStatus();
2989 for (
const auto& segment : rom_segments) {
2990 if (segment.room_id < room_limit) {
2991 seen_original_room[segment.room_id] =
true;
2992 if (owned_rooms[segment.room_id]) {
2993 if (!emitted_owned_room[segment.room_id]) {
2994 bytes.insert(bytes.end(), replacements[segment.room_id].begin(),
2995 replacements[segment.room_id].end());
2996 emitted_owned_room[segment.room_id] =
true;
3001 bytes.insert(bytes.end(), segment.bytes.begin(), segment.bytes.end());
3004 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3005 if (owned_rooms[room_id] && !seen_original_room[room_id] &&
3006 !replacements[room_id].empty()) {
3007 bytes.insert(bytes.end(), replacements[room_id].begin(),
3008 replacements[room_id].end());
3012 if (bytes.size() > kTorchesMaxSize) {
3013 return absl::ResourceExhaustedError(
3014 absl::StrFormat(
"Torch data too large: %d bytes (max %d)", bytes.size(),
3018 const uint16_t current_len =
3021 if (current_len == bytes.size() &&
3022 kTorchData +
static_cast<int>(bytes.size()) <=
3023 static_cast<int>(rom_data.size()) &&
3024 std::equal(bytes.begin(), bytes.end(), rom_data.begin() +
kTorchData)) {
3025 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3026 if (
const Room* room = room_lookup(room_id);
3028 const_cast<Room*
>(room)->ClearTorchesDirty();
3031 return absl::OkStatus();
3035 static_cast<uint16_t
>(bytes.size())));
3037 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3038 if (
const Room* room = room_lookup(room_id);
3040 const_cast<Room*
>(room)->ClearTorchesDirty();
3043 return absl::OkStatus();
3048 [&rooms](
int room_id) { return &rooms[room_id]; });
3052 Rom* rom,
int room_count,
3053 const std::function<
const Room*(
int)>& room_lookup) {
3065 if (pit_damage_table !=
nullptr && pit_damage_table->
dirty()) {
3068 return absl::OkStatus();
3071 return absl::InvalidArgumentError(
"ROM not loaded");
3073 const auto& rom_data = rom->
vector();
3074 if (kPitCount < 0 || kPitCount >=
static_cast<int>(rom_data.size()) ||
3075 kPitPointer + 2 >=
static_cast<int>(rom_data.size())) {
3076 return absl::OutOfRangeError(
"Pit count/pointer out of range");
3082 int data_len = max_offset + 2;
3083 int pit_ptr_snes = (rom_data[
kPitPointer + 2] << 16) |
3085 int pit_data_pc =
SnesToPc(pit_ptr_snes);
3086 if (pit_data_pc < 0 ||
3087 pit_data_pc + data_len >
static_cast<int>(rom_data.size())) {
3088 return absl::OutOfRangeError(
"Pit data region out of range");
3090 std::vector<uint8_t> data(rom_data.begin() + pit_data_pc,
3091 rom_data.begin() + pit_data_pc + data_len);
3107 return first_begin < second_end && second_begin < first_end;
3111 const std::vector<uint8_t>& rom_data,
int operand_pc) {
3112 if (operand_pc <= 0 || operand_pc + 5 >=
static_cast<int>(rom_data.size())) {
3113 return absl::OutOfRangeError(
"Blocks pointer operand out of range");
3127 if (rom_data[operand_pc - 1] != 0xBF || rom_data[operand_pc + 3] != 0x9D) {
3128 return absl::FailedPreconditionError(absl::StrFormat(
3129 "Blocks pointer operand at PC 0x%05X is not in the expected "
3130 "LDA.l ...,X / STA.w loader sequence",
3133 return absl::OkStatus();
3137 const std::vector<uint8_t>& rom_data, std::array<int, 4>* destination_pcs) {
3140 return absl::OutOfRangeError(
"Blocks length out of range");
3146 const int snes = (rom_data[operand_pc + 2] << 16) |
3147 (rom_data[operand_pc + 1] << 8) | rom_data[operand_pc];
3148 const int data_pc =
SnesToPc(snes);
3151 return absl::OutOfRangeError(absl::StrFormat(
3152 "Blocks data region out of range for loader page %d", page + 1));
3154 (*destination_pcs)[page] = data_pc;
3158 for (
size_t page = 0; page < destination_pcs->size(); ++page) {
3159 const int page_begin = (*destination_pcs)[page];
3162 kLengthMetadataEnd)) {
3163 return absl::FailedPreconditionError(absl::StrFormat(
3164 "Blocks data page %d at PC [0x%05X, 0x%05X) overlaps block-table "
3165 "length metadata [0x%05X, 0x%05X)",
3166 page + 1, page_begin, page_end,
kBlocksLength, kLengthMetadataEnd));
3177 return absl::FailedPreconditionError(absl::StrFormat(
3178 "Blocks data page %d at PC [0x%05X, 0x%05X) overlaps loader %d "
3179 "opcode/operand metadata [0x%05X, 0x%05X)",
3180 page + 1, page_begin, page_end, loader + 1, loader_begin,
3185 for (
size_t previous = 0; previous < page; ++previous) {
3186 const int previous_begin = (*destination_pcs)[previous];
3190 return absl::FailedPreconditionError(absl::StrFormat(
3191 "Blocks data pages %d and %d overlap at PC ranges [0x%05X, "
3192 "0x%05X) and [0x%05X, 0x%05X)",
3193 previous + 1, page + 1, previous_begin, previous_end, page_begin,
3199 return absl::OkStatus();
3206 return absl::InvalidArgumentError(
"ROM not loaded");
3208 const auto& rom_data = rom->
vector();
3209 if (
kBlocksLength + 1 >=
static_cast<int>(rom_data.size())) {
3210 return absl::OutOfRangeError(
"Blocks length out of range");
3214 std::array<int, 4> destination_pcs{};
3216 PreflightBlocksLoaderDestinations(rom_data, &destination_pcs));
3217 if (blocks_count <= 0) {
3218 return absl::OkStatus();
3220 for (
int r = 0; r < 4; ++r) {
3221 const int pc = destination_pcs[r];
3222 int off = r * kBlocksRegionSize;
3223 int len = std::min(kBlocksRegionSize, blocks_count - off);
3226 std::vector<uint8_t> chunk(rom_data.begin() + pc,
3227 rom_data.begin() + pc + len);
3232 return absl::OkStatus();
3236 const std::function<
const Room*(
int)>& room_lookup) {
3238 return absl::InvalidArgumentError(
"ROM not loaded");
3240 const auto& rom_data = rom->
vector();
3241 if (
kBlocksLength + 1 >=
static_cast<int>(rom_data.size())) {
3242 return absl::OutOfRangeError(
"Blocks length out of range");
3245 std::array<int, 4> destination_pcs{};
3247 PreflightBlocksLoaderDestinations(rom_data, &destination_pcs));
3255 const int original_count_word =
3257 const int original_byte_len = std::max(0, original_count_word);
3258 std::vector<uint8_t> original_buffer(original_byte_len, 0);
3259 for (
int r = 0; r < 4; ++r) {
3260 const int pc = destination_pcs[r];
3261 const int off = r * kBlocksRegionSize;
3262 const int len = std::min(kBlocksRegionSize, original_byte_len - off);
3265 std::copy_n(rom_data.begin() + pc, len, original_buffer.begin() + off);
3267 const int original_slot_count = original_byte_len / 4;
3278 struct EncodedBlock {
3282 std::unordered_set<uint16_t> owned_room_ids;
3283 std::unordered_set<int> claimed_load_orders;
3284 std::unordered_map<int, EncodedBlock> slot_replacements;
3285 std::vector<EncodedBlock> appended;
3286 for (
int rid = 0; rid < room_count; ++rid) {
3287 const Room* room = room_lookup(rid);
3288 if (room ==
nullptr)
3292 return absl::FailedPreconditionError(absl::StrFormat(
3293 "Room 0x%03X has unsaved pushable-block edits, but its block "
3294 "table is not loaded. Load the room's blocks before saving.",
3299 owned_room_ids.insert(
static_cast<uint16_t
>(rid));
3304 ValidateSpecialObjectDrawLayerSelector(obj, rid,
"Pushable block"));
3306 encoded_entry.
room_id =
static_cast<uint16_t
>(rid);
3307 encoded_entry.
px = obj.x();
3308 encoded_entry.
py = obj.y();
3309 encoded_entry.
draw_layer = obj.GetLayerValue();
3313 const EncodedBlock encoded_block{encoded, &obj};
3314 const int load_order = obj.block_load_order();
3315 if (load_order >= 0 && !claimed_load_orders.insert(load_order).second) {
3316 return absl::FailedPreconditionError(absl::StrFormat(
3317 "Room 0x%03X has multiple pushable blocks claiming non-new "
3318 "load-order slot %d",
3322 appended.push_back(encoded_block);
3323 }
else if (load_order >= 0 && load_order < original_slot_count) {
3324 const int original_offset = load_order * 4;
3325 const uint16_t original_room_id =
3326 static_cast<uint16_t
>(original_buffer[original_offset] |
3327 (original_buffer[original_offset + 1] << 8));
3328 if (original_room_id !=
static_cast<uint16_t
>(rid)) {
3333 appended.push_back(encoded_block);
3336 slot_replacements.emplace(load_order, encoded_block);
3340 appended.push_back(encoded_block);
3347 std::vector<uint8_t> output;
3348 output.reserve(original_byte_len + appended.size() * 4);
3349 std::vector<std::pair<const RoomObject*, int>> load_order_updates;
3350 load_order_updates.reserve(slot_replacements.size() + appended.size());
3351 const auto append_encoded_block =
3352 [&output, &load_order_updates](
const EncodedBlock& block) {
3353 const int output_slot =
static_cast<int>(output.size() / 4);
3354 output.push_back(block.bytes.b1);
3355 output.push_back(block.bytes.b2);
3356 output.push_back(block.bytes.b3);
3357 output.push_back(block.bytes.b4);
3358 load_order_updates.emplace_back(block.source_object, output_slot);
3360 for (
int slot = 0; slot < original_slot_count; ++slot) {
3361 const uint8_t b1 = original_buffer[slot * 4 + 0];
3362 const uint8_t b2 = original_buffer[slot * 4 + 1];
3363 const uint16_t slot_room_id =
static_cast<uint16_t
>(b1 | (b2 << 8));
3364 if (owned_room_ids.contains(slot_room_id)) {
3365 const auto it = slot_replacements.find(slot);
3366 if (it == slot_replacements.end()) {
3371 append_encoded_block(it->second);
3374 output.push_back(b1);
3375 output.push_back(b2);
3376 output.push_back(original_buffer[slot * 4 + 2]);
3377 output.push_back(original_buffer[slot * 4 + 3]);
3383 for (
const auto& block : appended) {
3384 append_encoded_block(block);
3393 if (output.empty()) {
3394 return absl::FailedPreconditionError(
3395 "Pushable-block table cannot be empty: ALTTP's runtime scan reads one "
3396 "entry before comparing the byte-length limit. Keep at least one "
3397 "pushable block, or patch the runtime loop before removing the last "
3402 const int kMaxEntries = (4 * kBlocksRegionSize) / 4;
3403 if (
static_cast<int>(output.size() / 4) > kMaxEntries) {
3404 return absl::FailedPreconditionError(absl::StrCat(
3405 "Pushable-block table overflow: ", output.size() / 4,
3406 " entries exceeds the vanilla cap of ", kMaxEntries,
3407 " (expand layout requires repointing all 4 LDA.l operand slots; "
3408 "out of scope for this encoder)."));
3415 const int total_bytes =
static_cast<int>(output.size());
3416 struct BlockWriteDestination {
3421 std::vector<BlockWriteDestination> write_destinations;
3422 write_destinations.reserve(4);
3423 for (
int r = 0; r < 4; ++r) {
3424 const int off = r * kBlocksRegionSize;
3425 const int len = std::min(kBlocksRegionSize, total_bytes - off);
3428 write_destinations.push_back({destination_pcs[r], off, len});
3433 for (
const auto&
destination : write_destinations) {
3434 std::vector<uint8_t> chunk(
3448 for (
const auto& [
object, load_order] : load_order_updates) {
3449 const_cast<RoomObject*
>(object)->set_block_load_order(load_order);
3451 for (
int room_id = 0; room_id < room_count; ++room_id) {
3452 if (
const Room* room = room_lookup(room_id);
3454 const_cast<Room*
>(room)->ClearBlocksDirty();
3457 return absl::OkStatus();
3460template <
typename RoomLookup>
3462 RoomLookup&& room_lookup) {
3464 return absl::InvalidArgumentError(
"ROM not loaded");
3471 const auto& rom_data = rom->
vector();
3476 if (!has_ptr_table) {
3477 for (
int room_id = 0; room_id < room_count; ++room_id) {
3478 const Room* room = room_lookup(room_id);
3480 return absl::FailedPreconditionError(
3481 "Custom collision region not present in this ROM");
3484 return absl::OkStatus();
3487 if (!has_data_region) {
3488 for (
int room_id = 0; room_id < room_count; ++room_id) {
3489 const Room* room = room_lookup(room_id);
3491 return absl::FailedPreconditionError(
3492 "Custom collision data region not present in this ROM");
3495 return absl::OkStatus();
3504 "CustomCollisionPointers"));
3508 "CustomCollisionData"));
3512 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3513 const Room* room = room_lookup(room_id);
3518 const int actual_room_id = room->
id();
3520 if (ptr_offset + 2 >=
static_cast<int>(rom_data.size())) {
3521 return absl::OutOfRangeError(
"Custom collision pointer out of range");
3529 const_cast<Room*
>(room)->ClearCustomCollisionDirty();
3545 const_cast<Room*
>(room)->ClearCustomCollisionDirty();
3551 const_cast<Room*
>(room)->ClearCustomCollisionDirty();
3554 return absl::OkStatus();
3559 rom,
static_cast<int>(rooms.size()),
3560 [&rooms](
int room_id) { return &rooms[room_id]; });
3564 const std::function<
Room*(
int)>& room_lookup) {
3568absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
3570 if (rom ==
nullptr || !rom->
is_loaded()) {
3571 return absl::InvalidArgumentError(
"ROM not loaded");
3573 const auto& rom_data = rom->
vector();
3576 return absl::OutOfRangeError(
"Chest pointers out of range");
3579 const uint32_t data_pointer =
3583 const uint32_t data_pc =
SnesToPc(data_pointer);
3584 if (data_pc > rom_data.size() ||
3586 rom_data.size() -
static_cast<size_t>(data_pc)) {
3587 return absl::OutOfRangeError(
"Chest data region out of range");
3589 const uint32_t data_end =
3591 const auto overlaps = [](uint32_t begin, uint32_t end, uint32_t other_begin,
3592 uint32_t other_end) {
3593 return begin < other_end && other_begin < end;
3599 return absl::FailedPreconditionError(
3600 "Chest data region overlaps chest metadata operands");
3603 return std::vector<std::pair<uint32_t, uint32_t>>{
3606 {data_pc, data_end},
3616 uint16_t
room_id()
const {
return word & 0x7FFF; }
3622 const std::vector<uint8_t>& rom_data,
int cpos,
int byte_length) {
3623 std::vector<PhysicalChestRecord> records;
3625 records.reserve(record_count);
3626 for (
int i = 0; i < record_count; ++i) {
3632 const uint16_t word =
3633 (
static_cast<uint16_t
>(rom_data[off + 1]) << 8) | rom_data[off];
3641 bytes->push_back(word & 0xFF);
3642 bytes->push_back((word >> 8) & 0xFF);
3643 bytes->push_back(item);
3648 const uint16_t word =
static_cast<uint16_t
>(room_id) |
3649 (
chest.size ?
static_cast<uint16_t
>(0x8000) : 0);
3654 absl::Span<const uint8_t> replacement,
3655 std::vector<ChestWriteRun>* writes) {
3657 while (cursor < replacement.size()) {
3658 if (replacement[cursor] == expected[cursor]) {
3662 const size_t begin = cursor;
3665 }
while (cursor < replacement.size() &&
3666 replacement[cursor] != expected[cursor]);
3669 run.
pc = pc +
static_cast<uint32_t
>(begin);
3671 expected.begin() + cursor);
3673 replacement.begin() + cursor);
3674 if (!writes->empty() && writes->back().end() == run.
pc) {
3675 writes->back().expected_bytes.insert(writes->back().expected_bytes.end(),
3678 writes->back().replacement_bytes.insert(
3682 writes->push_back(std::move(run));
3688 std::sort(writes->begin(), writes->end(),
3690 return lhs.pc < rhs.pc;
3692 std::vector<ChestWriteRun> merged;
3693 merged.reserve(writes->size());
3695 if (!merged.empty() && merged.back().end() == write.pc) {
3696 merged.back().expected_bytes.insert(merged.back().expected_bytes.end(),
3697 write.expected_bytes.begin(),
3698 write.expected_bytes.end());
3699 merged.back().replacement_bytes.insert(
3700 merged.back().replacement_bytes.end(),
3701 write.replacement_bytes.begin(), write.replacement_bytes.end());
3704 merged.push_back(std::move(write));
3706 *writes = std::move(merged);
3710 std::vector<uint8_t> bytes;
3719 const Rom* rom,
int room_count,
3720 const std::function<
const Room*(
int)>& room_lookup) {
3721 if (rom ==
nullptr || !rom->
is_loaded()) {
3722 return absl::InvalidArgumentError(
"ROM not loaded");
3724 const auto& rom_data = rom->
vector();
3727 return absl::OutOfRangeError(
"Chest pointers out of range");
3733 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
3734 const Room* room = room_lookup(room_id);
3736 dirty_rooms[room_id] = room;
3747 plan.
data_pc = potential_ranges[1].first;
3754 rom_data.begin() + plan.
data_pc,
3758 return absl::FailedPreconditionError(absl::StrFormat(
3759 "Chest table byte length %d is invalid (capacity %d)",
3765 if (physical_records.size() !=
3767 return absl::OutOfRangeError(
"Chest data region is truncated");
3773 ++old_counts[record.room_id()];
3777 size_t final_record_count = physical_records.size();
3778 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
3779 if (dirty_rooms[room_id] ==
nullptr) {
3782 final_record_count -= old_counts[room_id];
3783 final_record_count += dirty_rooms[room_id]->GetChests().size();
3786 return absl::ResourceExhaustedError(absl::StrFormat(
3787 "Chest table has %d records; capacity is %d",
3791 std::vector<uint8_t> replacement_bytes;
3795 const uint16_t room_id = record.room_id();
3796 const Room* dirty_room =
3798 if (dirty_room ==
nullptr) {
3803 const size_t occurrence = seen_counts[room_id]++;
3804 const auto& replacements = dirty_room->
GetChests();
3805 if (occurrence < replacements.size()) {
3807 replacements[occurrence]);
3814 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
3815 const Room* dirty_room = dirty_rooms[room_id];
3816 if (dirty_room ==
nullptr) {
3819 const auto& replacements = dirty_room->
GetChests();
3820 for (
size_t i = seen_counts[room_id]; i < replacements.size(); ++i) {
3826 return absl::InternalError(
"Chest save plan size mismatch");
3829 const std::array<uint8_t, 2> expected_length = {
3832 const uint16_t replacement_length =
3833 static_cast<uint16_t
>(replacement_bytes.size());
3834 const std::array<uint8_t, 2> encoded_replacement_length = {
3835 static_cast<uint8_t
>(replacement_length & 0xFF),
3836 static_cast<uint8_t
>((replacement_length >> 8) & 0xFF)};
3838 encoded_replacement_length, &plan.
writes);
3841 .subspan(0, replacement_bytes.size()),
3842 replacement_bytes, &plan.
writes);
3853 if (ptr_off < 0 || ptr_off + 1 >=
static_cast<int>(rom_data.size())) {
3856 const uint16_t item_ptr =
3857 (
static_cast<uint16_t
>(rom_data[ptr_off + 1]) << 8) | rom_data[ptr_off];
3858 if (item_ptr < 0x8000) {
3861 const int item_addr =
static_cast<int>(
SnesToPc(0x010000 | item_ptr));
3862 return item_addr >= 0 && item_addr < static_cast<int>(rom_data.size())
3868 const std::vector<uint8_t>& rom_data,
int room_id) {
3870 static_cast<int>(rom_data.size())) {
3871 return absl::OutOfRangeError(
"Room items pointer table out of range");
3874 return absl::OutOfRangeError(
"Room ID out of range");
3881 const int hard_end =
3885 return absl::FailedPreconditionError(
3886 "Room pot item pointer is null, invalid, or outside the item region");
3894 const Rom* rom,
int room_count,
3895 const std::function<
const Room*(
int)>& room_lookup) {
3896 return BuildChestSavePlanImpl(rom, room_count, room_lookup);
3899absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
3901 const std::function<
const Room*(
int)>& room_lookup) {
3909 const std::function<
const Room*(
int)>& room_lookup) {
3910 if (rom ==
nullptr || !rom->
is_loaded()) {
3911 return absl::InvalidArgumentError(
"ROM not loaded");
3916 if (canonical_plan != plan) {
3917 return absl::FailedPreconditionError(
3918 "Chest save plan does not match canonical serialization");
3920 return absl::OkStatus();
3923 const auto& rom_data = rom->
vector();
3926 plan.
data_pc > rom_data.size() ||
3928 return absl::FailedPreconditionError(
3929 "Chest save plan source is no longer addressable");
3933 return absl::FailedPreconditionError(
3934 "Chest save plan is stale: data pointer changed");
3936 const uint16_t current_length =
3940 return absl::FailedPreconditionError(
3941 "Chest save plan is stale: runtime length changed");
3945 rom_data.begin() + plan.
data_pc)) {
3946 return absl::FailedPreconditionError(
3947 "Chest save plan is stale: table bytes changed");
3950 const Room* room = room_lookup(state.room_id);
3952 EncodeChestRoomState(state.room_id, *room) != state.encoded_chests) {
3953 return absl::FailedPreconditionError(absl::StrFormat(
3954 "Chest save plan is stale for room 0x%03X", state.room_id));
3959 if (canonical_plan != plan) {
3960 return absl::FailedPreconditionError(
3961 "Chest save plan does not match canonical serialization");
3974 const_cast<Room*
>(room_lookup(state.room_id))->ClearChestsDirty();
3977 return absl::OkStatus();
3980template <
typename RoomLookup>
3982 RoomLookup&& room_lookup) {
3983 const std::function<
const Room*(int)> lookup =
3984 std::forward<RoomLookup>(room_lookup);
3992 [&rooms](
int room_id) { return &rooms[room_id]; });
3996 const std::function<
const Room*(
int)>& room_lookup) {
4000template <
typename RoomLookup>
4002 Rom* rom,
int room_count, RoomLookup&& room_lookup,
4005 return absl::InvalidArgumentError(
"ROM not loaded");
4007 const auto& rom_data = rom->
vector();
4009 static_cast<int>(rom_data.size())) {
4010 return absl::OutOfRangeError(
"Room items pointer table out of range");
4014 if (repack_layout !=
nullptr) {
4015 std::vector<DungeonStreamReplacement> replacements;
4016 for (
int room_id = 0; room_id < room_limit; ++room_id) {
4017 const Room* room = room_lookup(room_id);
4023 replacement.
room_id =
static_cast<uint32_t
>(room_id);
4027 replacement.
encoded_stream.push_back((item.position >> 8) & 0xFF);
4032 replacements.push_back(std::move(replacement));
4034 if (replacements.empty()) {
4035 return absl::OkStatus();
4044 if (
const Room* room = room_lookup(replacement.room_id);
4046 const_cast<Room*
>(room)->ClearPotItemsDirty();
4049 return absl::OkStatus();
4052 struct PendingPotItemWrite {
4055 std::vector<uint8_t> bytes;
4060 std::vector<PendingPotItemWrite> pending_writes;
4061 for (
int room_id = 0; room_id < room_limit; ++room_id) {
4062 const Room* room = room_lookup(room_id);
4068 GetPotItemStreamInfo(rom_data, room_id));
4069 if (stream_info.shared) {
4070 return absl::FailedPreconditionError(absl::StrFormat(
4071 "Room %d pot item stream at PC 0x%06X is shared; repacking is "
4073 room_id, stream_info.address));
4075 if (stream_info.capacity() <= 0) {
4076 return absl::FailedPreconditionError(absl::StrFormat(
4077 "Room %d pot item stream has no safe physical boundary", room_id));
4080 PendingPotItemWrite pending;
4081 pending.room_id = room_id;
4082 pending.address = stream_info.address;
4084 pending.bytes.push_back(pi.position & 0xFF);
4085 pending.bytes.push_back((pi.position >> 8) & 0xFF);
4086 pending.bytes.push_back(pi.item);
4088 pending.bytes.push_back(0xFF);
4089 pending.bytes.push_back(0xFF);
4090 if (
static_cast<int>(pending.bytes.size()) > stream_info.capacity()) {
4091 return absl::ResourceExhaustedError(absl::StrFormat(
4092 "Room %d pot item data too large! Size: %d, Available: %d", room_id,
4093 static_cast<int>(pending.bytes.size()), stream_info.capacity()));
4095 pending_writes.push_back(std::move(pending));
4098 for (
const auto& pending : pending_writes) {
4099 const bool data_changed =
4100 !std::equal(pending.bytes.begin(), pending.bytes.end(),
4101 rom_data.begin() + pending.address);
4107 for (
const auto& pending : pending_writes) {
4108 if (
const Room* room = room_lookup(pending.room_id); room !=
nullptr) {
4109 const_cast<Room*
>(room)->ClearPotItemsDirty();
4112 return absl::OkStatus();
4117 [&rooms](
int room_id) { return &rooms[room_id]; });
4123 rom,
static_cast<int>(rooms.size()),
4124 [&rooms](
int room_id) { return &rooms[room_id]; }, repack_layout);
4128 Rom* rom,
int room_count,
4129 const std::function<
const Room*(
int)>& room_lookup) {
4134 const std::function<
const Room*(
int)>& room_lookup,
4140 auto rom_data =
rom()->vector();
4161 const int kRegionSize = 0x80;
4164 std::vector<uint8_t> blocks_data(blocks_count, 0);
4165 for (
int r = 0; r < 4; ++r) {
4166 const int slot = kPointerSlots[r];
4167 if (slot + 2 >=
static_cast<int>(rom_data.size())) {
4168 LOG_WARN(
"Room",
"LoadBlocks: pointer slot %d out of range", r);
4171 const absl::Status operand_status =
4172 ValidateBlocksLoaderPointerOperand(rom_data, slot);
4173 if (!operand_status.ok()) {
4175 std::string(operand_status.message()).c_str());
4179 (rom_data[slot + 2] << 16) | (rom_data[slot + 1] << 8) | rom_data[slot];
4181 const int off = r * kRegionSize;
4182 const int len = std::min(kRegionSize, blocks_count - off);
4185 if (pc < 0 || pc + len >
static_cast<int>(rom_data.size())) {
4186 LOG_WARN(
"Room",
"LoadBlocks: region %d data out of range", r);
4189 std::copy_n(rom_data.begin() + pc, len, blocks_data.begin() + off);
4198 return (obj.options() & ObjectOption::Block) !=
4199 ObjectOption::Nothing;
4211 for (
int i = 0; i + 3 < blocks_count; i += 4) {
4213 blocks_data[i + 2], blocks_data[i + 3]};
4229 LOG_DEBUG(
"Room",
"Loaded block at (%d,%d) draw_layer=%d behavior_layer=%d",
4238 auto rom_data =
rom()->vector();
4254 int ptr_addr = table_addr + (
room_id_ * 2);
4255 if (ptr_addr + 1 >=
static_cast<int>(rom_data.size()))
4258 uint16_t item_ptr = (rom_data[ptr_addr + 1] << 8) | rom_data[ptr_addr];
4261 int item_addr =
SnesToPc(0x010000 | item_ptr);
4264 while (item_addr + 2 <
static_cast<int>(rom_data.size())) {
4266 uint16_t position = (rom_data[item_addr + 1] << 8) | rom_data[item_addr];
4269 if (position == 0xFFFF)
4273 uint8_t item_type = rom_data[item_addr + 2];
4277 pot_item.
item = item_type;
4287 auto rom_data =
rom()->vector();
4298 const int max_offset = rom_data[
kPitCount];
4299 const int pit_entries = max_offset / 2 + 1;
4301 const int pit_ptr = (rom_data[
kPitPointer + 2] << 16) |
4305 "LoadPits: room_id=%d, RoomsWithPitDamage entries=%d, "
4306 "table_snes=0x%06X",
4315 LOG_DEBUG(
"Room",
"Per-room pit destination: target=%d, target_layer=%d",
4330 for (
const auto& sprite :
sprites_) {
4331 if (sprite.IsOverlord()) {
4341 for (
const auto& door :
doors_) {
4343 const bool is_special = [&]() ->
bool {
4344 switch (door.type) {
4375 auto options = obj.options();
4388 if (obj.id_ == 0x11E || obj.id_ == 0x11F) {
4393 if (obj.id_ >= 0xF83 && obj.id_ <= 0xF8F) {
4400 if ((obj.id_ >= 0x130 && obj.id_ <= 0x135) || obj.id_ == 0x139 ||
4401 obj.id_ == 0x13A || obj.id_ == 0x13B) {
4405 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.
Draws dungeon objects to background buffers using game patterns.
void SetRoomFloorGraphics(uint8_t floor1, uint8_t floor2)
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 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)
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
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()
bool custom_collision_dirty() const
gfx::SnesPalette rendered_dungeon_palette_
uint64_t composite_rendered_source_revision_
zelda3_version_pointers version_constants() const
std::vector< Door > doors_
void SetStaircaseRoom(int index, uint8_t room)
static constexpr uint8_t kObjectHeaderFloor1Dirty
absl::Status RemoveObject(size_t index)
void SetStair1TargetLayer(uint8_t layer)
void SetLayer2Mode(uint8_t mode)
void LoadLayoutTilesToBuffer()
uint8_t staircase_room(int index) const
void SetTag2(TagKey tag2)
std::vector< DungeonLimitInfo > GetExceededLimitDetails() const
Get list of exceeded limits with details.
void ParseObjectsFromLocation(int objects_location)
uint8_t cached_floor1_graphics_
bool custom_collision_dirty_
static constexpr uint8_t kObjectHeaderFloor2Dirty
void ReloadGraphics(std::optional< uint8_t > entrance_blockset=std::nullopt)
bool pot_items_dirty() const
void SetTag1Direct(TagKey tag1)
void SetHolewarp(uint8_t hw)
void SetStair2Target(uint8_t target)
bool object_stream_dirty() const
bool sprites_dirty() const
void SetCollision(CollisionKey collision)
bool object_stream_header_dirty() const
gfx::BackgroundBuffer bg1_buffer_
void SetStaircasePlane(int index, uint8_t plane)
absl::Status SaveObjects(const DungeonStreamLayout *layout=nullptr)
void SetIsDark(bool is_dark)
std::map< DungeonLimit, int > GetLimitedObjectCounts() const
Count limited objects in this room.
void RenderRoomGraphics()
absl::Status SaveRoomHeader()
gfx::Bitmap composite_bitmap_
Room & operator=(Room &&)
bool chests_dirty() const
uint64_t composite_signature_
CollisionKey collision() const
void LoadRoomGraphics(std::optional< uint8_t > entrance_blockset=std::nullopt)
uint8_t staircase_rooms_[4]
gfx::Bitmap & GetCompositeBitmap(RoomLayerManager &layer_mgr)
Get a composite bitmap of all layers merged.
std::vector< uint8_t > EncodeObjects() const
void SetEffect(EffectKey effect)
absl::Status SaveObjectStreamHeader(const DungeonStreamLayout *layout=nullptr)
gfx::BackgroundBuffer object_bg2_buffer_
void ClearWaterFillDirty()
std::array< uint8_t, 16 > blocks_
void SetTag1(TagKey tag1)
void SetBackgroundTileset(uint8_t tileset)
void SetStair3TargetLayer(uint8_t layer)
void SetRenderEntranceBlockset(uint8_t entrance_blockset)
absl::Status SaveSprites(const DungeonStreamLayout *layout=nullptr)
void PrepareForRender(std::optional< uint8_t > entrance_blockset=std::nullopt)
const std::vector< RoomObject > & GetTileObjects() const
void SetLayerMerging(LayerMergeType merging)
void SetPitsTargetLayer(uint8_t layer)
bool blocks_dirty() const
void SetSpriteTileset(uint8_t tileset)
void SetStair1Target(uint8_t target)
void SetBg2(background2 bg2)
static constexpr uint8_t kObjectHeaderLayoutDirty
bool torches_dirty() const
void EnsureObjectsLoaded()
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_
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
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)
uint64_t NextRoomCompositeRevision()
constexpr int kTorchesMaxSize
const gfx::SnesPalette * ResolvePaletteOrFirst(const gfx::PaletteGroup &group, size_t requested_index)
absl::Status ValidateSpecialObjectDrawLayerSelector(const RoomObject &object, int room_id, const char *object_type)
int ReadRoomPotItemAddressPc(const std::vector< uint8_t > &rom_data, int room_id)
absl::Status RelocateDungeonStream(Rom *rom, int room_id, DungeonStreamKind expected_kind, const DungeonStreamLayout &layout, std::vector< uint8_t > encoded_stream)
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)
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)
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)
std::vector< SDL_Color > BuildDungeonRenderPalette(const gfx::SnesPalette &dungeon_palette, const gfx::SnesPalette *hud_palette)
absl::StatusOr< DungeonStreamWritePlan > PlanDungeonStreamRepack(const DungeonStreamInventory &inventory, const std::vector< DungeonStreamReplacement > &requested_replacements)
constexpr int kRoomsSpritePointer
constexpr int kTileAddressFloor
constexpr int GetDungeonObjectDataRegionEnd(int pc_address)
absl::Status SaveAllPits(Rom *rom)
constexpr int kChestsDataPointer1
constexpr int kBlocksLength
absl::Status SaveAllBlocks(Rom *rom)
constexpr int kBlocksPointer1
constexpr int kChestTableCapacityRecords
constexpr int kRoomItemsPointers
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)
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::Status SaveAllTorchesImpl(Rom *rom, int room_count, RoomLookup &&room_lookup)
constexpr int kNumberOfRooms
absl::Status ApplyChestSavePlan(Rom *rom, const ChestSavePlan &plan, const std::function< const Room *(int)> &room_lookup)
const std::string RoomEffect[8]
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)
PushableBlockEntry DecodePushableBlockEntry(const PushableBlockBytes &bytes)
constexpr int kBlocksPointer2
constexpr int kRoomObjectPointer
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
std::array< uint8_t, 64 *64 > tiles
std::vector< DungeonStreamAliasGroup > aliases
DungeonStreamLayout layout
std::vector< DungeonStreamIssue > issues
std::vector< DungeonStreamOverlap > overlaps
std::vector< DungeonStreamRecord > streams
std::vector< DungeonStreamPcRange > data_ranges
std::vector< uint8_t > encoded_stream
std::array< std::array< uint8_t, 4 >, kNumSpritesets > spriteset_ids
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
std::array< std::array< uint8_t, 4 >, kNumPalettesets > paletteset_ids
gfx::PaletteGroupMap palette_groups
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
std::vector< uint8_t > graphics_buffer
static Door FromRomBytes(uint8_t b1, uint8_t b2)
uint8_t object_stream_header
std::vector< uint8_t > bytes
Public YAZE API umbrella header.