12#include <unordered_map>
13#include <unordered_set>
17#include "absl/strings/str_cat.h"
18#include "absl/strings/str_format.h"
46 return std::any_of(objects.begin(), objects.end(),
47 [](
const RoomObject& obj) { return obj.id_ == 0x31; });
51 return static_cast<uint8_t
>((byte0 >> 5) & 0x07);
55 return (byte0 & 0x01) != 0;
66 return background2::DarkRoom;
71template <
typename WriteColor>
74 WriteColor write_color) {
75 if (hud_palette !=
nullptr) {
76 const size_t hud_count = std::min<size_t>(hud_palette->
size(), 32);
77 for (
size_t i = 0; i < hud_count; ++i) {
78 write_color(
static_cast<int>(i), (*hud_palette)[i]);
82 constexpr int kColorsPerRomBank = 15;
83 constexpr int kIndicesPerSdlBank = 16;
84 constexpr int kNumRomBanks = 6;
85 constexpr int kDungeonBankStart = 2;
86 for (
int rom_bank = 0; rom_bank < kNumRomBanks; ++rom_bank) {
87 const int sdl_bank = rom_bank + kDungeonBankStart;
88 for (
int color = 0; color < kColorsPerRomBank; ++color) {
89 const size_t rom_index =
90 static_cast<size_t>(rom_bank * kColorsPerRomBank + color);
91 if (rom_index >= dungeon_palette.
size()) {
94 const int dst_index = sdl_bank * kIndicesPerSdlBank + color + 1;
95 write_color(dst_index, dungeon_palette[rom_index]);
105 std::vector<SDL_Color> colors(256, {0, 0, 0, 0});
106 PopulateDungeonRenderPaletteRows(
107 dungeon_palette, hud_palette,
109 if (dst_index < 0 || dst_index >=
static_cast<int>(colors.size())) {
112 const ImVec4 rgb = color.
rgb();
113 colors[dst_index] = {
static_cast<Uint8
>(rgb.x),
114 static_cast<Uint8
>(rgb.y),
115 static_cast<Uint8
>(rgb.z), 255};
117 colors[255] = {0, 0, 0, 0};
124 PopulateDungeonRenderPaletteRows(
125 dungeon_palette, hud_palette,
127 if (dst_index < 0 || dst_index >=
static_cast<int>(cgram.size())) {
130 cgram[dst_index] = color.
snes();
141 "Light Torch to See Floor",
146 "NW Kill Enemy to Open",
147 "NE Kill Enemy to Open",
148 "SW Kill Enemy to Open",
149 "SE Kill Enemy to Open",
150 "W Kill Enemy to Open",
151 "E Kill Enemy to Open",
152 "N Kill Enemy to Open",
153 "S Kill Enemy to Open",
154 "Clear Quadrant to Open",
155 "Clear Full Tile to Open",
156 "NW Push Block to Open",
157 "NE Push Block to Open",
158 "SW Push Block to Open",
159 "SE Push Block to Open",
160 "W Push Block to Open",
161 "E Push Block to Open",
162 "N Push Block to Open",
163 "S Push Block to Open",
164 "Push Block to Open",
165 "Pull Lever to Open",
166 "Collect Prize to Open",
167 "Hold Switch Open Door",
168 "Toggle Switch to Open Door",
177 "Push Switch Exploding Wall",
179 "Open Chest (Holes 0)",
182 "Defeat Boss for Dungeon Prize",
183 "SE Kill Enemy to Push Block",
184 "Trigger Switch Chest",
185 "Pull Lever Exploding Wall",
186 "NW Kill Enemy for Chest",
187 "NE Kill Enemy for Chest",
188 "SW Kill Enemy for Chest",
189 "SE Kill Enemy for Chest",
190 "W Kill Enemy for Chest",
191 "E Kill Enemy for Chest",
192 "N Kill Enemy for Chest",
193 "S Kill Enemy for Chest",
194 "Clear Quadrant for Chest",
195 "Clear Full Tile for Chest",
196 "Light Torches to Open",
204 "Open Chest for Holes 8",
205 "Push Block for Chest",
206 "Clear Room for Triforce Door",
207 "Light Torches for Chest",
214 int physical_end = -1;
218 return physical_end > address ? physical_end - address : 0;
224 int known_region_end = -1) {
226 if (room_id < 0 || room_id >=
static_cast<int>(room_addresses.size())) {
230 info.
address = room_addresses[room_id];
235 int next_address = std::numeric_limits<int>::max();
236 for (
int other_room_id = 0;
237 other_room_id < static_cast<int>(room_addresses.size());
239 if (other_room_id == room_id || room_addresses[other_room_id] < 0) {
242 const int other_address = room_addresses[other_room_id];
243 if (other_address == info.
address) {
245 }
else if (other_address > info.
address) {
246 next_address = std::min(next_address, other_address);
250 if (known_region_end > info.
address) {
251 next_address = std::min(next_address, known_region_end);
253 if (next_address == std::numeric_limits<int>::max()) {
261 constexpr int kLoRomBankSize = 0x8000;
262 const int bank_end = ((info.
address / kLoRomBankSize) + 1) * kLoRomBankSize;
263 if (next_address > bank_end) {
272 if (table_pc ==
nullptr) {
273 return absl::InvalidArgumentError(
"table_pc pointer is null");
276 return absl::OutOfRangeError(
277 "Object pointer table address is out of range");
280 const uint32_t table_snes =
284 const int pc =
static_cast<int>(
SnesToPc(table_snes));
285 if (pc < 0 || pc + (
kNumberOfRooms * 3) >
static_cast<int>(rom_data.size())) {
286 return absl::OutOfRangeError(
"Object pointer table is out of range");
290 return absl::OkStatus();
294 int table_pc,
int room_id) {
298 const int ptr_off = table_pc + (room_id * 3);
299 if (ptr_off < 0 || ptr_off + 2 >=
static_cast<int>(rom_data.size())) {
302 return (
static_cast<uint32_t
>(rom_data[ptr_off + 2]) << 16) |
303 (
static_cast<uint32_t
>(rom_data[ptr_off + 1]) << 8) |
310 if ((snes & 0xFFFF) < 0x8000) {
313 const int pc =
static_cast<int>(
SnesToPc(snes));
314 return pc >= 0 && pc < static_cast<int>(rom_data.size()) ? pc : -1;
318 const std::vector<uint8_t>& rom_data,
int room_id) {
320 return absl::OutOfRangeError(
"Room ID out of range");
332 return absl::OutOfRangeError(
"Object stream pointer is out of range");
339 if (table_pc ==
nullptr) {
340 return absl::InvalidArgumentError(
"table_pc pointer is null");
343 return absl::OutOfRangeError(
344 "Sprite pointer table address is out of range");
350 if (pc < 0 || pc + (
kNumberOfRooms * 2) >
static_cast<int>(rom_data.size())) {
351 return absl::OutOfRangeError(
"Sprite pointer table is out of range");
355 return absl::OkStatus();
363 const int ptr_off = table_pc + (room_id * 2);
364 if (ptr_off < 0 || ptr_off + 1 >=
static_cast<int>(rom_data.size())) {
368 const uint16_t pointer =
369 (
static_cast<uint16_t
>(rom_data[ptr_off + 1]) << 8) | rom_data[ptr_off];
370 if (pointer < 0x8000) {
373 const int sprite_address =
static_cast<int>(
SnesToPc((0x09 << 16) | pointer));
374 return sprite_address >= 0 &&
375 sprite_address < static_cast<int>(rom_data.size())
381 const std::vector<uint8_t>& rom_data,
int room_id) {
383 return absl::OutOfRangeError(
"Room ID out of range");
396 return absl::OutOfRangeError(
"Sprite stream pointer is out of range");
402 int sprite_address,
int hard_end) {
403 if (sprite_address < 0 || sprite_address >= hard_end ||
404 sprite_address >=
static_cast<int>(rom_data.size())) {
408 int cursor = sprite_address + 1;
409 while (cursor < hard_end) {
410 if (rom_data[cursor] == 0xFF) {
414 if (cursor + 2 >= hard_end) {
421 return std::max(0, cursor - sprite_address);
427 std::vector<uint8_t> encoded_stream) {
428 if (layout.
kind != expected_kind) {
429 return absl::InvalidArgumentError(absl::StrFormat(
430 "Room %d relocation layout has the wrong dungeon stream kind",
439 std::move(encoded_stream)}}));
446 if (layout.
kind != expected_kind) {
447 return absl::InvalidArgumentError(absl::StrFormat(
448 "Room %d save layout has the wrong dungeon stream kind", room_id));
450 if (room_id < 0 ||
static_cast<uint32_t
>(room_id) >= layout.
pointer_count) {
451 return absl::OutOfRangeError(
452 "Room ID is outside the dungeon stream layout");
457 if (!inventory.
ok()) {
458 return absl::FailedPreconditionError(absl::StrFormat(
459 "Dungeon stream inventory has %zu issue(s); refusing an in-place "
461 inventory.
issues.size()));
464 const auto contains_room = [room_id](
const std::vector<uint32_t>& owners) {
465 return std::find(owners.begin(), owners.end(),
466 static_cast<uint32_t
>(room_id)) != owners.end();
468 for (
const auto& alias : inventory.
aliases) {
469 if (contains_room(alias.room_ids)) {
473 for (
const auto& overlap : inventory.
overlaps) {
474 if (contains_room(overlap.first_room_ids) ||
475 contains_room(overlap.second_room_ids)) {
480 const auto& record = inventory.
streams[room_id];
481 const uint64_t replacement_end =
482 static_cast<uint64_t
>(record.data_pc) + replacement_size;
483 const uint32_t bank_end = ((record.data_pc / 0x8000u) + 1u) * 0x8000u;
484 const bool stays_in_declared_data =
485 replacement_end <= bank_end &&
488 return range.begin <= record.data_pc &&
489 replacement_end <= range.end;
491 return !stays_in_declared_data;
498 if (!rom || !rom->
is_loaded() || rom->
size() == 0 || room_id < 0 ||
503 const auto& rom_data = rom->
vector();
505 if (!GetObjectPointerTablePc(rom_data, &table_pc).ok()) {
508 room_size.room_size_pointer =
509 ReadRoomObjectAddressSnes(rom_data, table_pc, room_id);
511 auto stream_info = GetObjectStreamInfo(rom_data, room_id);
512 if (!stream_info.ok() || stream_info->shared) {
515 room_size.room_size = stream_info->capacity();
547 Room room(room_id, rom);
563 header_pointer =
SnesToPc(header_pointer);
572 int table_offset = (header_pointer) + (room_id * 2);
573 if (table_offset < 0 || table_offset + 1 >=
static_cast<int>(rom->
size())) {
578 (rom->
data()[table_offset + 1] << 8) +
579 rom->
data()[table_offset];
581 auto header_location =
SnesToPc(address);
584 if (header_location < 0 ||
585 header_location + 13 >=
static_cast<int>(rom->
size())) {
589 const uint8_t header_byte0 = rom->
data()[header_location];
592 room.
SetBg2(Background2FromHeaderByte(header_byte0));
594 room.
SetIsLight(IsDarkRoomHeaderByte(header_byte0));
595 room.
SetIsDark(IsDarkRoomHeaderByte(header_byte0));
630 header_pointer_2 =
SnesToPc(header_pointer_2);
639 int table_offset_2 = (header_pointer_2) + (room_id * 2);
640 if (table_offset_2 < 0 ||
641 table_offset_2 + 1 >=
static_cast<int>(rom->
size())) {
646 (rom->
data()[table_offset_2 + 1] << 8) +
647 rom->
data()[table_offset_2];
650 if (msg_addr >= 0 && msg_addr + 1 <
static_cast<int>(rom->
size())) {
651 uint16_t msg_val = (rom->
data()[msg_addr + 1] << 8) | rom->
data()[msg_addr];
659 if (hpos < 0 || hpos + 14 >=
static_cast<int>(rom->
size())) {
663 uint8_t b = rom->
data()[hpos];
689 b = rom->
data()[hpos];
719 game_data_(game_data),
731 const int num_palettes =
static_cast<int>(group.size());
732 if (num_palettes == 0)
736 if (palette_ < game_data_->paletteset_ids.size() &&
744 if (id < 0 || id >= num_palettes)
757 const uint8_t requested_main_blockset =
759 uint8_t main_blockset = 0;
760 if (requested_main_blockset != 0xFF &&
761 requested_main_blockset < game_data_->main_blockset_ids.size()) {
762 main_blockset = requested_main_blockset;
763 }
else if (blockset_ < game_data_->main_blockset_ids.size()) {
767 "Room %d: invalid main fallback blockset %d; using main group 0",
770 if (requested_main_blockset != 0xFF &&
773 "Room %d: entrance main blockset %d out of range; using %d",
774 room_id_, requested_main_blockset, main_blockset);
779 "Room %d: room_blockset=%d, main_blockset=%d, spriteset=%d, "
783 for (
int i = 0; i < 8; i++) {
785 if (i >= 3 && i <= 6 &&
blockset_ < room_gfx.size()) {
786 const uint8_t room_sheet = room_gfx[
blockset_][i - 3];
787 if (room_sheet != 0) {
793 LOG_WARN(
"Room",
"Room %d: room blockset %d out of range; skipped $0AA2",
801 const size_t sprite_gfx_index =
static_cast<size_t>(
spriteset_) + 64;
802 if (sprite_gfx_index < sprite_gfx.size()) {
803 for (
int i = 0; i < 4; i++) {
805 static_cast<uint8_t
>(sprite_gfx[sprite_gfx_index][i] + 115);
809 "Room %d: spriteset %d out of range; clearing sprite sheets",
811 for (
int i = 0; i < 4; i++) {
843 if (entrance_blockset.has_value()) {
854 if (entrance_blockset.has_value()) {
863 !bg2_bmp.is_active() || bg2_bmp.width() == 0) {
879 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: ROM not loaded");
884 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: GameData not set");
888 if (gfx_buffer_data->empty()) {
889 LOG_DEBUG(
"Room",
"CopyRoomGraphicsToBuffer: Graphics buffer is empty");
893 LOG_DEBUG(
"Room",
"Room %d: Copying 8BPP graphics (buffer size: %zu)",
911 const uint8_t active_main_blockset =
916 auto is_right_palette_background_slot = [&](
int block) ->
bool {
917 if (block < 0 || block >= 8) {
920 const int runtime_slot = 7 - block;
921 if (active_main_blockset < 0x20) {
922 return runtime_slot >= 4;
924 return (runtime_slot == 2 || runtime_slot == 3 || runtime_slot == 4 ||
929 for (
int block = 0; block < 16; block++) {
933 if (sheet_id >= 223) {
934 LOG_WARN(
"Room",
"Invalid sheet index %d for block %d", sheet_id, block);
940 int src_sheet_offset = sheet_id * 4096;
943 if (src_sheet_offset + 4096 > gfx_buffer_data->size()) {
944 LOG_ERROR(
"Room",
"Graphics offset out of bounds: %d (size: %zu)",
945 src_sheet_offset, gfx_buffer_data->size());
950 int dest_index_base = block * 4096;
952 const uint8_t* src = gfx_buffer_data->data() + src_sheet_offset;
957 const bool right_pal = is_right_palette_background_slot(block);
959 memcpy(dst, src, 4096);
962 for (
int i = 0; i < 4096; ++i) {
964 if (p != 0 && p < 8) {
992 bool properties_changed =
false;
1006 properties_changed =
true;
1016 properties_changed =
true;
1024 if (bg1_bmp.is_active() && bg1_bmp.width() > 0 && bg2_bmp.is_active() &&
1025 bg2_bmp.width() > 0) {
1027 "Room %d: No changes detected, skipping render",
room_id_);
1033 "Room %d: Rendering graphics (dirty_flags: g=%d o=%d l=%d t=%d)",
1052 "Room %d: floor1=%d, floor2=%d, blocks_size=%zu",
room_id_,
1057 bool need_floor_draw = was_graphics_dirty;
1062 if (!bg1_bmp.is_active() || bg1_bmp.width() == 0 || !bg2_bmp.is_active() ||
1063 bg2_bmp.width() == 0) {
1064 need_floor_draw =
true;
1066 "Room %d: Bitmaps not created yet, forcing floor draw",
room_id_);
1069 if (need_floor_draw) {
1078 bool need_bg_draw = was_graphics_dirty || need_floor_draw;
1090 if (was_layout_dirty || need_floor_draw) {
1099 if (dungeon_pal_group.empty())
1103 auto bg1_palette = dungeon_pal_group[palette_id];
1112 LOG_DEBUG(
"Room",
"RenderRoomGraphics: Palette ID=%d, Size=%zu", palette_id,
1113 bg1_palette.size());
1114 if (!bg1_palette.empty()) {
1115 LOG_DEBUG(
"Room",
"RenderRoomGraphics: First color: R=%d G=%d B=%d",
1116 bg1_palette[0].rom_color().red, bg1_palette[0].rom_color().green,
1117 bg1_palette[0].rom_color().blue);
1120 if (bg1_palette.size() > 0) {
1121 std::optional<gfx::SnesPalette> hud_palette_storage;
1125 hud_palette = &*hud_palette_storage;
1144 const auto render_palette =
1152 auto set_dungeon_palette = [&](
gfx::Bitmap& bmp) {
1153 bmp.SetPalette(render_palette);
1154 if (bmp.surface()) {
1156 SDL_SetColorKey(bmp.surface(), SDL_TRUE, 255);
1157 SDL_SetSurfaceBlendMode(bmp.surface(), SDL_BLENDMODE_BLEND);
1161 set_dungeon_palette(bg1_bmp);
1162 set_dungeon_palette(bg2_bmp);
1167 auto* surface = bg1_bmp.surface();
1172 "Room::RenderRoomGraphics (BG1)", palette_id,
true);
1176 "Room::RenderRoomGraphics (after SetPalette)", surface);
1179 "Room::RenderRoomGraphics", palette_id,
false,
1180 "SDL surface has no palette!");
1194 if (bg2_bmp.surface()) {
1195 SDL_SetSurfaceAlphaMod(bg2_bmp.surface(), 128);
1203 if (bg2_bmp.surface()) {
1204 SDL_SetSurfaceBlendMode(bg2_bmp.surface(), SDL_BLENDMODE_ADD);
1220 if (bitmap->texture()) {
1226 release_texture(&bg1_bmp);
1227 release_texture(&bg2_bmp);
1242 "Texture commands queued for batch processing");
1250 LOG_DEBUG(
"Room",
"ROM not loaded, aborting");
1261 if (!layout_status.ok()) {
1263 layout_status.message().data());
1269 layout_objects.size());
1270 if (layout_objects.empty()) {
1278 LOG_DEBUG(
"RenderRoomGraphics",
"GameData not set, cannot render layout");
1284 if (dungeon_pal_group.empty())
1288 auto room_palette = dungeon_pal_group[palette_id];
1302 "RenderRoomGraphics",
"Layout Draw failed: %s",
1303 std::string(status.message().data(), status.message().size()).c_str());
1305 LOG_DEBUG(
"RenderRoomGraphics",
"Layout rendered with %zu objects",
1306 layout_objects.size());
1311 LOG_DEBUG(
"[RenderObjectsToBackground]",
1312 "Starting object rendering for room %d",
room_id_);
1315 LOG_DEBUG(
"[RenderObjectsToBackground]",
"ROM not loaded, aborting");
1324 bool bitmaps_exist = bg1_bmp.is_active() && bg1_bmp.width() > 0 &&
1325 bg2_bmp.is_active() && bg2_bmp.width() > 0;
1328 LOG_DEBUG(
"[RenderObjectsToBackground]",
1329 "Room %d: Objects not dirty, skipping render",
room_id_);
1335 LOG_DEBUG(
"[RenderObjectsToBackground]",
1336 "Room %d: Emulator rendering objects",
room_id_);
1341 if (dungeon_pal_group.empty())
1345 auto room_palette = dungeon_pal_group[palette_id];
1390 int layer0_count = 0, layer1_count = 0, layer2_count = 0;
1392 switch (obj.GetLayerValue()) {
1406 "Room %03X Object Stream Summary: Main=%d, BG2Overlay=%d, BG1Overlay=%d",
1407 room_id_, layer0_count, layer1_count, layer2_count);
1424 std::vector<std::vector<RoomObject>> by_list(3);
1436 uint8_t list_index = obj.GetLayerValue();
1437 if (list_index > 2) {
1442 by_list[list_index].push_back(std::move(render_obj));
1445 absl::Status status = absl::OkStatus();
1446 bool reset_room_events_for_next_chunk =
true;
1447 for (
int pass = 0; pass < 3; ++pass) {
1448 if (by_list[pass].empty()) {
1454 reset_room_events_for_next_chunk =
false;
1455 if (!chunk_status.ok() && status.ok()) {
1456 status = chunk_status;
1463 for (
int i = 0; i < static_cast<int>(
doors_.size()); ++i) {
1464 const auto& door =
doors_[i];
1466 door_def.
type = door.type;
1483 if (pot_item.item != 0) {
1485 int tile_x = pot_item.GetTileX();
1486 int tile_y = pot_item.GetTileY();
1493 for (
const auto& sprite :
sprites_) {
1494 if (sprite.key_drop() > 0) {
1500 uint8_t key_item = (sprite.key_drop() == 1) ? 0xFD : 0xFE;
1510 constexpr uint16_t kRoomDrawObj_PushableBlock = 0x0E52;
1511 constexpr uint16_t kRoomDrawObj_TorchUnlit = 0x0EC2;
1512 constexpr uint16_t kRoomDrawObj_TorchLit = 0x0ECA;
1519 static_cast<uint16_t
>(obj.id_), obj.x_, obj.y_, obj.layer_,
1524 const uint16_t off =
1525 obj.lit_ ? kRoomDrawObj_TorchLit : kRoomDrawObj_TorchUnlit;
1530 static_cast<uint16_t
>(obj.id_), obj.x_, obj.y_, obj.layer_, off,
1538 "[RenderObjectsToBackground]",
1539 "Room %03X: ObjectDrawer failed: %s (objects left dirty for retry)",
1541 std::string(status.message().data(), status.message().size()).c_str());
1548 LOG_DEBUG(
"[RenderObjectsToBackground]",
1549 "Room %d: Objects rendered successfully",
room_id_);
1565 if (gfx_buffer_data->empty()) {
1569 auto rom_data =
rom()->vector();
1570 if (rom_data.empty()) {
1575 if (animated_frame_ < 0 || animated_frame_ > 10) {
1580 if (background_tileset_ < 0 || background_tileset_ > 255) {
1585 if (gfx_ptr < 0 || gfx_ptr >=
static_cast<int>(rom_data.size())) {
1590 while (data < 1024) {
1594 if (first_offset >= 0 &&
1595 first_offset <
static_cast<int>(gfx_buffer_data->size())) {
1596 uint8_t map_byte = (*gfx_buffer_data)[first_offset];
1599 int gfx_offset = data + (7 * 4096);
1600 if (gfx_offset >= 0 &&
1610 if (second_offset >= 0 &&
1611 second_offset <
static_cast<int>(gfx_buffer_data->size())) {
1612 uint8_t map_byte = (*gfx_buffer_data)[second_offset];
1615 int gfx_offset = data + (7 * 4096) - 1024;
1616 if (gfx_offset >= 0 &&
1628 auto rom_data =
rom()->vector();
1634 object_pointer =
SnesToPc(object_pointer);
1637 if (object_pointer < 0 || object_pointer >= (
int)
rom_->
size()) {
1641 int room_address = object_pointer + (
room_id_ * 3);
1644 if (room_address < 0 || room_address + 2 >= (
int)
rom_->
size()) {
1648 int tile_address = (rom_data[room_address + 2] << 16) +
1649 (rom_data[room_address + 1] << 8) + rom_data[room_address];
1651 int objects_location =
SnesToPc(tile_address);
1654 if (objects_location < 0 || objects_location >= (
int)
rom_->
size()) {
1659 if (objects_location + 1 < (
int)
rom_->
size()) {
1662 static_cast<uint8_t
>(rom_data[objects_location] & 0x0F);
1664 static_cast<uint8_t
>((rom_data[objects_location] >> 4) & 0x0F);
1666 "Room %d: Set floor1_graphics_=%d, floor2_graphics_=%d",
1671 static_cast<uint8_t
>((rom_data[objects_location + 1] >> 2) & 0x07);
1693 auto rom_data =
rom()->vector();
1699 int nbr_of_staircase = 0;
1701 int pos = objects_location;
1707 bool end_read =
false;
1711 while (!end_read && pos < (
int)
rom_->
size()) {
1713 if (pos + 1 >= (
int)
rom_->
size()) {
1718 b2 = rom_data[pos + 1];
1725 if (b1 == 0xFF && b2 == 0xFF) {
1729 "Room",
"Room %03X: Object list transition to index %d (%s)",
1731 layer == 1 ?
"BG2 overlay" : (layer == 2 ?
"BG1 overlay" :
"END"));
1741 if (b1 == 0xF0 && b2 == 0xFF) {
1748 if (pos + 2 >= (
int)
rom_->
size()) {
1752 b3 = rom_data[pos + 2];
1763 b1, b2, b3,
static_cast<uint8_t
>(layer));
1765 LOG_DEBUG(
"Room",
"Room %03X: Object 0x%03X at (%d,%d) stream=%d (%s)",
1767 layer == 0 ?
"Primary"
1768 : (layer == 1 ?
"BG2 overlay" :
"BG1 overlay"));
1772 if (r.
id_ >= 0 && r.
id_ <= 0xFFF) {
1786 "ParseDoor: room=%d b1=0x%02X b2=0x%02X pos=%d dir=%d type=%d",
1788 static_cast<int>(door.direction),
static_cast<int>(door.type));
1799 std::vector<uint8_t> bytes;
1803 std::vector<RoomObject> layer0_objects;
1804 std::vector<RoomObject> layer1_objects;
1805 std::vector<RoomObject> layer2_objects;
1818 switch (obj.GetLayerValue()) {
1820 layer0_objects.push_back(obj);
1823 layer1_objects.push_back(obj);
1826 layer2_objects.push_back(obj);
1842 for (
const auto& obj : layer0_objects) {
1843 auto encoded = obj.EncodeObjectToBytes();
1844 bytes.push_back(encoded.b1);
1845 bytes.push_back(encoded.b2);
1846 bytes.push_back(encoded.b3);
1848 bytes.push_back(0xFF);
1849 bytes.push_back(0xFF);
1852 for (
const auto& obj : layer1_objects) {
1853 auto encoded = obj.EncodeObjectToBytes();
1854 bytes.push_back(encoded.b1);
1855 bytes.push_back(encoded.b2);
1856 bytes.push_back(encoded.b3);
1858 bytes.push_back(0xFF);
1859 bytes.push_back(0xFF);
1862 for (
const auto& obj : layer2_objects) {
1863 auto encoded = obj.EncodeObjectToBytes();
1864 bytes.push_back(encoded.b1);
1865 bytes.push_back(encoded.b2);
1866 bytes.push_back(encoded.b3);
1871 bytes.push_back(0xF0);
1872 bytes.push_back(0xFF);
1873 for (
const auto& door :
doors_) {
1874 auto [b1, b2] = door.EncodeBytes();
1875 bytes.push_back(b1);
1876 bytes.push_back(b2);
1880 bytes.push_back(0xFF);
1881 bytes.push_back(0xFF);
1887 std::vector<uint8_t> bytes;
1889 for (
const auto& sprite :
sprites_) {
1897 b2 = (sprite.x() & 0x1F) | ((sprite.subtype() & 0x07) << 5);
1902 b1 = (sprite.y() & 0x1F) | ((sprite.subtype() & 0x18) << 2) |
1903 ((sprite.layer() & 0x01) << 7);
1905 bytes.push_back(b1);
1906 bytes.push_back(b2);
1907 bytes.push_back(b3);
1911 if (sprite.key_drop() == 1) {
1912 bytes.insert(bytes.end(), {0xFE, 0x00, 0xE4});
1913 }
else if (sprite.key_drop() == 2) {
1914 bytes.insert(bytes.end(), {0xFD, 0x00, 0xE4});
1919 bytes.push_back(0xFF);
1929 const auto& rom_data = rom->
vector();
1930 int sprite_pointer = 0;
1931 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
1935 const int hard_end =
1937 if (hard_end <= 0) {
1942 std::unordered_set<int> visited_addresses;
1944 int sprite_address =
1945 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
1946 if (sprite_address < kSpritesData || sprite_address >= hard_end) {
1949 if (!visited_addresses.insert(sprite_address).second) {
1954 MeasureSpriteStreamSize(rom_data, sprite_address, hard_end);
1955 int stream_end = sprite_address + stream_size;
1956 if (stream_end > max_used) {
1957 max_used = stream_end;
1965 const std::vector<uint8_t>& encoded_bytes) {
1967 return absl::InvalidArgumentError(
"ROM not loaded");
1970 return absl::OutOfRangeError(
"Room ID out of range");
1972 if (encoded_bytes.empty() || encoded_bytes.back() != 0xFF ||
1973 (encoded_bytes.size() % 3) != 1) {
1974 return absl::InvalidArgumentError(
1975 "Encoded sprite payload must be N*3 bytes plus 0xFF terminator");
1978 const auto& rom_data = rom->
vector();
1979 int sprite_pointer = 0;
1982 int old_sprite_address =
1983 ReadRoomSpriteAddressPc(rom_data, sprite_pointer, room_id);
1984 if (old_sprite_address < 0 ||
1985 old_sprite_address >=
static_cast<int>(rom_data.size())) {
1986 return absl::OutOfRangeError(
"Sprite address out of range");
1989 const uint8_t sort_mode = rom_data[old_sprite_address];
1992 const size_t required_size = 1u + encoded_bytes.size();
1994 static_cast<size_t>(write_pos) + required_size >
1996 return absl::ResourceExhaustedError(absl::StrFormat(
1997 "Not enough sprite data space. Need %d bytes at 0x%06X, "
1998 "region ends at 0x%06X",
2001 if (
static_cast<size_t>(write_pos) + required_size > rom_data.size()) {
2002 const int required_end = write_pos +
static_cast<int>(required_size);
2003 return absl::OutOfRangeError(
2004 absl::StrFormat(
"ROM too small for sprite relocation write (need "
2005 "end=0x%06X, size=0x%06X)",
2006 required_end,
static_cast<int>(rom_data.size())));
2009 std::vector<uint8_t> relocated;
2010 relocated.reserve(required_size);
2011 relocated.push_back(sort_mode);
2012 relocated.insert(relocated.end(), encoded_bytes.begin(), encoded_bytes.end());
2015 const uint32_t snes_addr =
PcToSnes(write_pos);
2016 const int ptr_off = sprite_pointer + (room_id * 2);
2020 return absl::OkStatus();
2024 if (
rom_ ==
nullptr) {
2025 return absl::InvalidArgumentError(
"ROM pointer is null");
2028 return absl::OkStatus();
2037 const auto& rom_data =
rom()->vector();
2039 GetObjectStreamInfo(rom_data,
room_id_));
2041 bool requires_copy_on_write =
false;
2042 if (layout !=
nullptr) {
2044 DungeonStreamRequiresCopyOnWrite(
2046 encoded_bytes.size() + 2u));
2048 const auto relocate = [&]() -> absl::Status {
2049 if (stream_info.address + 2 >
static_cast<int>(rom_data.size())) {
2050 return absl::OutOfRangeError(
"Object stream header is out of range");
2052 std::vector<uint8_t> replacement = {rom_data[stream_info.address],
2053 rom_data[stream_info.address + 1]};
2054 replacement.insert(replacement.end(), encoded_bytes.begin(),
2055 encoded_bytes.end());
2058 std::move(replacement)));
2060 return absl::OkStatus();
2062 if (stream_info.shared || requires_copy_on_write) {
2063 if (layout !=
nullptr) {
2066 return absl::FailedPreconditionError(absl::StrFormat(
2067 "Room %d object stream at PC 0x%06X is shared; repacking is required",
2070 if (stream_info.capacity() <= 2) {
2071 if (layout !=
nullptr) {
2074 return absl::FailedPreconditionError(absl::StrFormat(
2075 "Room %d object stream has no safe physical boundary",
room_id_));
2079 const int write_pos = stream_info.address + 2;
2082 const int available_payload_size = stream_info.capacity() - 2;
2086 if (encoded_bytes.size() >
static_cast<size_t>(available_payload_size)) {
2087 if (layout !=
nullptr) {
2090 return absl::ResourceExhaustedError(absl::StrFormat(
2091 "Room %d object data too large! Size: %d, Available: %d",
room_id_,
2092 static_cast<int>(encoded_bytes.size()), available_payload_size));
2095 const int door_list_offset =
static_cast<int>(encoded_bytes.size()) -
2096 static_cast<int>(
doors_.size()) * 2 - 2;
2097 if (door_list_offset < 0) {
2098 return absl::FailedPreconditionError(
"Invalid encoded door list offset");
2101 if (door_pointer_slot < 0 ||
2102 door_pointer_slot + 2 >=
static_cast<int>(rom_data.size())) {
2103 return absl::OutOfRangeError(
"Door pointer slot is out of range");
2105 const int door_pointer_pc = write_pos + door_list_offset;
2112 door_pointer_slot,
static_cast<uint32_t
>(
PcToSnes(door_pointer_pc))));
2116 return absl::OkStatus();
2120 if (
rom_ ==
nullptr) {
2121 return absl::InvalidArgumentError(
"ROM pointer is null");
2124 return absl::OkStatus();
2127 return absl::InvalidArgumentError(
2128 "Dungeon floor graphics values must be in range 0..15");
2131 return absl::InvalidArgumentError(
2132 "Dungeon layout ID must be in range 0..7");
2137 GetObjectStreamInfo(rom_data,
room_id_));
2138 if (stream_info.address < 0 ||
2139 stream_info.address + 1 >=
static_cast<int>(rom_data.size())) {
2140 return absl::OutOfRangeError(
"Object stream header is out of range");
2144 auto patch_header = [&](std::vector<uint8_t>* stream) -> absl::Status {
2145 if (stream ==
nullptr || stream->size() < 2) {
2146 return absl::DataLossError(
2147 "Object stream is missing its two-byte header");
2150 (*stream)[0] =
static_cast<uint8_t
>(((*stream)[0] & 0xF0) |
2158 (*stream)[1] =
static_cast<uint8_t
>(((*stream)[1] & 0xE3) |
2161 return absl::OkStatus();
2164 bool requires_copy_on_write = stream_info.shared;
2165 std::vector<uint8_t> replacement;
2166 if (layout !=
nullptr) {
2168 return absl::InvalidArgumentError(
2169 "Object-stream header save requires an object stream layout");
2173 if (!inventory.
ok()) {
2174 return absl::FailedPreconditionError(absl::StrFormat(
2175 "Dungeon stream inventory has %zu issue(s); refusing object "
2177 inventory.
issues.size()));
2181 return absl::OutOfRangeError(
2182 "Room ID is outside the dungeon stream layout");
2185 bool layout_requires_copy_on_write =
false;
2187 DungeonStreamRequiresCopyOnWrite(
2189 replacement.size()));
2190 requires_copy_on_write =
2191 requires_copy_on_write || layout_requires_copy_on_write;
2194 if (requires_copy_on_write) {
2195 if (layout ==
nullptr) {
2196 return absl::FailedPreconditionError(absl::StrFormat(
2197 "Room %d object stream at PC 0x%06X is shared; a copy-on-write "
2198 "manifest is required to save its header",
2204 std::move(replacement)));
2206 return absl::OkStatus();
2209 std::vector<uint8_t> header = {rom_data[stream_info.address],
2210 rom_data[stream_info.address + 1]};
2214 return absl::OkStatus();
2218 if (
rom_ ==
nullptr) {
2219 return absl::InvalidArgumentError(
"ROM pointer is null");
2222 return absl::OkStatus();
2225 const auto& rom_data =
rom()->vector();
2227 return absl::OutOfRangeError(
"Room ID out of range");
2231 GetSpriteStreamInfo(rom_data,
room_id_));
2233 bool requires_copy_on_write =
false;
2234 if (layout !=
nullptr) {
2236 DungeonStreamRequiresCopyOnWrite(
2238 encoded_bytes.size() + 1u));
2240 const auto relocate = [&]() -> absl::Status {
2241 std::vector<uint8_t> replacement = {rom_data[stream_info.address]};
2242 replacement.insert(replacement.end(), encoded_bytes.begin(),
2243 encoded_bytes.end());
2246 std::move(replacement)));
2248 return absl::OkStatus();
2250 if (stream_info.shared || requires_copy_on_write) {
2251 if (layout !=
nullptr) {
2254 return absl::FailedPreconditionError(absl::StrFormat(
2255 "Room %d sprite stream at PC 0x%06X is shared; repacking is required",
2258 if (stream_info.capacity() <= 1) {
2259 if (layout !=
nullptr) {
2262 return absl::FailedPreconditionError(absl::StrFormat(
2263 "Room %d sprite stream has no safe physical boundary",
room_id_));
2266 const int available_payload_size = stream_info.capacity() - 1;
2267 const int payload_address = stream_info.address + 1;
2268 if (payload_address < 0 ||
2269 payload_address >=
static_cast<int>(
rom_->
size())) {
2270 return absl::OutOfRangeError(absl::StrFormat(
2271 "Room %d has invalid sprite payload address",
room_id_));
2274 if (
static_cast<int>(encoded_bytes.size()) > available_payload_size) {
2275 if (layout !=
nullptr) {
2278 return absl::ResourceExhaustedError(absl::StrFormat(
2279 "Room %d sprite data too large! Size: %d, Available: %d; repacking "
2281 room_id_,
static_cast<int>(encoded_bytes.size()),
2282 available_payload_size));
2287 return absl::OkStatus();
2291 if (
rom_ ==
nullptr) {
2292 return absl::InvalidArgumentError(
"ROM pointer is null");
2295 const auto& rom_data =
rom()->vector();
2298 return absl::OutOfRangeError(
"Room header pointer out of range");
2302 return absl::OutOfRangeError(
"Room header pointer bank out of range");
2308 header_pointer =
SnesToPc(header_pointer);
2310 int table_offset = header_pointer + (
room_id_ * 2);
2311 if (table_offset < 0 ||
2312 table_offset + 1 >=
static_cast<int>(rom_data.size())) {
2313 return absl::OutOfRangeError(
"Room header table offset out of range");
2317 (rom_data[table_offset + 1] << 8) + rom_data[table_offset];
2318 int header_location =
SnesToPc(address);
2320 if (header_location < 0 ||
2321 header_location + 13 >=
static_cast<int>(rom_data.size())) {
2322 return absl::OutOfRangeError(
"Room header location out of range");
2329 if (
bg2() != background2::DarkRoom) {
2330 layer2_mode_for_save =
static_cast<uint8_t
>(
bg2()) & 0x07;
2332 const bool dark_room =
2334 uint8_t byte0 =
static_cast<uint8_t
>(
2335 (layer2_mode_for_save << 5) |
2336 ((
static_cast<uint8_t
>(
collision()) & 0x07) << 2) |
2337 (rom_data[header_location] & 0x02) | (dark_room ? 1 : 0));
2345 const uint8_t byte8 =
static_cast<uint8_t
>(
2346 (rom_data[header_location + 8] & 0xFC) | (
staircase_plane(3) & 0x03));
2367 if (msg_addr < 0 || msg_addr + 1 >=
static_cast<int>(rom_data.size())) {
2368 return absl::OutOfRangeError(
"Message ID address out of range");
2374 return absl::OkStatus();
2384 return absl::InvalidArgumentError(
"Invalid object parameters");
2392 return absl::OkStatus();
2397 return absl::OutOfRangeError(
"Object index out of range");
2405 return absl::OkStatus();
2410 return absl::OutOfRangeError(
"Object index out of range");
2414 return absl::InvalidArgumentError(
"Invalid object parameters");
2422 return absl::OkStatus();
2428 if (obj.x() == x && obj.y() == y && obj.GetLayerValue() == layer) {
2432 return absl::NotFoundError(
"No object found at position");
2437 if (
object.x() < 0 ||
object.x() > 63)
2439 if (
object.y() < 0 ||
object.y() > 63)
2443 if (
object.GetLayerValue() < 0 ||
object.GetLayerValue() > 2)
2447 if (
object.id_ < 0 || object.id_ > 0xFFF)
2451 if (
object.id_ < 0x100 &&
object.size() > 15)
2458 int& nbr_of_staircase) {
2462 if (nbr_of_staircase < 4) {
2485 }
else if (oid == 0xFB1) {
2495 const auto& rom_data =
rom()->vector();
2503 int sprite_pointer = 0;
2504 if (!GetSpritePointerTablePc(rom_data, &sprite_pointer).ok()) {
2508 int sprite_address =
2509 ReadRoomSpriteAddressPc(rom_data, sprite_pointer,
room_id_);
2510 if (sprite_address < 0 ||
2511 sprite_address + 1 >=
static_cast<int>(rom_data.size())) {
2516 sprite_address += 1;
2518 while (sprite_address + 2 <
static_cast<int>(rom_data.size())) {
2519 uint8_t b1 = rom_data[sprite_address];
2520 uint8_t b2 = rom_data[sprite_address + 1];
2521 uint8_t b3 = rom_data[sprite_address + 2];
2527 sprites_.emplace_back(b3, (b2 & 0x1F), (b1 & 0x1F),
2528 ((b2 & 0xE0) >> 5) + ((b1 & 0x60) >> 2),
2535 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1E &&
2541 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1D &&
2548 sprite_address += 3;
2560 const auto& rom_data =
rom()->vector();
2566 const int cpos =
static_cast<int>(
SnesToPc(
2570 const size_t byte_length =
2573 const size_t bounded_byte_length = std::min<size_t>(
2574 byte_length, cpos >= 0 && cpos <
static_cast<int>(rom_data.size())
2575 ? rom_data.size() -
static_cast<size_t>(cpos)
2577 const size_t record_count = std::min<size_t>(
2580 for (
size_t i = 0; i < record_count; ++i) {
2581 const size_t offset =
2583 if ((((rom_data[offset + 1] << 8) + rom_data[offset]) & 0x7FFF) ==
2587 if ((((rom_data[offset + 1] << 8) + rom_data[offset]) & 0x8000) ==
2599 auto rom_data =
rom()->vector();
2610 "LoadDoors for room %d - doors are loaded via object stream",
2615 auto rom_data =
rom()->vector();
2628 return (obj.options() & ObjectOption::Torch) !=
2629 ObjectOption::Nothing;
2634 for (
int i = 0; i < bytes_count; i += 2) {
2635 if (i + 1 >= bytes_count)
2642 if (b1 == 0xFF && b2 == 0xFF) {
2647 uint16_t torch_room_id = (b2 << 8) | b1;
2651 while (i < bytes_count) {
2652 if (i + 1 >= bytes_count)
2659 if (b1 == 0xFF && b2 == 0xFF) {
2667 torch_obj.SetRom(
rom_);
2669 torch_obj.set_torch_reserved_bit(entry.
reserved);
2670 torch_obj.lit_ = entry.
lit;
2675 "Room",
"Loaded torch at (%d,%d) draw_layer=%d reserved=%d lit=%d",
2684 while (i < bytes_count) {
2685 if (i + 1 >= bytes_count)
2689 if (b1 == 0xFF && b2 == 0xFF) {
2704 uint16_t room_id = 0;
2710 const std::vector<uint8_t>& rom_data,
int bytes_count) {
2711 std::vector<TorchSegment> segments;
2716 if (b1 == 0xFF && b2 == 0xFF) {
2722 padding.
bytes = {0xFF, 0xFF};
2723 segments.push_back(std::move(padding));
2727 uint16_t room_id = (b2 << 8) | b1;
2734 seg.
bytes.push_back(b1);
2735 seg.
bytes.push_back(b2);
2740 if (b1 == 0xFF && b2 == 0xFF) {
2741 seg.
bytes.push_back(0xFF);
2742 seg.
bytes.push_back(0xFF);
2746 seg.
bytes.push_back(b1);
2747 seg.
bytes.push_back(b2);
2750 segments.push_back(std::move(seg));
2756 std::vector<uint8_t> bytes;
2761 if (bytes.empty()) {
2762 bytes.push_back(room_id & 0xFF);
2763 bytes.push_back((room_id >> 8) & 0xFF);
2766 .px =
static_cast<uint8_t
>(obj.x()),
2767 .py =
static_cast<uint8_t
>(obj.y()),
2768 .draw_layer =
static_cast<uint8_t
>(obj.GetLayerValue() & 1),
2769 .reserved = obj.torch_reserved_bit(),
2772 bytes.push_back(encoded.
low);
2773 bytes.push_back(encoded.
high);
2775 if (!bytes.empty()) {
2776 bytes.push_back(0xFF);
2777 bytes.push_back(0xFF);
2784 const char* object_type) {
2785 const uint8_t selector =
object.GetLayerValue();
2786 if (selector <= 1) {
2787 return absl::OkStatus();
2789 return absl::InvalidArgumentError(absl::StrFormat(
2790 "%s in room 0x%03X has invalid special draw-layer selector %d; "
2792 "(upper/BG1) or 1 (lower/BG2)",
2793 object_type, room_id, selector));
2800 if (
object.x() <= 0x3E &&
object.y() <= 0x3E) {
2801 return absl::OkStatus();
2803 return absl::InvalidArgumentError(absl::StrFormat(
2804 "Torch in room 0x%03X has invalid position (%d,%d); expected x/y in "
2806 room_id,
object.x(),
object.y()));
2811template <
typename RoomLookup>
2813 RoomLookup&& room_lookup) {
2815 return absl::InvalidArgumentError(
"ROM not loaded");
2818 const auto& rom_data = rom->
vector();
2821 if (existing_count > kTorchesMaxSize) {
2822 existing_count = kTorchesMaxSize;
2824 auto rom_segments = ParseRomTorchSegments(rom_data, existing_count);
2826 std::vector<uint8_t> bytes;
2828 std::vector<bool> owned_rooms(room_limit,
false);
2829 std::vector<bool> seen_original_room(room_limit,
false);
2830 std::vector<bool> emitted_owned_room(room_limit,
false);
2831 std::vector<std::vector<uint8_t>> replacements(room_limit);
2832 bool any_owned_room =
false;
2833 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2834 const Room* room = room_lookup(room_id);
2835 const bool room_owned =
2845 owned_rooms[room_id] =
true;
2846 any_owned_room =
true;
2847 replacements[room_id] = EncodeTorchSegmentForRoom(room_id, *room);
2850 if (!any_owned_room) {
2851 return absl::OkStatus();
2854 for (
const auto& segment : rom_segments) {
2855 if (segment.room_id < room_limit) {
2856 seen_original_room[segment.room_id] =
true;
2857 if (owned_rooms[segment.room_id]) {
2858 if (!emitted_owned_room[segment.room_id]) {
2859 bytes.insert(bytes.end(), replacements[segment.room_id].begin(),
2860 replacements[segment.room_id].end());
2861 emitted_owned_room[segment.room_id] =
true;
2866 bytes.insert(bytes.end(), segment.bytes.begin(), segment.bytes.end());
2869 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2870 if (owned_rooms[room_id] && !seen_original_room[room_id] &&
2871 !replacements[room_id].empty()) {
2872 bytes.insert(bytes.end(), replacements[room_id].begin(),
2873 replacements[room_id].end());
2877 if (bytes.size() > kTorchesMaxSize) {
2878 return absl::ResourceExhaustedError(
2879 absl::StrFormat(
"Torch data too large: %d bytes (max %d)", bytes.size(),
2883 const uint16_t current_len =
2886 if (current_len == bytes.size() &&
2887 kTorchData +
static_cast<int>(bytes.size()) <=
2888 static_cast<int>(rom_data.size()) &&
2889 std::equal(bytes.begin(), bytes.end(), rom_data.begin() +
kTorchData)) {
2890 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2891 if (
const Room* room = room_lookup(room_id);
2893 const_cast<Room*
>(room)->ClearTorchesDirty();
2896 return absl::OkStatus();
2900 static_cast<uint16_t
>(bytes.size())));
2902 for (
int room_id = 0; room_id < room_limit; ++room_id) {
2903 if (
const Room* room = room_lookup(room_id);
2905 const_cast<Room*
>(room)->ClearTorchesDirty();
2908 return absl::OkStatus();
2913 [&rooms](
int room_id) { return &rooms[room_id]; });
2917 Rom* rom,
int room_count,
2918 const std::function<
const Room*(
int)>& room_lookup) {
2930 if (pit_damage_table !=
nullptr && pit_damage_table->
dirty()) {
2933 return absl::OkStatus();
2936 return absl::InvalidArgumentError(
"ROM not loaded");
2938 const auto& rom_data = rom->
vector();
2939 if (kPitCount < 0 || kPitCount >=
static_cast<int>(rom_data.size()) ||
2940 kPitPointer + 2 >=
static_cast<int>(rom_data.size())) {
2941 return absl::OutOfRangeError(
"Pit count/pointer out of range");
2947 int data_len = max_offset + 2;
2948 int pit_ptr_snes = (rom_data[
kPitPointer + 2] << 16) |
2950 int pit_data_pc =
SnesToPc(pit_ptr_snes);
2951 if (pit_data_pc < 0 ||
2952 pit_data_pc + data_len >
static_cast<int>(rom_data.size())) {
2953 return absl::OutOfRangeError(
"Pit data region out of range");
2955 std::vector<uint8_t> data(rom_data.begin() + pit_data_pc,
2956 rom_data.begin() + pit_data_pc + data_len);
2972 return first_begin < second_end && second_begin < first_end;
2976 const std::vector<uint8_t>& rom_data,
int operand_pc) {
2977 if (operand_pc <= 0 || operand_pc + 5 >=
static_cast<int>(rom_data.size())) {
2978 return absl::OutOfRangeError(
"Blocks pointer operand out of range");
2992 if (rom_data[operand_pc - 1] != 0xBF || rom_data[operand_pc + 3] != 0x9D) {
2993 return absl::FailedPreconditionError(absl::StrFormat(
2994 "Blocks pointer operand at PC 0x%05X is not in the expected "
2995 "LDA.l ...,X / STA.w loader sequence",
2998 return absl::OkStatus();
3002 const std::vector<uint8_t>& rom_data, std::array<int, 4>* destination_pcs) {
3005 return absl::OutOfRangeError(
"Blocks length out of range");
3011 const int snes = (rom_data[operand_pc + 2] << 16) |
3012 (rom_data[operand_pc + 1] << 8) | rom_data[operand_pc];
3013 const int data_pc =
SnesToPc(snes);
3016 return absl::OutOfRangeError(absl::StrFormat(
3017 "Blocks data region out of range for loader page %d", page + 1));
3019 (*destination_pcs)[page] = data_pc;
3023 for (
size_t page = 0; page < destination_pcs->size(); ++page) {
3024 const int page_begin = (*destination_pcs)[page];
3027 kLengthMetadataEnd)) {
3028 return absl::FailedPreconditionError(absl::StrFormat(
3029 "Blocks data page %d at PC [0x%05X, 0x%05X) overlaps block-table "
3030 "length metadata [0x%05X, 0x%05X)",
3031 page + 1, page_begin, page_end,
kBlocksLength, kLengthMetadataEnd));
3042 return absl::FailedPreconditionError(absl::StrFormat(
3043 "Blocks data page %d at PC [0x%05X, 0x%05X) overlaps loader %d "
3044 "opcode/operand metadata [0x%05X, 0x%05X)",
3045 page + 1, page_begin, page_end, loader + 1, loader_begin,
3050 for (
size_t previous = 0; previous < page; ++previous) {
3051 const int previous_begin = (*destination_pcs)[previous];
3055 return absl::FailedPreconditionError(absl::StrFormat(
3056 "Blocks data pages %d and %d overlap at PC ranges [0x%05X, "
3057 "0x%05X) and [0x%05X, 0x%05X)",
3058 previous + 1, page + 1, previous_begin, previous_end, page_begin,
3064 return absl::OkStatus();
3071 return absl::InvalidArgumentError(
"ROM not loaded");
3073 const auto& rom_data = rom->
vector();
3074 if (
kBlocksLength + 1 >=
static_cast<int>(rom_data.size())) {
3075 return absl::OutOfRangeError(
"Blocks length out of range");
3079 std::array<int, 4> destination_pcs{};
3081 PreflightBlocksLoaderDestinations(rom_data, &destination_pcs));
3082 if (blocks_count <= 0) {
3083 return absl::OkStatus();
3085 for (
int r = 0; r < 4; ++r) {
3086 const int pc = destination_pcs[r];
3087 int off = r * kBlocksRegionSize;
3088 int len = std::min(kBlocksRegionSize, blocks_count - off);
3091 std::vector<uint8_t> chunk(rom_data.begin() + pc,
3092 rom_data.begin() + pc + len);
3097 return absl::OkStatus();
3101 const std::function<
const Room*(
int)>& room_lookup) {
3103 return absl::InvalidArgumentError(
"ROM not loaded");
3105 const auto& rom_data = rom->
vector();
3106 if (
kBlocksLength + 1 >=
static_cast<int>(rom_data.size())) {
3107 return absl::OutOfRangeError(
"Blocks length out of range");
3110 std::array<int, 4> destination_pcs{};
3112 PreflightBlocksLoaderDestinations(rom_data, &destination_pcs));
3120 const int original_count_word =
3122 const int original_byte_len = std::max(0, original_count_word);
3123 std::vector<uint8_t> original_buffer(original_byte_len, 0);
3124 for (
int r = 0; r < 4; ++r) {
3125 const int pc = destination_pcs[r];
3126 const int off = r * kBlocksRegionSize;
3127 const int len = std::min(kBlocksRegionSize, original_byte_len - off);
3130 std::copy_n(rom_data.begin() + pc, len, original_buffer.begin() + off);
3132 const int original_slot_count = original_byte_len / 4;
3143 struct EncodedBlock {
3147 std::unordered_set<uint16_t> owned_room_ids;
3148 std::unordered_set<int> claimed_load_orders;
3149 std::unordered_map<int, EncodedBlock> slot_replacements;
3150 std::vector<EncodedBlock> appended;
3151 for (
int rid = 0; rid < room_count; ++rid) {
3152 const Room* room = room_lookup(rid);
3153 if (room ==
nullptr)
3157 return absl::FailedPreconditionError(absl::StrFormat(
3158 "Room 0x%03X has unsaved pushable-block edits, but its block "
3159 "table is not loaded. Load the room's blocks before saving.",
3164 owned_room_ids.insert(
static_cast<uint16_t
>(rid));
3169 ValidateSpecialObjectDrawLayerSelector(obj, rid,
"Pushable block"));
3171 encoded_entry.
room_id =
static_cast<uint16_t
>(rid);
3172 encoded_entry.
px = obj.x();
3173 encoded_entry.
py = obj.y();
3174 encoded_entry.
draw_layer = obj.GetLayerValue();
3178 const EncodedBlock encoded_block{encoded, &obj};
3179 const int load_order = obj.block_load_order();
3180 if (load_order >= 0 && !claimed_load_orders.insert(load_order).second) {
3181 return absl::FailedPreconditionError(absl::StrFormat(
3182 "Room 0x%03X has multiple pushable blocks claiming non-new "
3183 "load-order slot %d",
3187 appended.push_back(encoded_block);
3188 }
else if (load_order >= 0 && load_order < original_slot_count) {
3189 const int original_offset = load_order * 4;
3190 const uint16_t original_room_id =
3191 static_cast<uint16_t
>(original_buffer[original_offset] |
3192 (original_buffer[original_offset + 1] << 8));
3193 if (original_room_id !=
static_cast<uint16_t
>(rid)) {
3198 appended.push_back(encoded_block);
3201 slot_replacements.emplace(load_order, encoded_block);
3205 appended.push_back(encoded_block);
3212 std::vector<uint8_t> output;
3213 output.reserve(original_byte_len + appended.size() * 4);
3214 std::vector<std::pair<const RoomObject*, int>> load_order_updates;
3215 load_order_updates.reserve(slot_replacements.size() + appended.size());
3216 const auto append_encoded_block =
3217 [&output, &load_order_updates](
const EncodedBlock& block) {
3218 const int output_slot =
static_cast<int>(output.size() / 4);
3219 output.push_back(block.bytes.b1);
3220 output.push_back(block.bytes.b2);
3221 output.push_back(block.bytes.b3);
3222 output.push_back(block.bytes.b4);
3223 load_order_updates.emplace_back(block.source_object, output_slot);
3225 for (
int slot = 0; slot < original_slot_count; ++slot) {
3226 const uint8_t b1 = original_buffer[slot * 4 + 0];
3227 const uint8_t b2 = original_buffer[slot * 4 + 1];
3228 const uint16_t slot_room_id =
static_cast<uint16_t
>(b1 | (b2 << 8));
3229 if (owned_room_ids.contains(slot_room_id)) {
3230 const auto it = slot_replacements.find(slot);
3231 if (it == slot_replacements.end()) {
3236 append_encoded_block(it->second);
3239 output.push_back(b1);
3240 output.push_back(b2);
3241 output.push_back(original_buffer[slot * 4 + 2]);
3242 output.push_back(original_buffer[slot * 4 + 3]);
3248 for (
const auto& block : appended) {
3249 append_encoded_block(block);
3258 if (output.empty()) {
3259 return absl::FailedPreconditionError(
3260 "Pushable-block table cannot be empty: ALTTP's runtime scan reads one "
3261 "entry before comparing the byte-length limit. Keep at least one "
3262 "pushable block, or patch the runtime loop before removing the last "
3267 const int kMaxEntries = (4 * kBlocksRegionSize) / 4;
3268 if (
static_cast<int>(output.size() / 4) > kMaxEntries) {
3269 return absl::FailedPreconditionError(absl::StrCat(
3270 "Pushable-block table overflow: ", output.size() / 4,
3271 " entries exceeds the vanilla cap of ", kMaxEntries,
3272 " (expand layout requires repointing all 4 LDA.l operand slots; "
3273 "out of scope for this encoder)."));
3280 const int total_bytes =
static_cast<int>(output.size());
3281 struct BlockWriteDestination {
3286 std::vector<BlockWriteDestination> write_destinations;
3287 write_destinations.reserve(4);
3288 for (
int r = 0; r < 4; ++r) {
3289 const int off = r * kBlocksRegionSize;
3290 const int len = std::min(kBlocksRegionSize, total_bytes - off);
3293 write_destinations.push_back({destination_pcs[r], off, len});
3298 for (
const auto&
destination : write_destinations) {
3299 std::vector<uint8_t> chunk(
3313 for (
const auto& [
object, load_order] : load_order_updates) {
3314 const_cast<RoomObject*
>(object)->set_block_load_order(load_order);
3316 for (
int room_id = 0; room_id < room_count; ++room_id) {
3317 if (
const Room* room = room_lookup(room_id);
3319 const_cast<Room*
>(room)->ClearBlocksDirty();
3322 return absl::OkStatus();
3325template <
typename RoomLookup>
3327 RoomLookup&& room_lookup) {
3329 return absl::InvalidArgumentError(
"ROM not loaded");
3336 const auto& rom_data = rom->
vector();
3341 if (!has_ptr_table) {
3342 for (
int room_id = 0; room_id < room_count; ++room_id) {
3343 const Room* room = room_lookup(room_id);
3345 return absl::FailedPreconditionError(
3346 "Custom collision region not present in this ROM");
3349 return absl::OkStatus();
3352 if (!has_data_region) {
3353 for (
int room_id = 0; room_id < room_count; ++room_id) {
3354 const Room* room = room_lookup(room_id);
3356 return absl::FailedPreconditionError(
3357 "Custom collision data region not present in this ROM");
3360 return absl::OkStatus();
3369 "CustomCollisionPointers"));
3373 "CustomCollisionData"));
3377 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3378 const Room* room = room_lookup(room_id);
3383 const int actual_room_id = room->
id();
3385 if (ptr_offset + 2 >=
static_cast<int>(rom_data.size())) {
3386 return absl::OutOfRangeError(
"Custom collision pointer out of range");
3394 const_cast<Room*
>(room)->ClearCustomCollisionDirty();
3410 const_cast<Room*
>(room)->ClearCustomCollisionDirty();
3416 const_cast<Room*
>(room)->ClearCustomCollisionDirty();
3419 return absl::OkStatus();
3424 rom,
static_cast<int>(rooms.size()),
3425 [&rooms](
int room_id) { return &rooms[room_id]; });
3429 const std::function<
Room*(
int)>& room_lookup) {
3433absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
3435 if (rom ==
nullptr || !rom->
is_loaded()) {
3436 return absl::InvalidArgumentError(
"ROM not loaded");
3438 const auto& rom_data = rom->
vector();
3441 return absl::OutOfRangeError(
"Chest pointers out of range");
3444 const uint32_t data_pointer =
3448 const uint32_t data_pc =
SnesToPc(data_pointer);
3449 if (data_pc > rom_data.size() ||
3451 rom_data.size() -
static_cast<size_t>(data_pc)) {
3452 return absl::OutOfRangeError(
"Chest data region out of range");
3454 const uint32_t data_end =
3456 const auto overlaps = [](uint32_t begin, uint32_t end, uint32_t other_begin,
3457 uint32_t other_end) {
3458 return begin < other_end && other_begin < end;
3464 return absl::FailedPreconditionError(
3465 "Chest data region overlaps chest metadata operands");
3468 return std::vector<std::pair<uint32_t, uint32_t>>{
3471 {data_pc, data_end},
3481 uint16_t
room_id()
const {
return word & 0x7FFF; }
3487 const std::vector<uint8_t>& rom_data,
int cpos,
int byte_length) {
3488 std::vector<PhysicalChestRecord> records;
3490 records.reserve(record_count);
3491 for (
int i = 0; i < record_count; ++i) {
3497 const uint16_t word =
3498 (
static_cast<uint16_t
>(rom_data[off + 1]) << 8) | rom_data[off];
3506 bytes->push_back(word & 0xFF);
3507 bytes->push_back((word >> 8) & 0xFF);
3508 bytes->push_back(item);
3513 const uint16_t word =
static_cast<uint16_t
>(room_id) |
3514 (
chest.size ?
static_cast<uint16_t
>(0x8000) : 0);
3519 absl::Span<const uint8_t> replacement,
3520 std::vector<ChestWriteRun>* writes) {
3522 while (cursor < replacement.size()) {
3523 if (replacement[cursor] == expected[cursor]) {
3527 const size_t begin = cursor;
3530 }
while (cursor < replacement.size() &&
3531 replacement[cursor] != expected[cursor]);
3534 run.
pc = pc +
static_cast<uint32_t
>(begin);
3536 expected.begin() + cursor);
3538 replacement.begin() + cursor);
3539 if (!writes->empty() && writes->back().end() == run.
pc) {
3540 writes->back().expected_bytes.insert(writes->back().expected_bytes.end(),
3543 writes->back().replacement_bytes.insert(
3547 writes->push_back(std::move(run));
3553 std::sort(writes->begin(), writes->end(),
3555 return lhs.pc < rhs.pc;
3557 std::vector<ChestWriteRun> merged;
3558 merged.reserve(writes->size());
3560 if (!merged.empty() && merged.back().end() == write.pc) {
3561 merged.back().expected_bytes.insert(merged.back().expected_bytes.end(),
3562 write.expected_bytes.begin(),
3563 write.expected_bytes.end());
3564 merged.back().replacement_bytes.insert(
3565 merged.back().replacement_bytes.end(),
3566 write.replacement_bytes.begin(), write.replacement_bytes.end());
3569 merged.push_back(std::move(write));
3571 *writes = std::move(merged);
3575 std::vector<uint8_t> bytes;
3584 const Rom* rom,
int room_count,
3585 const std::function<
const Room*(
int)>& room_lookup) {
3586 if (rom ==
nullptr || !rom->
is_loaded()) {
3587 return absl::InvalidArgumentError(
"ROM not loaded");
3589 const auto& rom_data = rom->
vector();
3592 return absl::OutOfRangeError(
"Chest pointers out of range");
3598 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
3599 const Room* room = room_lookup(room_id);
3601 dirty_rooms[room_id] = room;
3612 plan.
data_pc = potential_ranges[1].first;
3619 rom_data.begin() + plan.
data_pc,
3623 return absl::FailedPreconditionError(absl::StrFormat(
3624 "Chest table byte length %d is invalid (capacity %d)",
3630 if (physical_records.size() !=
3632 return absl::OutOfRangeError(
"Chest data region is truncated");
3638 ++old_counts[record.room_id()];
3642 size_t final_record_count = physical_records.size();
3643 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
3644 if (dirty_rooms[room_id] ==
nullptr) {
3647 final_record_count -= old_counts[room_id];
3648 final_record_count += dirty_rooms[room_id]->GetChests().size();
3651 return absl::ResourceExhaustedError(absl::StrFormat(
3652 "Chest table has %d records; capacity is %d",
3656 std::vector<uint8_t> replacement_bytes;
3660 const uint16_t room_id = record.room_id();
3661 const Room* dirty_room =
3663 if (dirty_room ==
nullptr) {
3668 const size_t occurrence = seen_counts[room_id]++;
3669 const auto& replacements = dirty_room->
GetChests();
3670 if (occurrence < replacements.size()) {
3672 replacements[occurrence]);
3679 for (
int room_id = 0; room_id < plan.
room_limit; ++room_id) {
3680 const Room* dirty_room = dirty_rooms[room_id];
3681 if (dirty_room ==
nullptr) {
3684 const auto& replacements = dirty_room->
GetChests();
3685 for (
size_t i = seen_counts[room_id]; i < replacements.size(); ++i) {
3691 return absl::InternalError(
"Chest save plan size mismatch");
3694 const std::array<uint8_t, 2> expected_length = {
3697 const uint16_t replacement_length =
3698 static_cast<uint16_t
>(replacement_bytes.size());
3699 const std::array<uint8_t, 2> encoded_replacement_length = {
3700 static_cast<uint8_t
>(replacement_length & 0xFF),
3701 static_cast<uint8_t
>((replacement_length >> 8) & 0xFF)};
3703 encoded_replacement_length, &plan.
writes);
3706 .subspan(0, replacement_bytes.size()),
3707 replacement_bytes, &plan.
writes);
3718 if (ptr_off < 0 || ptr_off + 1 >=
static_cast<int>(rom_data.size())) {
3721 const uint16_t item_ptr =
3722 (
static_cast<uint16_t
>(rom_data[ptr_off + 1]) << 8) | rom_data[ptr_off];
3723 if (item_ptr < 0x8000) {
3726 const int item_addr =
static_cast<int>(
SnesToPc(0x010000 | item_ptr));
3727 return item_addr >= 0 && item_addr < static_cast<int>(rom_data.size())
3733 const std::vector<uint8_t>& rom_data,
int room_id) {
3735 static_cast<int>(rom_data.size())) {
3736 return absl::OutOfRangeError(
"Room items pointer table out of range");
3739 return absl::OutOfRangeError(
"Room ID out of range");
3746 const int hard_end =
3750 return absl::FailedPreconditionError(
3751 "Room pot item pointer is null, invalid, or outside the item region");
3759 const Rom* rom,
int room_count,
3760 const std::function<
const Room*(
int)>& room_lookup) {
3761 return BuildChestSavePlanImpl(rom, room_count, room_lookup);
3764absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
3766 const std::function<
const Room*(
int)>& room_lookup) {
3774 const std::function<
const Room*(
int)>& room_lookup) {
3775 if (rom ==
nullptr || !rom->
is_loaded()) {
3776 return absl::InvalidArgumentError(
"ROM not loaded");
3781 if (canonical_plan != plan) {
3782 return absl::FailedPreconditionError(
3783 "Chest save plan does not match canonical serialization");
3785 return absl::OkStatus();
3788 const auto& rom_data = rom->
vector();
3791 plan.
data_pc > rom_data.size() ||
3793 return absl::FailedPreconditionError(
3794 "Chest save plan source is no longer addressable");
3798 return absl::FailedPreconditionError(
3799 "Chest save plan is stale: data pointer changed");
3801 const uint16_t current_length =
3805 return absl::FailedPreconditionError(
3806 "Chest save plan is stale: runtime length changed");
3810 rom_data.begin() + plan.
data_pc)) {
3811 return absl::FailedPreconditionError(
3812 "Chest save plan is stale: table bytes changed");
3815 const Room* room = room_lookup(state.room_id);
3817 EncodeChestRoomState(state.room_id, *room) != state.encoded_chests) {
3818 return absl::FailedPreconditionError(absl::StrFormat(
3819 "Chest save plan is stale for room 0x%03X", state.room_id));
3824 if (canonical_plan != plan) {
3825 return absl::FailedPreconditionError(
3826 "Chest save plan does not match canonical serialization");
3839 const_cast<Room*
>(room_lookup(state.room_id))->ClearChestsDirty();
3842 return absl::OkStatus();
3845template <
typename RoomLookup>
3847 RoomLookup&& room_lookup) {
3848 const std::function<
const Room*(int)> lookup =
3849 std::forward<RoomLookup>(room_lookup);
3857 [&rooms](
int room_id) { return &rooms[room_id]; });
3861 const std::function<
const Room*(
int)>& room_lookup) {
3865template <
typename RoomLookup>
3867 Rom* rom,
int room_count, RoomLookup&& room_lookup,
3870 return absl::InvalidArgumentError(
"ROM not loaded");
3872 const auto& rom_data = rom->
vector();
3874 static_cast<int>(rom_data.size())) {
3875 return absl::OutOfRangeError(
"Room items pointer table out of range");
3879 if (repack_layout !=
nullptr) {
3880 std::vector<DungeonStreamReplacement> replacements;
3881 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3882 const Room* room = room_lookup(room_id);
3888 replacement.
room_id =
static_cast<uint32_t
>(room_id);
3892 replacement.
encoded_stream.push_back((item.position >> 8) & 0xFF);
3897 replacements.push_back(std::move(replacement));
3899 if (replacements.empty()) {
3900 return absl::OkStatus();
3909 if (
const Room* room = room_lookup(replacement.room_id);
3911 const_cast<Room*
>(room)->ClearPotItemsDirty();
3914 return absl::OkStatus();
3917 struct PendingPotItemWrite {
3920 std::vector<uint8_t> bytes;
3925 std::vector<PendingPotItemWrite> pending_writes;
3926 for (
int room_id = 0; room_id < room_limit; ++room_id) {
3927 const Room* room = room_lookup(room_id);
3933 GetPotItemStreamInfo(rom_data, room_id));
3934 if (stream_info.shared) {
3935 return absl::FailedPreconditionError(absl::StrFormat(
3936 "Room %d pot item stream at PC 0x%06X is shared; repacking is "
3938 room_id, stream_info.address));
3940 if (stream_info.capacity() <= 0) {
3941 return absl::FailedPreconditionError(absl::StrFormat(
3942 "Room %d pot item stream has no safe physical boundary", room_id));
3945 PendingPotItemWrite pending;
3946 pending.room_id = room_id;
3947 pending.address = stream_info.address;
3949 pending.bytes.push_back(pi.position & 0xFF);
3950 pending.bytes.push_back((pi.position >> 8) & 0xFF);
3951 pending.bytes.push_back(pi.item);
3953 pending.bytes.push_back(0xFF);
3954 pending.bytes.push_back(0xFF);
3955 if (
static_cast<int>(pending.bytes.size()) > stream_info.capacity()) {
3956 return absl::ResourceExhaustedError(absl::StrFormat(
3957 "Room %d pot item data too large! Size: %d, Available: %d", room_id,
3958 static_cast<int>(pending.bytes.size()), stream_info.capacity()));
3960 pending_writes.push_back(std::move(pending));
3963 for (
const auto& pending : pending_writes) {
3964 const bool data_changed =
3965 !std::equal(pending.bytes.begin(), pending.bytes.end(),
3966 rom_data.begin() + pending.address);
3972 for (
const auto& pending : pending_writes) {
3973 if (
const Room* room = room_lookup(pending.room_id); room !=
nullptr) {
3974 const_cast<Room*
>(room)->ClearPotItemsDirty();
3977 return absl::OkStatus();
3982 [&rooms](
int room_id) { return &rooms[room_id]; });
3988 rom,
static_cast<int>(rooms.size()),
3989 [&rooms](
int room_id) { return &rooms[room_id]; }, repack_layout);
3993 Rom* rom,
int room_count,
3994 const std::function<
const Room*(
int)>& room_lookup) {
3999 const std::function<
const Room*(
int)>& room_lookup,
4005 auto rom_data =
rom()->vector();
4026 const int kRegionSize = 0x80;
4029 std::vector<uint8_t> blocks_data(blocks_count, 0);
4030 for (
int r = 0; r < 4; ++r) {
4031 const int slot = kPointerSlots[r];
4032 if (slot + 2 >=
static_cast<int>(rom_data.size())) {
4033 LOG_WARN(
"Room",
"LoadBlocks: pointer slot %d out of range", r);
4036 const absl::Status operand_status =
4037 ValidateBlocksLoaderPointerOperand(rom_data, slot);
4038 if (!operand_status.ok()) {
4040 std::string(operand_status.message()).c_str());
4044 (rom_data[slot + 2] << 16) | (rom_data[slot + 1] << 8) | rom_data[slot];
4046 const int off = r * kRegionSize;
4047 const int len = std::min(kRegionSize, blocks_count - off);
4050 if (pc < 0 || pc + len >
static_cast<int>(rom_data.size())) {
4051 LOG_WARN(
"Room",
"LoadBlocks: region %d data out of range", r);
4054 std::copy_n(rom_data.begin() + pc, len, blocks_data.begin() + off);
4063 return (obj.options() & ObjectOption::Block) !=
4064 ObjectOption::Nothing;
4076 for (
int i = 0; i + 3 < blocks_count; i += 4) {
4078 blocks_data[i + 2], blocks_data[i + 3]};
4094 LOG_DEBUG(
"Room",
"Loaded block at (%d,%d) draw_layer=%d behavior_layer=%d",
4103 auto rom_data =
rom()->vector();
4119 int ptr_addr = table_addr + (
room_id_ * 2);
4120 if (ptr_addr + 1 >=
static_cast<int>(rom_data.size()))
4123 uint16_t item_ptr = (rom_data[ptr_addr + 1] << 8) | rom_data[ptr_addr];
4126 int item_addr =
SnesToPc(0x010000 | item_ptr);
4129 while (item_addr + 2 <
static_cast<int>(rom_data.size())) {
4131 uint16_t position = (rom_data[item_addr + 1] << 8) | rom_data[item_addr];
4134 if (position == 0xFFFF)
4138 uint8_t item_type = rom_data[item_addr + 2];
4142 pot_item.
item = item_type;
4152 auto rom_data =
rom()->vector();
4163 const int max_offset = rom_data[
kPitCount];
4164 const int pit_entries = max_offset / 2 + 1;
4166 const int pit_ptr = (rom_data[
kPitPointer + 2] << 16) |
4170 "LoadPits: room_id=%d, RoomsWithPitDamage entries=%d, "
4171 "table_snes=0x%06X",
4180 LOG_DEBUG(
"Room",
"Per-room pit destination: target=%d, target_layer=%d",
4195 for (
const auto& sprite :
sprites_) {
4196 if (sprite.IsOverlord()) {
4206 for (
const auto& door :
doors_) {
4208 const bool is_special = [&]() ->
bool {
4209 switch (door.type) {
4240 auto options = obj.options();
4253 if (obj.id_ == 0x11E || obj.id_ == 0x11F) {
4258 if (obj.id_ >= 0xF83 && obj.id_ <= 0xF8F) {
4265 if ((obj.id_ >= 0x130 && obj.id_ <= 0x135) || obj.id_ == 0x139 ||
4266 obj.id_ == 0x13A || obj.id_ == 0x13B) {
4270 else if (obj.id_ >= 0x13C && obj.id_ <= 0x13F) {
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::Status WriteByte(int addr, uint8_t value)
const auto & vector() const
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
absl::StatusOr< uint16_t > ReadWord(int offset) const
absl::Status WriteWord(int addr, uint16_t value)
absl::Status WriteLong(uint32_t addr, uint32_t value)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
void ClearBG1RevealMask()
void DrawBackground(std::span< uint8_t > gfx16_data)
void DrawFloor(const std::vector< uint8_t > &rom_data, int tile_address, int tile_address_floor, uint8_t floor_graphics)
void EnsureBitmapInitialized()
void ClearCoverageBuffer()
void ClearPriorityBuffer()
Represents a bitmap image optimized for SNES ROM hacking.
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
constexpr uint16_t snes() const
Get SNES 15-bit color.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
absl::Status Allow(uint32_t start, uint32_t end, std::string_view label)
Editor implementation of DungeonState.
Draws dungeon objects to background buffers using game patterns.
void SetBG1RevealMaskSource(gfx::BG1RevealMaskSource source)
void DrawDoor(const DoorDef &door, int door_index, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const DungeonState *state=nullptr)
Draw a door to background buffers.
void DrawPotItem(uint8_t item_id, int x, int y, gfx::BackgroundBuffer &bg)
Draw a pot item visualization.
absl::Status DrawObjectList(const std::vector< RoomObject > &objects, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr, bool reset_room_event_indices=true)
Draw all objects in a room.
absl::Status DrawRoomDrawObjectData2x2(uint16_t object_id, int tile_x, int tile_y, RoomObject::LayerType layer, uint16_t room_draw_object_data_offset, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2)
Draw a fixed 2x2 (16x16) tile pattern from RoomDrawObjectData.
void SetAllowTrackCornerAliases(bool allow)
void SetCurrentBitmap(gfx::Bitmap *bitmap)
void LogPaletteLoad(const std::string &location, int palette_id, const gfx::SnesPalette &palette)
static PaletteDebugger & Get()
void LogPaletteApplication(const std::string &location, int palette_id, bool success, const std::string &reason="")
void SetCurrentPalette(const gfx::SnesPalette &palette)
void LogSurfaceState(const std::string &location, SDL_Surface *surface)
void SetCurrentRenderPalette(const std::vector< SDL_Color > &palette)
absl::Status SaveToRom(Rom *rom) const
RoomLayerManager - Manages layer visibility and compositing.
void CompositeToOutput(Room &room, gfx::Bitmap &output) const
Composite all visible layers into a single output bitmap.
uint64_t CompositeStateSignature() const
const std::vector< RoomObject > & GetObjects() const
absl::Status Draw(int room_id, const uint8_t *gfx_data, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, DungeonState *state) const
absl::Status LoadLayout(int layout_id)
static RoomObject DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t layer)
void set_block_behavior_layer(uint8_t layer)
static constexpr int kBlockLoadOrderNew
void set_block_load_order(int order)
void set_options(ObjectOption options)
bool ValidateObject(const RoomObject &object) const
SaveDirtyState save_dirty_state_
std::vector< RoomObject > tile_objects_
gfx::BackgroundBuffer object_bg1_buffer_
void SetTag2Direct(TagKey tag2)
bool HasExceededLimits() const
Check if any object limits are exceeded.
void ClearObjectStreamHeaderDirty()
uint8_t render_entrance_blockset_
const CustomCollisionMap & custom_collision() const
absl::Status UpdateObject(size_t index, const RoomObject &object)
void ClearSaveDirtyState()
void SetStair4Target(uint8_t target)
void SetPitsTarget(uint8_t target)
void SetIsLight(bool is_light)
void EnsureSpritesLoaded()
gfx::BackgroundBuffer bg2_buffer_
uint8_t resolved_main_blockset_
const std::vector< chest_data > & GetChests() const
uint8_t cached_floor2_graphics_
std::vector< zelda3::Sprite > sprites_
CustomCollisionMap custom_collision_
void MarkSaveDirtyForTileObject(const RoomObject &object)
void SetLoaded(bool loaded)
void ClearObjectStreamDirty()
void ClearCustomCollisionDirty()
void CopyRoomGraphicsToBuffer()
bool custom_collision_dirty() const
zelda3_version_pointers version_constants() const
std::vector< Door > doors_
void SetStaircaseRoom(int index, uint8_t room)
static constexpr uint8_t kObjectHeaderFloor1Dirty
absl::Status RemoveObject(size_t index)
void SetStair1TargetLayer(uint8_t layer)
void SetLayer2Mode(uint8_t mode)
void LoadLayoutTilesToBuffer()
uint8_t staircase_room(int index) const
void SetTag2(TagKey tag2)
std::vector< DungeonLimitInfo > GetExceededLimitDetails() const
Get list of exceeded limits with details.
void ParseObjectsFromLocation(int objects_location)
uint8_t cached_floor1_graphics_
bool custom_collision_dirty_
static constexpr uint8_t kObjectHeaderFloor2Dirty
void ReloadGraphics(std::optional< uint8_t > entrance_blockset=std::nullopt)
bool pot_items_dirty() const
void SetTag1Direct(TagKey tag1)
void SetHolewarp(uint8_t hw)
void SetStair2Target(uint8_t target)
bool object_stream_dirty() const
bool sprites_dirty() const
void SetCollision(CollisionKey collision)
bool object_stream_header_dirty() const
gfx::BackgroundBuffer bg1_buffer_
void SetStaircasePlane(int index, uint8_t plane)
absl::Status SaveObjects(const DungeonStreamLayout *layout=nullptr)
void SetIsDark(bool is_dark)
std::map< DungeonLimit, int > GetLimitedObjectCounts() const
Count limited objects in this room.
void RenderRoomGraphics()
absl::Status SaveRoomHeader()
gfx::Bitmap composite_bitmap_
Room & operator=(Room &&)
bool chests_dirty() const
uint64_t composite_signature_
CollisionKey collision() const
void LoadRoomGraphics(std::optional< uint8_t > entrance_blockset=std::nullopt)
uint8_t staircase_rooms_[4]
gfx::Bitmap & GetCompositeBitmap(RoomLayerManager &layer_mgr)
Get a composite bitmap of all layers merged.
std::vector< uint8_t > EncodeObjects() const
void SetEffect(EffectKey effect)
absl::Status SaveObjectStreamHeader(const DungeonStreamLayout *layout=nullptr)
gfx::BackgroundBuffer object_bg2_buffer_
void ClearWaterFillDirty()
std::array< uint8_t, 16 > blocks_
void SetTag1(TagKey tag1)
void SetBackgroundTileset(uint8_t tileset)
void SetStair3TargetLayer(uint8_t layer)
void SetRenderEntranceBlockset(uint8_t entrance_blockset)
absl::Status SaveSprites(const DungeonStreamLayout *layout=nullptr)
void PrepareForRender(std::optional< uint8_t > entrance_blockset=std::nullopt)
const std::vector< RoomObject > & GetTileObjects() const
void SetLayerMerging(LayerMergeType merging)
void SetPitsTargetLayer(uint8_t layer)
bool blocks_dirty() const
void SetSpriteTileset(uint8_t tileset)
void SetStair1Target(uint8_t target)
void SetBg2(background2 bg2)
static constexpr uint8_t kObjectHeaderLayoutDirty
bool torches_dirty() const
void EnsureObjectsLoaded()
void SetSpriteset(uint8_t ss)
int ResolveDungeonPaletteId() const
std::unique_ptr< DungeonState > dungeon_state_
void LoadAnimatedGraphics()
std::vector< uint8_t > EncodeSprites() const
std::vector< chest_data > chests_in_room_
void SetBlockset(uint8_t bs)
LayerMergeType layer_merging_
void EnsurePotItemsLoaded()
uint8_t staircase_plane(int index) const
bool has_composite_signature_
bool AreTorchesLoaded() const
uint8_t cached_spriteset_
std::array< uint8_t, 0x10000 > current_gfx16_
std::vector< staircase > z3_staircases_
std::vector< PotItem > pot_items_
bool AreBlocksLoaded() const
uint8_t background_tileset_
void SetStair3Target(uint8_t target)
void HandleSpecialObjects(short oid, uint8_t posX, uint8_t posY, int &nbr_of_staircase)
void SetStair4TargetLayer(uint8_t layer)
absl::Status AddObject(const RoomObject &object)
absl::StatusOr< size_t > FindObjectAt(int x, int y, int layer) const
void SetPalette(uint8_t pal)
bool has_custom_collision() const
const std::vector< PotItem > & GetPotItems() const
void SetStair2TargetLayer(uint8_t layer)
void RenderObjectsToBackground()
void SetLayer2Behavior(uint8_t behavior)
void SetMessageId(uint16_t mid)
A class for managing sprites in the overworld and underworld.
auto set_key_drop(int key)
zelda3_bg2_effect
Background layer 2 effects.
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
absl::Status GetObjectPointerTablePc(const std::vector< uint8_t > &rom_data, int *table_pc)
void AppendChestRecord(std::vector< uint8_t > *bytes, uint16_t word, uint8_t item)
constexpr int kTorchesMaxSize
absl::Status ValidateSpecialObjectDrawLayerSelector(const RoomObject &object, int room_id, const char *object_type)
int ReadRoomPotItemAddressPc(const std::vector< uint8_t > &rom_data, int room_id)
absl::Status RelocateDungeonStream(Rom *rom, int room_id, DungeonStreamKind expected_kind, const DungeonStreamLayout &layout, std::vector< uint8_t > encoded_stream)
void SortAndCoalesceChestRuns(std::vector< ChestWriteRun > *writes)
bool RoomUsesTrackCornerAliases(const std::vector< RoomObject > &objects)
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)
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)
constexpr int kBlocksPointer4
constexpr int kSpritesDataEndExclusive
absl::Status SaveAllChests(Rom *rom, absl::Span< const Room > rooms)
constexpr int kDoorPointers
constexpr int kGfxBufferAnimatedFrameStride
const std::string RoomTag[65]
absl::Status WriteTrackCollision(Rom *rom, int room_id, const CustomCollisionMap &map)
constexpr int kGfxBufferAnimatedFrameOffset
@ NormalDoorOneSidedShutter
Normal door (lower layer; with one-sided shutters)
@ TopShutterLower
Top-sided shutter door (lower layer)
@ SmallKeyDoor
Small key door.
@ BottomShutterLower
Bottom-sided shutter door (lower layer)
@ TopSidedShutter
Top-sided shutter door.
@ DoubleSidedShutterLower
Double-sided shutter (lower layer)
@ UnusableBottomShutter
Unusable bottom-sided shutter door.
@ UnopenableBigKeyDoor
Unopenable, double-sided big key door.
@ BottomSidedShutter
Bottom-sided shutter door.
@ UnusedDoubleSidedShutter
Unused double-sided shutter.
@ CurtainDoor
Curtain door.
@ BigKeyDoor
Big key door.
@ EyeWatchDoor
Eye watch door.
@ DoubleSidedShutter
Double sided shutter door.
PushableBlockBytes EncodePushableBlockEntry(const PushableBlockEntry &entry)
constexpr int kTorchesLengthPointer
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
constexpr int kCustomCollisionDataSoftEnd
std::vector< DungeonLimitInfo > GetExceededLimits(const std::map< DungeonLimit, int > &counts)
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)
constexpr int kGfxBufferRoomOffset
RoomObject::LayerType MapRoomObjectListIndexToDrawLayer(uint8_t list_index)
absl::Status SaveAllPotItems(Rom *rom, absl::Span< const Room > rooms)
constexpr int kGfxBufferRoomSpriteOffset
RoomSize CalculateRoomSize(Rom *rom, int room_id)
absl::Status SaveAllPotItemsImpl(Rom *rom, int room_count, RoomLookup &&room_lookup, const DungeonStreamLayout *repack_layout=nullptr)
constexpr int kPitPointer
constexpr int kDungeonPaletteBytes
constexpr int kSpritesData
void LoadDungeonRenderPaletteToCgram(std::span< uint16_t > cgram, const gfx::SnesPalette &dungeon_palette, const gfx::SnesPalette *hud_palette)
LightableTorchEntry DecodeLightableTorchEntry(const LightableTorchBytes &bytes)
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetChestTableWriteRanges(const Rom *rom)
constexpr int kRoomItemsDataEnd
absl::Status SaveAllTorches(Rom *rom, absl::Span< const Room > rooms)
constexpr int kTileAddress
LightableTorchBytes EncodeLightableTorchEntry(const LightableTorchEntry &entry)
std::vector< SDL_Color > BuildDungeonRenderPalette(const gfx::SnesPalette &dungeon_palette, const gfx::SnesPalette *hud_palette)
absl::StatusOr< DungeonStreamWritePlan > PlanDungeonStreamRepack(const DungeonStreamInventory &inventory, const std::vector< DungeonStreamReplacement > &requested_replacements)
constexpr int kRoomsSpritePointer
constexpr int kTileAddressFloor
constexpr int GetDungeonObjectDataRegionEnd(int pc_address)
absl::Status SaveAllPits(Rom *rom)
constexpr int kChestsDataPointer1
constexpr int kBlocksLength
absl::Status SaveAllBlocks(Rom *rom)
constexpr int kBlocksPointer1
constexpr int kChestTableCapacityRecords
constexpr int kRoomItemsPointers
constexpr int kGfxBufferRoomSpriteStride
absl::StatusOr< CustomCollisionMap > LoadCustomCollisionMap(Rom *rom, int room_id)
constexpr int kChestTableRecordSize
constexpr bool HasCustomCollisionPointerTable(std::size_t rom_size)
absl::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
constexpr int kGfxBufferStride
std::map< DungeonLimit, int > CreateLimitCounter()
absl::Status SaveAllCollision(Rom *rom, absl::Span< Room > rooms)
constexpr int kGfxBufferRoomSpriteLastLineOffset
PushableBlockEntry DecodePushableBlockEntry(const PushableBlockBytes &bytes)
constexpr int kBlocksPointer2
constexpr int kRoomObjectPointer
constexpr int kGfxBufferOffset
uint32_t PcToSnes(uint32_t addr)
uint32_t SnesToPc(uint32_t addr) noexcept
SDL2/SDL3 compatibility layer.
#define RETURN_IF_ERROR(expr)
Legacy chest data structure.
Room transition destination.
PaletteGroup dungeon_main
Represents a group of palettes.
const SnesPalette & palette_ref(int i) const
void AddPalette(SnesPalette pal)
std::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.