13#include <unordered_map>
16#include "absl/status/status.h"
17#include "absl/status/statusor.h"
18#include "absl/strings/str_format.h"
43 const std::vector<OverworldMap>& maps) {
44 std::vector<uint8_t> bytes;
45 const auto append = [&bytes](uint32_t value) {
46 for (
int shift = 0; shift < 32; shift += 8)
47 bytes.push_back(
static_cast<uint8_t
>(value >> shift));
49 append(
static_cast<uint32_t
>(maps.size()));
50 for (
const auto& map : maps) {
51 append(map.overlay_id());
52 append(map.has_overlay() ? 1 : 0);
53 append(map.subscreen_overlay());
54 append(map.render_subscreen_overlay());
55 append(
static_cast<uint32_t
>(map.overlay_data().size()));
56 bytes.insert(bytes.end(), map.overlay_data().begin(),
57 map.overlay_data().end());
63 std::array<int, 4> destinations{};
64 int storage_bytes_per_quadrant = 0;
65 int definition_capacity = 0;
66 bool expanded =
false;
74 layout.
expanded = profile.has_expanded_tile32;
88 return absl::OutOfRangeError(absl::StrFormat(
89 "Tile32 destination 0x%06X is outside the ROM",
destination));
92 usable_bytes = std::min(usable_bytes,
93 static_cast<int>(std::min<size_t>(
94 available, std::numeric_limits<int>::max())));
101 std::sort(ordered_destinations.begin(), ordered_destinations.end());
102 for (
size_t i = 1; i < ordered_destinations.size(); ++i) {
103 const int span = ordered_destinations[i] - ordered_destinations[i - 1];
105 return absl::FailedPreconditionError(
106 "Tile32 destinations overlap or share the same address");
108 usable_bytes = std::min(usable_bytes, span);
113 if (usable_bytes <= 0) {
114 return absl::ResourceExhaustedError(
115 "Tile32 destinations have no usable packed storage");
127 return absl::InvalidArgumentError(absl::StrFormat(
128 "Tile32 definition count %zu is not aligned to a packed group of %d",
132 return absl::ResourceExhaustedError(absl::StrFormat(
133 "Number of unique Tiles32: %zu Out of: %d\n"
134 "Unique Tile32 count exceeds the %s destination capacity; the ROM has "
137 layout.
expanded ?
"expanded" :
"vanilla"));
140 const size_t required_bytes =
144 return absl::ResourceExhaustedError(absl::StrFormat(
145 "Tile32 definitions require %zu bytes per quadrant; destination "
146 "capacity is %d bytes",
149 return absl::OkStatus();
161 if (
rom->size() == 0) {
162 return absl::InvalidArgumentError(
"ROM file not loaded");
167 const auto layout =
rom->InspectLyraLayout();
169 return absl::FailedPreconditionError(
170 "Invalid Lyra descriptor; overworld loading refused");
173 return absl::FailedPreconditionError(
174 "Lyra descriptor canvas loading is not yet supported");
284 return absl::OkStatus();
288 if (
rom ==
nullptr ||
rom->size() == 0) {
289 return absl::InvalidArgumentError(
"ROM file not loaded");
292 return absl::FailedPreconditionError(
293 "Lyra terrain preview requires a fresh overworld model");
295 auto layout =
rom->InspectLyraLayout();
297 return absl::FailedPreconditionError(
298 "Valid Lyra descriptor required for terrain preview");
325 return absl::FailedPreconditionError(
326 absl::StrFormat(
"Lyra map %02X stream is invalid", map_index));
328 if (map_index == 0xA0) {
331 const int local = map_index & 0x3F;
332 const int sx = local % 8;
333 const int sy = local / 8;
335 for (
int y = 0; y < 16; ++y) {
336 for (
int x = 0; x < 16; ++x) {
337 const int tile = (*tiles)[y * 16 + x];
339 return absl::FailedPreconditionError(
340 "Lyra Tile32 definition unavailable");
356 return absl::OkStatus();
361 return absl::FailedPreconditionError(
"Lyra A0 terrain is read-only");
363 if (x < 0 || x >= 16 || y < 0 || y >= 16 ||
364 (tile != 0x2103 && tile != 0x20B2) || tile >=
tiles32_unique_.size()) {
365 return absl::InvalidArgumentError(
"Unsupported Lyra A0 terrain edit");
367 if ((x == 7 || x == 8) && (y == 7 || y == 8) && tile != 0x2103) {
368 return absl::FailedPreconditionError(
369 "Lyra A0 entry and return anchors must remain walkable");
372 return absl::OkStatus();
378 return absl::OkStatus();
398 }
else if (i == parent + 1) {
400 }
else if (i == parent + 8) {
402 }
else if (i == parent + 9) {
413 std::array<bool, kNumOverworldMaps> map_checked{};
414 std::ranges::fill(map_checked,
false);
417 for (
int world_offset = 0; world_offset < 128; world_offset += 64) {
418 for (
int local = 0; local < 64; local++) {
419 int i = world_offset + local;
434 }
else if (i == parent + 1) {
436 }
else if (i == parent + 8) {
438 }
else if (i == parent + 9) {
443 map_checked[i] =
true;
448 std::array<int, 4> siblings = {parent, parent + 1, parent + 8,
450 int world_start = world_offset;
451 int world_end = world_offset + 64;
452 for (
int q = 0; q < 4; q++) {
453 int sibling = siblings[q];
455 if (sibling >= world_start && sibling < world_end &&
456 !map_checked[sibling]) {
458 map_checked[sibling] =
true;
464 map_checked[i] =
true;
482 int i = world + xx + (yy * 8);
484 if (i >=
static_cast<int>(map_checked.size())) {
488 if (!map_checked[i]) {
489 switch (maps[i].area_size()) {
491 map_checked[i] =
true;
496 map_checked[i] =
true;
497 maps[i].SetAsLargeMap(i, 0);
499 if (i + 1 <
static_cast<int>(maps.size())) {
500 map_checked[i + 1] =
true;
501 maps[i + 1].SetAsLargeMap(i, 1);
504 if (i + 8 <
static_cast<int>(maps.size())) {
505 map_checked[i + 8] =
true;
506 maps[i + 8].SetAsLargeMap(i, 2);
509 if (i + 9 <
static_cast<int>(maps.size())) {
510 map_checked[i + 9] =
true;
511 maps[i + 9].SetAsLargeMap(i, 3);
518 map_checked[i] =
true;
521 maps[i].SetAsWideMap(i, 0);
523 if (i + 1 <
static_cast<int>(maps.size())) {
524 map_checked[i + 1] =
true;
525 maps[i + 1].SetAsWideMap(i, 1);
532 map_checked[i] =
true;
535 maps[i].SetAsTallMap(i, 0);
537 if (i + 8 <
static_cast<int>(maps.size())) {
538 map_checked[i + 8] =
true;
539 maps[i + 8].SetAsTallMap(i, 2);
561 return absl::InvalidArgumentError(
562 absl::StrFormat(
"Invalid parent index: %d", parent_index));
571 return absl::FailedPreconditionError(
572 "Wide and Tall areas require ZSCustomOverworld v3+");
576 return absl::FailedPreconditionError(
577 "Special World multi-area changes require ZSCustomOverworld v3+");
581 "ConfigureMultiAreaMap: parent=%d, current_size=%d, new_size=%d, "
585 static_cast<int>(size),
589 std::vector<int> old_siblings;
595 old_siblings = {old_parent, old_parent + 1, old_parent + 8,
599 old_siblings = {old_parent, old_parent + 1};
602 old_siblings = {old_parent, old_parent + 8};
605 old_siblings = {parent_index};
611 std::vector<int> new_siblings;
614 new_siblings = {parent_index};
617 new_siblings = {parent_index, parent_index + 1, parent_index + 8,
621 new_siblings = {parent_index, parent_index + 1};
624 new_siblings = {parent_index, parent_index + 8};
627 for (
int sibling : new_siblings) {
630 return absl::FailedPreconditionError(
631 absl::StrFormat(
"Area size crosses an overworld boundary: parent=%d "
633 parent_index, sibling));
638 for (
int old_sibling : old_siblings) {
652 for (
size_t i = 0; i < new_siblings.size(); ++i) {
669 std::set<int> all_affected;
670 for (
int sibling : old_siblings) {
671 all_affected.insert(sibling);
673 for (
int sibling : new_siblings) {
674 all_affected.insert(sibling);
679 for (
int sibling : all_affected) {
695 for (
int sibling : all_affected) {
716 "Configured %s area: parent=%d, old_siblings=%zu, new_siblings=%zu",
721 parent_index, old_siblings.size(), new_siblings.size());
723 return absl::OkStatus();
727 int index,
int quadrant,
int dimension,
const uint32_t* map32address) {
729 auto arg1,
rom()->ReadByte(map32address[dimension] + quadrant + (index)));
731 rom()->ReadByte(map32address[dimension] + (index) +
732 (quadrant <= 1 ? 4 : 5)));
733 return (uint16_t)(arg1 +
734 (((arg2 >> (quadrant % 2 == 0 ? 4 : 0)) & 0x0F) * 256));
738 constexpr int kMap32TilesLength = 0x33F0;
739 int num_tile32 = kMap32TilesLength;
740 uint32_t map32address[4] = {
749 util::logf(
"Expanded tile32 flag: %d", expanded_flag);
750 if (profile.has_expanded_tile32) {
762 for (
int i = 0; i < num_tile32; i += 6) {
764 for (
int k = 0; k < 4; k++) {
788 for (
int i = 0; i < 0x200; i++) {
794 return absl::OkStatus();
807 util::logf(
"Expanded tile16 flag: %d", expanded_flag);
808 if (expanded !=
nullptr) {
809 *expanded = profile.has_expanded_tile16;
811 if (profile.has_expanded_tile16) {
818 std::vector<gfx::Tile16> tiles16;
819 tiles16.reserve(num_tile16);
820 for (
int i = 0; i < num_tile16; i += 1) {
833 tiles16.emplace_back(t0, t1, t2, t3);
839 bool expanded =
false;
847 return absl::OkStatus();
852 int position_x1 = (x * 2) + (sx * 32);
853 int position_y1 = (y * 2) + (sy * 32);
854 int position_x2 = (x * 2) + 1 + (sx * 32);
855 int position_y2 = (y * 2) + 1 + (sy * 32);
863 switch (world_type) {
882 int local_index = map_index % 64;
883 int sx = local_index % 8;
884 int sy = local_index / 8;
888 for (
int y = 0; y < 32; ++y) {
889 for (
int x = 0; x < 32; ++x) {
890 world[(sx * 32) + x][(sy * 32) + y] = 0;
896 std::vector<uint8_t>& bytes2,
int i,
int sx,
897 int sy,
int& ttpos) {
898 for (
int y = 0; y < 16; y++) {
899 for (
int x = 0; x < 16; x++) {
900 auto tidD = (uint16_t)((bytes2[ttpos] << 8) + bytes[ttpos]);
916 const auto get_ow_map_gfx_ptr = [
this](
int index, uint32_t map_ptr) {
917 int p = (
rom()->data()[map_ptr + 2 + (3 * index)] << 16) +
918 (
rom()->data()[map_ptr + 1 + (3 * index)] << 8) +
919 (
rom()->data()[map_ptr + (3 * index)]);
923 constexpr uint32_t kBaseLowest = 0x0FFFFF;
924 constexpr uint32_t kBaseHighest = 0x0F8000;
926 uint32_t lowest = kBaseLowest;
927 uint32_t highest = kBaseHighest;
934 const bool allow_special_tail =
940 if (!allow_special_tail &&
957 auto p1 = get_ow_map_gfx_ptr(
959 auto p2 = get_ow_map_gfx_ptr(
964 bool pointers_valid =
965 (p1 > 0 && p2 > 0 && p1 <
rom()->size() && p2 <
rom()->size());
966 if (!pointers_valid) {
988 if (p1 <= lowest && p1 > kBaseHighest)
990 if (p2 <= lowest && p2 > kBaseHighest)
994 size_t max_size_p2 =
rom()->size() - p2;
997 size_t max_size_p1 =
rom()->size() - p1;
1002 if (bytes.empty() || bytes2.empty()) {
1022 return absl::OkStatus();
1026 [[maybe_unused]]
auto size =
tiles16_.size();
1032 constexpr int kDarkWorldEssential =
1034 constexpr int kSpecialWorldEssential =
1036 std::vector<int> essential_map_ids;
1040 "Building essential maps only (first %d maps per world) for faster "
1044#ifdef __EMSCRIPTEN__
1050 bool is_essential =
false;
1052 if (i < kLightWorldEssential) {
1053 is_essential =
true;
1055 is_essential =
true;
1057 is_essential =
true;
1061 essential_map_ids.push_back(i);
1076 if (map->is_large_map() &&
1078 if (map->parent() != i && !map->is_initialized()) {
1081 map->set_sprite_graphics(0, 0x0E);
1082 map->set_sprite_graphics(1, 0x0E);
1083 map->set_sprite_graphics(2, 0x0E);
1084 map->set_area_graphics(
1088 }
else if (i == 0x88 || i == 0x93) {
1089 map->set_area_graphics(0x51);
1090 map->set_area_palette(0x00);
1094 map->set_area_palette(
1111 std::vector<std::future<absl::Status>> futures;
1115 bool is_essential =
false;
1118 if (i < kLightWorldEssential) {
1119 is_essential =
true;
1121 is_essential =
true;
1123 is_essential =
true;
1135 auto task_function = [
this, i, size, world_type]() {
1139 futures.emplace_back(std::async(std::launch::async, task_function));
1147 for (
auto& future : futures) {
1153 for (
int map_index : essential_map_ids) {
1158 util::logf(
"Essential maps built. Remaining maps will be built on-demand.");
1159 return absl::OkStatus();
1164 const int map_count =
1166 if (map_index < 0 || map_index >= map_count) {
1167 return absl::InvalidArgumentError(
"Invalid map index");
1173 const bool allow_special_tail =
1176 if (!descriptor_preview && !allow_special_tail &&
1182 return absl::OkStatus();
1194 return absl::OkStatus();
1224 if (map->parent() != map_index && !map->is_initialized()) {
1228 map_index != 0x88) {
1230 map->set_sprite_graphics(0, 0x0E);
1231 map->set_sprite_graphics(1, 0x0E);
1232 map->set_sprite_graphics(2, 0x0E);
1233 map->set_area_graphics(
1237 }
else if (map_index == 0x88 || map_index == 0x93) {
1238 map->set_area_graphics(0x51);
1239 map->set_area_palette(0x00);
1263 uint64_t hash = 0xcbf29ce484222325ULL;
1264 auto mix = [&hash](uint64_t value) {
1266 hash *= 0x100000001b3ULL;
1269 for (
const auto& tile16 :
tiles16_) {
1270 for (
const auto& info : tile16.tiles_info) {
1271 mix(
static_cast<uint64_t
>(info.id_) | (uint64_t{info.palette_} << 16) |
1272 (uint64_t{info.vertical_mirror_} << 24) |
1273 (uint64_t{info.horizontal_mirror_} << 25) |
1274 (uint64_t{info.over_} << 26));
1286 map.LoadAreaGraphics();
1288 if (map.HasGraphicsPreview()) {
1298 const std::vector<uint8_t>* cached_gfx =
nullptr;
1299 const std::vector<uint8_t>* cached_blockset =
nullptr;
1301 if (it !=
tileset_cache_.end() && it->second.sheet_revisions != revisions) {
1308 cached_gfx = &it->second.current_gfx;
1309 if (it->second.tiles16_fingerprint == fingerprint &&
1310 !it->second.tile16_blockset.empty()) {
1311 cached_blockset = &it->second.tile16_blockset;
1317 GetMapTiles(world_type), cached_gfx, cached_blockset));
1323 if (e->second.last_use < oldest->second.last_use) {
1330 it->second.current_gfx = map.current_graphics();
1331 it->second.sheet_revisions = revisions;
1333 if (cached_blockset ==
nullptr) {
1334 it->second.tile16_blockset = map.current_tile16_blockset();
1335 it->second.tiles16_fingerprint = fingerprint;
1338 return absl::OkStatus();
1347 for (
size_t i = 0; i < key.size(); ++i) {
1357 const auto& key = it->first;
1358 if (std::find(key.begin(), key.end(),
static_cast<uint8_t
>(sheet)) !=
1375 if (map_index < 0 ||
1380 const int parent = map.parent();
1385 if (parent == map_index || parent < 0 ||
1387 map.ClearInheritedAreaProperties();
1393absl::StatusOr<Overworld::SubscreenOverlayLayer>
1396 return absl::InvalidArgumentError(
"Invalid map index");
1401 layer.
overlay_id = map.render_subscreen_overlay();
1435absl::StatusOr<std::array<uint8_t, kNumTileTypes>>
1438 return absl::FailedPreconditionError(
"No ROM for collision preview");
1441 return absl::InvalidArgumentError(
"Invalid map ID for collision preview");
1444 return absl::FailedPreconditionError(
1445 "Descriptor collision preview is not supported by this reader");
1449 return absl::FailedPreconditionError(
1450 "Unknown ZScream collision layout; no vanilla fallback");
1456 return absl::FailedPreconditionError(
1457 "Map must be loaded before resolving per-sheet collisions");
1460 for (
size_t slot = 0; slot < sheets.size(); ++slot) {
1464 map->MainSlotProvenance(
static_cast<int>(slot)).effective_sheet;
1471 return absl::OutOfRangeError(
"Incomplete legacy collision table");
1473 std::array<uint8_t, kNumTileTypes> types{};
1474 std::copy_n(
rom_->
vector().begin() + offset, types.size(), types.begin());
1505 int parent_id = map->parent();
1506 std::vector<int> siblings;
1512 switch (map->area_size()) {
1514 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
1517 siblings = {parent_id, parent_id + 1};
1520 siblings = {parent_id, parent_id + 8};
1523 siblings = {map_index};
1528 if (map->is_large_map()) {
1529 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
1531 siblings = {map_index};
1536 for (
int sibling : siblings) {
1549#ifdef __EMSCRIPTEN__
1551 for (
int state = 0; state < 3; ++state) {
1557 std::vector<std::future<absl::Status>> futures;
1558 for (
int state = 0; state < 3; ++state) {
1559 futures.emplace_back(
1560 std::async(std::launch::async, [
this, layout, state]() {
1566 for (
auto& future : futures) {
1571 return absl::OkStatus();
1575 int num_maps_per_gamestate,
1577 if (!
rom_ || game_state < 0 || game_state >= 3 ||
1578 num_maps_per_gamestate < 0 || num_maps_per_gamestate > 160 ||
1580 return absl::FailedPreconditionError(
1581 "Sprite loading requires initialized maps");
1583 std::vector<Sprite> loaded;
1584 std::array<bool, 160> loaded_maps{};
1585 for (
int i = 0; i < num_maps_per_gamestate; ++i) {
1589 *
rom_, sprites_per_gamestate_ptr + i * 2));
1590 loaded_maps[i] =
true;
1591 const int screen = i % 64;
1592 for (
size_t pos = 0; pos + 1 < bytes.size(); pos += 3) {
1593 const auto y = bytes[pos];
1594 const auto x = bytes[pos + 1];
1596 static_cast<uint8_t
>(i), bytes[pos + 2], x & 0x3F,
1597 y & 0x3F, (x & 0x3F) * 16 + (screen % 8) * 512,
1598 (y & 0x3F) * 16 + (screen / 8) * 512);
1599 loaded.back().set_overworld_coordinate_flags(x, y);
1601 loaded.back().Draw();
1606 return absl::OkStatus();
1611 return absl::FailedPreconditionError(
"No ROM for overworld sprites");
1613 bool loaded =
false;
1614 for (
int state = 0; state < 3; ++state) {
1615 for (
int map = 0; map < 160; ++map) {
1622 if (sprite.deleted())
1624 const int map = sprite.map_id();
1625 if (map >= 160 || !edits[state][map]) {
1626 return absl::FailedPreconditionError(
1627 "Sprite belongs to an unloaded or unsupported map/game state");
1631 const int x = sprite.x() - (map % 8) * 512;
1632 const int y = sprite.y() - ((map % 64) / 8) * 512;
1633 if (x < 0 || x >= 1024 || y < 0 || y >= 1024 || x % 16 || y % 16) {
1634 return absl::InvalidArgumentError(
1635 "Overworld sprite must be on a 16-pixel grid within its parent "
1638 auto& bytes = *edits[state][map];
1639 bytes.push_back(
static_cast<uint8_t
>(y / 16) |
1640 sprite.overworld_y_flags());
1641 bytes.push_back(
static_cast<uint8_t
>(x / 16) |
1642 sprite.overworld_x_flags());
1643 bytes.push_back(sprite.id());
1645 for (
auto& bytes : edits[state])
1647 bytes->push_back(0xFF);
1660 if (
rom ==
nullptr) {
1661 return absl::FailedPreconditionError(
"No ROM for overworld sprites");
1690 return absl::OkStatus();
1695 return absl::OkStatus();
1698 return absl::FailedPreconditionError(
1699 "Private Lyra producer reservation refuses changed native terrain "
1700 "before serialization");
1703 return absl::FailedPreconditionError(
1704 "Private Lyra reservation refuses changed overlay model before "
1707 return absl::OkStatus();
1740 return absl::OkStatus();
1744 util::logf(
"Overworld tiles unchanged; preserving compressed map pointers");
1747 return absl::OkStatus();
1761 std::vector<uint8_t> single_map_1(512);
1762 std::vector<uint8_t> single_map_2(512);
1766 for (
int y = 0; y < 16; y++) {
1767 for (
int x = 0; x < 16; x++) {
1769 single_map_1[npos] = packed & 0xFF;
1770 single_map_2[npos] = (packed >> 8) & 0xFF;
1779 if (a.empty() || b.empty()) {
1780 return absl::AbortedError(
"Error compressing map gfx.");
1787 if ((pos + size_a) >= 0x5FE70 && (pos + size_a) <= 0x60000) {
1791 if ((pos + size_a) >= 0x6411F && (pos + size_a) <= 0x70000) {
1792 util::logf(
"Pos set to overflow region for map %s at %s",
1797 const auto compare_array = [](
const std::vector<uint8_t>& array1,
1798 const std::vector<uint8_t>& array2) ->
bool {
1799 if (array1.size() != array2.size()) {
1803 for (
size_t i = 0; i < array1.size(); i++) {
1804 if (array1[i] != array2[i]) {
1812 for (
int j = 0; j < i; j++) {
1826 std::copy(a.begin(), a.end(),
map_data_p1[i].begin());
1829 util::logf(
"Saving map pointers1 and compressed data for map %s at %s",
1846 if ((pos + b.size()) >= 0x5FE70 && (pos + b.size()) <= 0x60000) {
1850 if ((pos + b.size()) >= 0x6411F && (pos + b.size()) <= 0x70000) {
1851 util::logf(
"Pos set to overflow region for map %s at %s",
1858 std::copy(b.begin(), b.end(),
map_data_p2[i].begin());
1861 util::logf(
"Saving map pointers2 and compressed data for map %s at %s",
1882 return absl::AbortedError(
"Too many maps data " + std::to_string(pos));
1887 return absl::OkStatus();
1892 std::vector<std::pair<uint32_t, uint32_t>> ranges;
1895 uint32_t map32address[4] = {
1898 int map32_len = 0x33F0;
1909 for (
int i = 0; i < 4; ++i) {
1910 if (map32address[i] > 0) {
1911 ranges.emplace_back(map32address[i], map32address[i] + map32_len);
1922 ranges.emplace_back(map16_addr, map16_addr + map16_len);
1928 constexpr uint32_t kCompressedBank0bEnd = 0x5FE70;
1929 constexpr uint32_t kCompressedBank0cStart = 0x60000;
1930 constexpr uint32_t kCompressedBank0cEnd = 0x6411F;
1938 kCompressedBank0bEnd);
1939 ranges.emplace_back(kCompressedBank0cStart, kCompressedBank0cEnd);
1950 bool use_expanded_transitions =
1953 if (use_expanded_transitions) {
1959 std::vector<uint8_t> checked_map;
1971 if (std::find(checked_map.begin(), checked_map.end(), i) !=
1972 checked_map.end()) {
1979 const uint8_t large_map_offsets[] = {0, 1, 8, 9};
1980 for (
const auto& offset : large_map_offsets) {
2005 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
2008 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
2012 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
2015 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
2019 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
2022 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
2026 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
2029 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
2033 (parent_x_pos * 0x200)));
2035 (parent_y_pos * 0x200)));
2039 (parent_x_pos * 0x200)));
2042 (parent_y_pos * 0x200)));
2046 (parent_x_pos * 0x200)));
2049 (parent_y_pos * 0x200)));
2053 (parent_x_pos * 0x200)));
2056 (parent_y_pos * 0x200)));
2065 if (parent_x_pos == 0) {
2168 checked_map.emplace_back(i);
2169 checked_map.emplace_back((i + 1));
2170 checked_map.emplace_back((i + 8));
2171 checked_map.emplace_back((i + 9));
2193 if (i - 1 >= 0 && parent_x_pos != 0) {
2205 if (i + 1 < 64 && parent_x_pos != 7) {
2249 (uint16_t)((y_pos * 0x200) - 0xE0)));
2251 (uint16_t)((x_pos * 0x200) - 0x100)));
2258 checked_map.emplace_back(i);
2267 const auto legacy_size_byte = [
this](
int map_index) -> uint8_t {
2270 const auto legacy_loading_size_byte = [
this](
int map_index) -> uint8_t {
2276 legacy_size_byte(map_index)));
2278 legacy_loading_size_byte(map_index)));
2284 legacy_loading_size_byte(map_index)));
2287 constexpr int OverworldScreenTileMapChangeMask = 0x1262C;
2290 rom()->WriteShort(OverworldScreenTileMapChangeMask + 0, 0x1F80));
2292 rom()->WriteShort(OverworldScreenTileMapChangeMask + 2, 0x1F80));
2294 rom()->WriteShort(OverworldScreenTileMapChangeMask + 4, 0x007F));
2296 rom()->WriteShort(OverworldScreenTileMapChangeMask + 6, 0x007F));
2298 return absl::OkStatus();
2302 int i,
int parent_x_pos,
int parent_y_pos,
int transition_target_north,
2303 int transition_target_west,
int transition_pos_x,
int transition_pos_y,
2304 int screen_change_1,
int screen_change_2,
int screen_change_3,
2305 int screen_change_4) {
2308 rom()->WriteShort(transition_target_north + (i * 2),
2309 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
2311 rom()->WriteShort(transition_target_west + (i * 2),
2312 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
2315 rom()->WriteShort(transition_pos_x + (i * 2), parent_x_pos * 0x0200));
2317 rom()->WriteShort(transition_pos_y + (i * 2), parent_y_pos * 0x0200));
2320 uint16_t by_screen1_small = 0x0060;
2323 if ((i % 0x40) - 1 >= 0) {
2328 west_neighbor.large_index() == 3) {
2329 by_screen1_small = 0xF060;
2333 west_neighbor.large_index() == 2) {
2334 by_screen1_small = 0xF060;
2339 rom()->WriteShort(screen_change_1 + (i * 2), by_screen1_small));
2342 uint16_t by_screen2_small = 0x0040;
2350 east_neighbor.large_index() == 2) {
2351 by_screen2_small = 0xF040;
2355 east_neighbor.large_index() == 2) {
2356 by_screen2_small = 0xF040;
2361 rom()->WriteShort(screen_change_2 + (i * 2), by_screen2_small));
2364 uint16_t by_screen3_small = 0x1800;
2367 if ((i % 0x40) - 8 >= 0) {
2372 north_neighbor.large_index() == 3) {
2373 by_screen3_small = 0x17C0;
2377 north_neighbor.large_index() == 1) {
2378 by_screen3_small = 0x17C0;
2383 rom()->WriteShort(screen_change_3 + (i * 2), by_screen3_small));
2386 uint16_t by_screen4_small = 0x1000;
2394 south_neighbor.large_index() == 1) {
2395 by_screen4_small = 0x0FC0;
2399 south_neighbor.large_index() == 1) {
2400 by_screen4_small = 0x0FC0;
2405 rom()->WriteShort(screen_change_4 + (i * 2), by_screen4_small));
2407 return absl::OkStatus();
2411 int i,
int parent_x_pos,
int parent_y_pos,
int transition_target_north,
2412 int transition_target_west,
int transition_pos_x,
int transition_pos_y,
2413 int screen_change_1,
int screen_change_2,
int screen_change_3,
2414 int screen_change_4) {
2416 const uint16_t offsets[] = {0, 2, 16, 18};
2417 for (
auto offset : offsets) {
2419 rom()->WriteShort(transition_target_north + (i * 2) + offset,
2420 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
2422 rom()->WriteShort(transition_target_west + (i * 2) + offset,
2423 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
2425 parent_x_pos * 0x0200));
2427 parent_y_pos * 0x0200));
2432 std::array<uint16_t, 4> by_screen1_large = {0x0060, 0x0060, 0x1060, 0x1060};
2435 if ((i % 0x40) - 1 >= 0) {
2439 switch (west_neighbor.large_index()) {
2441 by_screen1_large[2] = 0x0060;
2444 by_screen1_large[0] = 0xF060;
2448 switch (west_neighbor.large_index()) {
2450 by_screen1_large[2] = 0x0060;
2453 by_screen1_large[0] = 0xF060;
2459 for (
int j = 0; j < 4; j++) {
2461 by_screen1_large[j]));
2465 std::array<uint16_t, 4> by_screen2_large = {0x0080, 0x0080, 0x1080, 0x1080};
2472 switch (east_neighbor.large_index()) {
2474 by_screen2_large[3] = 0x0080;
2477 by_screen2_large[1] = 0xF080;
2481 switch (east_neighbor.large_index()) {
2483 by_screen2_large[3] = 0x0080;
2486 by_screen2_large[1] = 0xF080;
2492 for (
int j = 0; j < 4; j++) {
2494 by_screen2_large[j]));
2498 std::array<uint16_t, 4> by_screen3_large = {0x1800, 0x1840, 0x1800, 0x1840};
2501 if ((i % 0x40) - 8 >= 0) {
2505 switch (north_neighbor.large_index()) {
2507 by_screen3_large[1] = 0x1800;
2510 by_screen3_large[0] = 0x17C0;
2514 switch (north_neighbor.large_index()) {
2516 by_screen3_large[1] = 0x1800;
2519 by_screen3_large[0] = 0x17C0;
2525 for (
int j = 0; j < 4; j++) {
2527 by_screen3_large[j]));
2531 std::array<uint16_t, 4> by_screen4_large = {0x2000, 0x2040, 0x2000, 0x2040};
2538 switch (south_neighbor.large_index()) {
2540 by_screen4_large[3] = 0x2000;
2543 by_screen4_large[2] = 0x1FC0;
2547 switch (south_neighbor.large_index()) {
2549 by_screen4_large[3] = 0x2000;
2552 by_screen4_large[2] = 0x1FC0;
2558 for (
int j = 0; j < 4; j++) {
2560 by_screen4_large[j]));
2563 return absl::OkStatus();
2567 int i,
int parent_x_pos,
int parent_y_pos,
int transition_target_north,
2568 int transition_target_west,
int transition_pos_x,
int transition_pos_y,
2569 int screen_change_1,
int screen_change_2,
int screen_change_3,
2570 int screen_change_4) {
2572 const uint16_t offsets[] = {0, 2};
2573 for (
auto offset : offsets) {
2575 rom()->WriteShort(transition_target_north + (i * 2) + offset,
2576 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
2578 rom()->WriteShort(transition_target_west + (i * 2) + offset,
2579 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
2581 parent_x_pos * 0x0200));
2583 parent_y_pos * 0x0200));
2587 std::array<uint16_t, 2> by_screen1_wide = {0x0060, 0x0060};
2590 if ((i % 0x40) - 1 >= 0) {
2595 west_neighbor.large_index() == 3) {
2596 by_screen1_wide[0] = 0xF060;
2600 west_neighbor.large_index() == 2) {
2601 by_screen1_wide[0] = 0xF060;
2605 for (
int j = 0; j < 2; j++) {
2607 by_screen1_wide[j]));
2611 std::array<uint16_t, 2> by_screen2_wide = {0x0080, 0x0080};
2619 east_neighbor.large_index() == 2) {
2620 by_screen2_wide[1] = 0xF080;
2624 east_neighbor.large_index() == 2) {
2625 by_screen2_wide[1] = 0xF080;
2629 for (
int j = 0; j < 2; j++) {
2631 by_screen2_wide[j]));
2635 std::array<uint16_t, 2> by_screen3_wide = {0x1800, 0x1840};
2638 if ((i % 0x40) - 8 >= 0) {
2642 switch (north_neighbor.large_index()) {
2644 by_screen3_wide[1] = 0x1800;
2647 by_screen3_wide[0] = 0x17C0;
2651 switch (north_neighbor.large_index()) {
2653 by_screen3_wide[1] = 0x1800;
2656 by_screen3_wide[0] = 0x17C0;
2662 for (
int j = 0; j < 2; j++) {
2664 by_screen3_wide[j]));
2668 std::array<uint16_t, 2> by_screen4_wide = {0x1000, 0x1040};
2675 switch (south_neighbor.large_index()) {
2677 by_screen4_wide[1] = 0x1000;
2680 by_screen4_wide[0] = 0x0FC0;
2684 if (south_neighbor.large_index() == 1) {
2685 by_screen4_wide[0] = 0x0FC0;
2687 switch (south_neighbor.large_index()) {
2689 by_screen4_wide[1] = 0x1000;
2692 by_screen4_wide[0] = 0x0FC0;
2698 for (
int j = 0; j < 2; j++) {
2700 by_screen4_wide[j]));
2703 return absl::OkStatus();
2707 int i,
int parent_x_pos,
int parent_y_pos,
int transition_target_north,
2708 int transition_target_west,
int transition_pos_x,
int transition_pos_y,
2709 int screen_change_1,
int screen_change_2,
int screen_change_3,
2710 int screen_change_4) {
2712 const uint16_t offsets[] = {0, 16};
2713 for (
auto offset : offsets) {
2715 rom()->WriteShort(transition_target_north + (i * 2) + offset,
2716 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
2718 rom()->WriteShort(transition_target_west + (i * 2) + offset,
2719 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
2721 parent_x_pos * 0x0200));
2723 parent_y_pos * 0x0200));
2727 std::array<uint16_t, 2> by_screen1_tall = {0x0060, 0x1060};
2730 if ((i % 0x40) - 1 >= 0) {
2734 switch (west_neighbor.large_index()) {
2736 by_screen1_tall[1] = 0x0060;
2739 by_screen1_tall[0] = 0xF060;
2743 switch (west_neighbor.large_index()) {
2745 by_screen1_tall[1] = 0x0060;
2748 by_screen1_tall[0] = 0xF060;
2754 for (
int j = 0; j < 2; j++) {
2756 by_screen1_tall[j]));
2760 std::array<uint16_t, 2> by_screen2_tall = {0x0040, 0x1040};
2767 switch (east_neighbor.large_index()) {
2769 by_screen2_tall[1] = 0x0040;
2772 by_screen2_tall[0] = 0xF040;
2776 switch (east_neighbor.large_index()) {
2778 by_screen2_tall[1] = 0x0040;
2781 by_screen2_tall[0] = 0xF040;
2787 for (
int j = 0; j < 2; j++) {
2789 by_screen2_tall[j]));
2793 std::array<uint16_t, 2> by_screen3_tall = {0x1800, 0x1800};
2796 if ((i % 0x40) - 8 >= 0) {
2801 north_neighbor.large_index() == 3) {
2802 by_screen3_tall[0] = 0x17C0;
2806 north_neighbor.large_index() == 1) {
2807 by_screen3_tall[0] = 0x17C0;
2811 for (
int j = 0; j < 2; j++) {
2813 by_screen3_tall[j]));
2817 std::array<uint16_t, 2> by_screen4_tall = {0x2000, 0x2000};
2825 south_neighbor.large_index() == 1) {
2826 by_screen4_tall[1] = 0x1FC0;
2830 south_neighbor.large_index() == 1) {
2831 by_screen4_tall[1] = 0x1FC0;
2835 for (
int j = 0; j < 2; j++) {
2837 by_screen4_tall[j]));
2840 return absl::OkStatus();
2844 util::logf(
"Saving Large Maps (v3+ Expanded)");
2856 std::vector<uint8_t> checked_map;
2861 if (std::find(checked_map.begin(), checked_map.end(), i) !=
2862 checked_map.end()) {
2878 i, parent_x_pos, parent_y_pos, transition_target_north,
2879 transition_target_west, transition_pos_x, transition_pos_y,
2880 screen_change_1, screen_change_2, screen_change_3,
2882 checked_map.emplace_back(i);
2887 i, parent_x_pos, parent_y_pos, transition_target_north,
2888 transition_target_west, transition_pos_x, transition_pos_y,
2889 screen_change_1, screen_change_2, screen_change_3,
2892 checked_map.emplace_back(i);
2893 checked_map.emplace_back(i + 1);
2894 checked_map.emplace_back(i + 8);
2895 checked_map.emplace_back(i + 9);
2900 i, parent_x_pos, parent_y_pos, transition_target_north,
2901 transition_target_west, transition_pos_x, transition_pos_y,
2902 screen_change_1, screen_change_2, screen_change_3,
2905 checked_map.emplace_back(i);
2906 checked_map.emplace_back(i + 1);
2911 i, parent_x_pos, parent_y_pos, transition_target_north,
2912 transition_target_west, transition_pos_x, transition_pos_y,
2913 screen_change_1, screen_change_2, screen_change_3,
2916 checked_map.emplace_back(i);
2917 checked_map.emplace_back(i + 8);
2922 return absl::OkStatus();
2927 std::vector<uint64_t> all_tile_16;
2942 for (
int y = 0; y < 32; y += 2) {
2943 for (
int x = 0; x < 32; x += 2) {
2945 tiles_used[x + (sx * 32)][y + (sy * 32)],
2946 tiles_used[x + 1 + (sx * 32)][y + (sy * 32)],
2947 tiles_used[x + (sx * 32)][y + 1 + (sy * 32)],
2948 tiles_used[x + 1 + (sx * 32)][y + 1 + (sy * 32)]);
2975 return absl::OkStatus();
2983 std::vector<uint64_t> all_tile_16 = GetAllTile16(
map_tiles_);
2986 std::set<uint64_t> unique_tiles_set(all_tile_16.begin(), all_tile_16.end());
2988 std::vector<uint64_t> unique_tiles(all_tile_16);
2989 unique_tiles.assign(unique_tiles_set.begin(), unique_tiles_set.end());
2992 std::unordered_map<uint64_t, uint16_t> all_tiles_indexed;
2993 for (
size_t tile32_id = 0; tile32_id < unique_tiles.size(); tile32_id++) {
2994 all_tiles_indexed.insert(
2995 {unique_tiles[tile32_id],
static_cast<uint16_t
>(tile32_id)});
3001 tiles32_list_.emplace_back(all_tiles_indexed[all_tile_16[j]]);
3005 for (
size_t i = 0; i < unique_tiles.size(); ++i) {
3015 ValidateMap32DefinitionCount(
tiles32_unique_.size(), storage_layout));
3020 <<
" Out of: " << storage_layout.definition_capacity << std::endl;
3023 return absl::OkStatus();
3029 return absl::OkStatus();
3031 util::logf(
"Overworld tiles unchanged; preserving expanded tile32");
3032 return absl::OkStatus();
3036 if (!storage_layout.expanded) {
3037 return absl::FailedPreconditionError(
3038 "Expanded Tile32 save requested for a vanilla Tile32 layout");
3043 ValidateMap32DefinitionCount(
tiles32_unique_.size(), storage_layout));
3053 const auto relocate = [
this](
int operand_pc,
int target_pc) -> absl::Status {
3055 if (
SnesToPc(current) ==
static_cast<uint32_t
>(target_pc)) {
3056 return absl::OkStatus();
3058 return rom()->WriteLong(operand_pc,
PcToSnes(target_pc));
3060 constexpr int kTopRightOperands[] = {0x0176EC, 0x0176F3, 0x0176FA,
3061 0x017701, 0x017708, 0x01771A};
3062 constexpr int kBottomLeftOperands[] = {0x01772C, 0x017733, 0x01773A,
3063 0x017741, 0x017748, 0x01775A};
3064 constexpr int kBottomRightOperands[] = {0x01776C, 0x017773, 0x01777A,
3065 0x017781, 0x017788, 0x01779A};
3066 for (
int i = 0; i < 6; ++i) {
3072 int unique_tile_index = 0;
3106 auto top_right = topRight;
3111 top_right + (i + 1),
3114 top_right + (i + 2),
3117 top_right + (i + 3),
3121 top_right + (i + 4),
3126 top_right + (i + 5),
3133 auto bottom_left = bottomLeft;
3138 bottom_left + (i + 1),
3141 bottom_left + (i + 2),
3144 bottom_left + (i + 3),
3148 bottom_left + (i + 4),
3153 bottom_left + (i + 5),
3160 auto bottom_right = bottomRight;
3165 bottom_right + (i + 1),
3168 bottom_right + (i + 2),
3171 bottom_right + (i + 3),
3175 bottom_right + (i + 4),
3180 bottom_right + (i + 5),
3186 unique_tile_index += 4;
3189 return absl::OkStatus();
3195 return absl::OkStatus();
3197 util::logf(
"Overworld tiles unchanged; preserving tile32 definitions");
3198 return absl::OkStatus();
3203 if (storage_layout.expanded) {
3204 return absl::FailedPreconditionError(
3205 "Vanilla Tile32 save requested for an expanded Tile32 layout");
3208 ValidateMap32DefinitionCount(
tiles32_unique_.size(), storage_layout));
3210 int unique_tile_index = 0;
3250 top_right + (i + 1),
3253 top_right + (i + 2),
3256 top_right + (i + 3),
3260 top_right + (i + 4),
3265 top_right + (i + 5),
3325 unique_tile_index += 4;
3328 return absl::OkStatus();
3334 return absl::OkStatus();
3336 const auto write_pointer = [
this](
int address,
3337 uint32_t target) -> absl::Status {
3342 return absl::OkStatus();
3344 return rom()->WriteLong(address, target);
3346 const auto write_bank = [
this](
int address, uint8_t bank) -> absl::Status {
3348 if ((current & 0x7F) == (bank & 0x7F)) {
3349 return absl::OkStatus();
3351 return rom()->WriteByte(address, bank);
3411 static_cast<uint8_t
>(
PcToSnes(map16_expanded) >> 16)));
3414 static_cast<uint8_t
>(
PcToSnes(map16_expanded) >> 16)));
3416 int tpos = map16_expanded;
3420 rom()->WriteShort(tpos, TileInfoToShort(
tiles16_[i].tile0_)));
3423 rom()->WriteShort(tpos, TileInfoToShort(
tiles16_[i].tile1_)));
3426 rom()->WriteShort(tpos, TileInfoToShort(
tiles16_[i].tile2_)));
3429 rom()->WriteShort(tpos, TileInfoToShort(
tiles16_[i].tile3_)));
3433 return absl::OkStatus();
3439 return absl::OkStatus();
3445 rom()->WriteShort(tpos, TileInfoToShort(
tiles16_[i].tile0_)))
3448 rom()->WriteShort(tpos, TileInfoToShort(
tiles16_[i].tile1_)))
3451 rom()->WriteShort(tpos, TileInfoToShort(
tiles16_[i].tile2_)))
3454 rom()->WriteShort(tpos, TileInfoToShort(
tiles16_[i].tile3_)))
3457 return absl::OkStatus();
3462 return absl::InvalidArgumentError(
"Cannot save entrances without a ROM");
3467 return absl::FailedPreconditionError(
3468 "Legacy hole source rows are missing");
3470 for (
size_t i = 0; i <
all_holes_.size(); ++i) {
3471 if (!
all_holes_[i].HasUnchangedHoleSourceState(i)) {
3472 return absl::FailedPreconditionError(
3473 "Legacy holes were edited; use the expanded entrance table rows");
3482 return absl::OkStatus();
3487 return absl::OkStatus();
3498 const auto serialized_items = [](
const std::vector<OverworldItem>& items) {
3499 std::vector<std::array<int, 4>> records;
3500 for (
const auto& item : items) {
3501 if (!item.deleted) {
3503 {item.room_map_id_, item.game_x_, item.game_y_, item.id_});
3506 std::stable_sort(records.begin(), records.end(),
3507 [](
const auto& a,
const auto& b) { return a[0] < b[0]; });
3510 if (serialized_items(stored_items) == serialized_items(
all_items_)) {
3511 return absl::OkStatus();
3522 return absl::OkStatus();
3530 return absl::OkStatus();
3536 std::vector<uint8_t> new_overlay_code = {
3542 0xBF, 0x00, 0x00, 0x00,
3544 0xBF, 0x00, 0x00, 0x00,
3560 int snes_ptr_start =
PcToSnes(ptr_start);
3570 constexpr int kExpandedOverlaySpace = 0x120000;
3571 int pos = kExpandedOverlaySpace;
3582 for (
size_t t = 0; t < overlay_data.size(); t += 3) {
3583 if (t + 2 < overlay_data.size()) {
3587 pos + 1, overlay_data[t] | (overlay_data[t + 1] << 8)));
3601 return absl::OkStatus();
3606 return absl::FailedPreconditionError(
"No ROM for collision save");
3611 return absl::FailedPreconditionError(
3612 "Legacy collision save is disabled for per-sheet or unknown ZScream "
3618 return absl::OutOfRangeError(
"Incomplete legacy collision save range");
3627 return absl::OkStatus();
3638 return absl::OkStatus();
3648 return absl::OkStatus();
3657 for (uint16_t tile_id = 0; tile_id < static_cast<uint16_t>(
tiles16_.size()) &&
3665 util::logf(
"Auto-detected %d diggable tiles",
3667 return absl::OkStatus();
3671 bool enable_main_palette,
3673 bool enable_gfx_groups,
3674 bool enable_subscreen_overlay,
3675 bool enable_animated) {
3679 return absl::OkStatus();
3684 const auto write_enable_flag = [
this](
int address,
3685 bool enabled) -> absl::Status {
3687 if ((current != 0x00) == enabled) {
3688 return absl::OkStatus();
3690 return rom()->WriteByte(address, enabled ? 0xFF : 0x00);
3698 enable_main_palette));
3701 if (enable_main_palette) {
3714 enable_gfx_groups));
3718 enable_subscreen_overlay));
3721 if (enable_mosaic) {
3725 uint8_t mosaic_byte = (mosaic[0] ? 0x08 : 0x00) |
3726 (mosaic[1] ? 0x04 : 0x00) |
3727 (mosaic[2] ? 0x02 : 0x00) |
3728 (mosaic[3] ? 0x01 : 0x00);
3735 if (enable_gfx_groups) {
3737 for (
int j = 0; j < 8; j++) {
3744 if (enable_animated) {
3752 if (enable_subscreen_overlay) {
3761 return absl::OkStatus();
3767 return absl::OkStatus();
3770 util::logf(
"Saving Area Specific Background Colors");
3779 return absl::OkStatus();
3784 return absl::FailedPreconditionError(
"ROM not loaded");
3790 return absl::OkStatus();
3793 const auto enabled = [
this](
int address) {
3800 const bool subscreen_overlay_enabled =
3804 bg_color_enabled, main_palette_enabled, mosaic_enabled,
3805 gfx_groups_enabled, subscreen_overlay_enabled, animated_enabled));
3806 if (bg_color_enabled) {
3810 return absl::OkStatus();
3816 const bool use_zscustom_palette_table =
3818 const bool use_zscustom_special_tables =
3820 const int palette_table = use_zscustom_palette_table
3864 if (use_zscustom_special_tables) {
3868 if (use_zscustom_palette_table) {
3874 const int sprite_gfx_table =
3875 use_zscustom_special_tables
3878 const int sprite_palette_table =
3882 rom()->WriteByte(sprite_gfx_table + local_index,
3885 rom()->WriteByte(sprite_palette_table + local_index,
3889 return absl::OkStatus();
3899 const auto owns_slot = [
this](
int map_index) {
3903 return parent == map_index;
3907 if (!owns_slot(i)) {
3921 if (!owns_slot(i)) {
3929 return absl::OkStatus();
3937 if (asm_version < 3 || asm_version == 0xFF) {
3938 return absl::OkStatus();
3943 uint8_t area_size_byte =
3955 return absl::OkStatus();
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
const auto & vector() const
absl::StatusOr< uint16_t > ReadWord(int offset) const
absl::Status ValidateLyraPrivateReservation() const
bool has_lyra_private_reservation() const
RAII timer for automatic timing management.
Tile composition of four 16x16 tiles.
uint64_t GetPackedValue() const
SNES 16-bit tile metadata container.
int GetDiggableCount() const
Get the count of tiles marked as diggable.
void SetVanillaDefaults()
Reset to vanilla diggable tiles.
void SetDiggable(uint16_t tile_id, bool diggable)
Set or clear the diggable bit for a Map16 tile ID.
static bool IsTile16Diggable(const gfx::Tile16 &tile16, const std::array< uint8_t, 0x200 > &all_tiles_types)
Check if a Tile16 should be diggable based on its component tiles.
void Clear()
Clear all diggable bits.
uint64_t Revision(uint16_t sheet) const
static absl::StatusOr< OverworldMap > CreateLyraPreview(int index, Rom *rom, GameData *game_data, const rom::LyraLayoutInspection *layout)
static bool SupportsCustomBGColors(OverworldVersion version)
Check if ROM supports custom background colors per area (v2+)
static OverworldVersion GetVersion(const Rom &rom)
Detect ROM version from ASM marker byte.
static bool SupportsAreaEnum(OverworldVersion version)
Check if ROM supports area enum system (v3+ only)
static const char * GetVersionName(OverworldVersion version)
Get human-readable version name for display/logging.
absl::Status SaveMap32Expanded()
Save expanded tile32 definitions (v1+ ROMs)
absl::Status BuildMapWithTilesetCache(int map_index, int world_type)
absl::Status DecompressAllMapTilesParallel()
std::vector< uint16_t > tiles32_list_
void InvalidateTilesetCacheForSheet(int sheet)
Drop cached tilesets that load sheet (graphics sheet edits). Built maps keep their pixels; mark them ...
bool staged_map_tiles_valid_
rom::LyraLayoutInspection descriptor_layout_
absl::Status Load(Rom *rom)
Load all overworld data from ROM.
std::vector< OverworldItem > all_items_
void OrganizeMapTiles(std::vector< uint8_t > &bytes, std::vector< uint8_t > &bytes2, int i, int sx, int sy, int &ttpos)
zelda3_version_pointers version_constants() const
Get version-specific ROM addresses.
std::array< int, kNumOverworldMaps > map_pointers1
void SyncAreaProperties(int map_index)
Copy the area parent's render settings (main palette, animated GFX, tile GFX groups,...
std::vector< gfx::Tile32 > tiles32_unique_
absl::Status SetLyraA0Tile32(int x, int y, uint16_t tile)
const std::vector< uint8_t > & current_graphics() const
absl::Status SaveMapProperties()
Save per-area graphics, palettes, and messages.
std::vector< std::pair< uint32_t, uint32_t > > GetProjectedWriteRanges() const
Get the projected write ranges (PC offsets) for overworld map saves.
absl::Status ValidatePrivateNativeTerrainSave() const
absl::Status SaveMap32Tiles()
Save tile32 definitions to ROM.
std::map< OverworldTilesetKey, TilesetCacheEntry > tileset_cache_
std::vector< OverworldEntrance > all_entrances_
absl::Status SaveTallAreaTransitions(int i, int parent_x_pos, int parent_y_pos, int transition_target_north, int transition_target_west, int transition_pos_x, int transition_pos_y, int screen_change_1, int screen_change_2, int screen_change_3, int screen_change_4)
Save screen transition data for tall (1x2) areas (v3+ only)
OverworldMapTiles map_tiles_
absl::Status SaveMap16Tiles()
Save tile16 definitions to ROM.
absl::Status SaveAreaSizes()
Save area size enum data (v3+ only)
absl::Status SaveSprites()
void InvalidateSiblingMapCaches(int map_index)
Invalidate cached tilesets for a map and all its siblings.
absl::Status SaveLargeMaps()
Save large map parent/sibling relationships.
void StageSavedMapTiles()
const std::vector< gfx::Tile16 > & tiles16() const
std::array< uint8_t, kNumOverworldMaps > map_parent_
void AssignWorldTiles(int x, int y, int sx, int sy, int tpos, OverworldBlockset &world)
absl::Status SaveDiggableTiles()
void InvalidateMapCache(int map_index)
Invalidate cached tileset for a specific map.
bool lyra_a0_terrain_writable() const
std::array< uint16_t, 256 > lyra_a0_tiles_
bool is_lyra_preview() const
std::array< uint8_t, kNumTileTypes > all_tiles_types_
int inspected_map_count() const
absl::StatusOr< SubscreenOverlayLayer > BuildSubscreenOverlayLayer(int map_index)
Build the subscreen overlay layer for a screen (builds the screen if needed). The overlay comes from ...
void LoadTileTypes()
Load tile type collision data.
absl::Status CreateTile32Tilemap()
Build tile32 tilemap from current tile16 data.
std::array< int, kNumOverworldMaps > map_pointers1_id
auto overworld_map(int i) const
uint64_t Tiles16Fingerprint() const
void SetGraphicsPreview(OverworldGraphicsPreview preview)
absl::Status SaveLargeAreaTransitions(int i, int parent_x_pos, int parent_y_pos, int transition_target_north, int transition_target_west, int transition_pos_x, int transition_pos_y, int screen_change_1, int screen_change_2, int screen_change_3, int screen_change_4)
Save screen transition data for large (2x2) areas.
OverworldBlockset & SelectWorldBlockset(int world_type)
bool HasExpandedPointerTables() const
Check if the ROM has expanded pointer tables for tail maps.
uint64_t tileset_cache_clock_
absl::Status SaveCustomOverworldASM(bool enable_bg_color, bool enable_main_palette, bool enable_mosaic, bool enable_gfx_groups, bool enable_subscreen_overlay, bool enable_animated)
Save custom ASM feature enable flags.
absl::Status SaveCustomOverworldData()
Save all v2/v3 property tables using the ROM's current feature flags.
absl::StatusOr< OverworldSpriteSavePlan > PrepareSpriteSave() const
void FillBlankMapTiles(int map_index)
auto mutable_overworld_map(int i)
bool MapTilesUnchanged() const
absl::Status SaveEntrances()
Save entrance warp points to ROM.
absl::Status SaveExits()
Save exit return points to ROM.
absl::Status LoadSprites()
Load sprite data for all game states.
bool has_tail_map_expansion_
absl::Status EnsureMapBuilt(int map_index)
Build a map on-demand if it hasn't been built yet.
void RollbackSavedMapTiles()
std::vector< OverworldMap > overworld_maps_
OverworldMapTiles loaded_map_tiles_
absl::Status SaveItems()
Save hidden overworld items to ROM.
absl::Status SaveAreaSpecificBGColors()
Save per-area background colors (v2+)
absl::Status LoadDiggableTiles()
absl::Status Save(Rom *rom)
Master save method (calls sub-methods in correct order)
void CommitSavedMapTiles()
absl::Status SaveWideAreaTransitions(int i, int parent_x_pos, int parent_y_pos, int transition_target_north, int transition_target_west, int transition_pos_x, int transition_pos_y, int screen_change_1, int screen_change_2, int screen_change_3, int screen_change_4)
Save screen transition data for wide (2x1) areas (v3+ only)
absl::Status LoadLyraTerrainPreview(Rom *rom)
absl::Status SaveOverworldMaps()
Save compressed map tile data to ROM.
std::array< std::vector< Sprite >, 3 > all_sprites_
OverworldVersion cached_version_
std::array< int, kNumOverworldMaps > map_pointers2_id
absl::Status LoadOverworldMaps()
Load overworld map tile data.
std::vector< gfx::Tile16 > tiles16_
absl::Status AutoDetectDiggableTiles()
TilesetRevisions SheetRevisionsForKey(const OverworldTilesetKey &key) const
OverworldGraphicsPreview graphics_preview_
absl::Status AssembleMap16Tiles()
absl::Status LoadSpritesFromMap(int sprite_start, int sprite_count, int sprite_index)
Load sprites from a specific map range.
std::vector< uint8_t > private_loaded_overlays_
bool loaded_map_tiles_valid_
void set_game_state(int state)
std::array< std::vector< uint8_t >, kNumOverworldMaps > map_data_p1
absl::Status SaveLargeMapsExpanded()
Save expanded large map data (v1+ ROMs)
void AssignMapSizes(std::vector< OverworldMap > &maps)
Assign map sizes based on area size enum (v3+)
absl::Status SaveMap16Expanded()
Save expanded tile16 definitions (v1+ ROMs)
absl::Status BuildLyraTerrainPreview(Rom *rom, rom::LyraLayoutInspection layout)
std::vector< OverworldExit > all_exits_
std::array< int, kNumOverworldMaps > map_pointers2
absl::StatusOr< uint16_t > GetTile16ForTile32(int index, int quadrant, int dimension, const uint32_t *map32address)
std::vector< gfx::Tile16 > private_loaded_tiles16_
std::array< std::vector< uint8_t >, kNumOverworldMaps > map_data_p2
absl::Status SaveSmallAreaTransitions(int i, int parent_x_pos, int parent_y_pos, int transition_target_north, int transition_target_west, int transition_pos_x, int transition_pos_y, int screen_change_1, int screen_change_2, int screen_change_3, int screen_change_4)
Save screen transition data for small (1x1) areas.
std::deque< int > built_map_lru_
absl::Status SaveMapOverlays()
Save interactive overlay data to ROM.
absl::Status AssembleMap32Tiles()
static constexpr size_t kMaxCachedTilesets
absl::StatusOr< std::array< uint8_t, kNumTileTypes > > GetTile8CollisionTypes(int map_id) const
DiggableTiles diggable_tiles_
static constexpr int kMaxBuiltMaps
std::array< std::array< bool, 160 >, 3 > sprite_maps_loaded_
OverworldBlockset & GetMapTiles(int world_type)
OverworldMapTiles staged_map_tiles_
absl::Status SaveOverworldTilesType()
Save tile type collision data to ROM.
absl::Status SaveMusic()
Save per-area music IDs.
absl::Status ConfigureMultiAreaMap(int parent_index, AreaSizeEnum size)
Configure a multi-area map structure (Large/Wide/Tall)
std::vector< OverworldEntrance > all_holes_
std::array< uint64_t, std::tuple_size_v< OverworldTilesetKey > > TilesetRevisions
struct destination destination
Room transition destination.
#define LOG_DEBUG(category, format,...)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
std::vector< uint8_t > HyruleMagicDecompress(uint8_t const *src, int *const size, int const p_big_endian, size_t max_src_size)
TileInfo GetTilesInfo(uint16_t tile)
std::vector< uint8_t > HyruleMagicCompress(uint8_t const *const src, int const oldsize, int *const size, int const flag)
std::optional< std::array< uint16_t, 256 > > LyraMapTile32(const LyraLayoutInspection &layout, const uint8_t *image, size_t size, size_t map_index)
std::string HexByte(uint8_t byte, HexStringParams params)
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
std::string HexLong(uint32_t dword, HexStringParams params)
std::vector< uint8_t > PrivateOverlaySnapshot(const std::vector< OverworldMap > &maps)
std::vector< uint64_t > GetAllTile16(OverworldMapTiles &map_tiles_)
absl::StatusOr< Map32StorageLayout > ResolveMap32StorageLayout(const Rom &rom, const zelda3_version_pointers &version_constants)
absl::Status ValidateMap32DefinitionCount(size_t definition_count, const Map32StorageLayout &layout)
LayoutDialect DetectLayout(const Rom &rom)
std::array< uint8_t, kResolvedSheetCount > ResolvedSheetIds
absl::Status ValidateSheetCollisionTable(const Rom &rom)
absl::StatusOr< Tile8Collisions > ComposeTile8Collisions(const Rom &rom, const ResolvedSheetIds &sheet_ids)
Zelda 3 specific classes and functions.
constexpr int kAreaGfxIdPtr
int GetOverworldMapParentIdExpanded()
absl::Status SaveEntrances(Rom *rom, const std::vector< OverworldEntrance > &entrances, bool expanded_entrances)
constexpr int OverworldCustomTileGFXGroupEnabled
std::vector< uint8_t > OverworldSpriteBytes
constexpr int OverworldCustomAreaSpecificBGEnabled
std::map< uint16_t, PreviewTile8 > AreaGraphicsPreview
constexpr int kOverworldTransitionPositionY
constexpr int kNumMapsPerWorld
constexpr int kOverworldSpriteset
int GetMap32TileBRExpanded()
int GetOverworldScreenChange3Expanded()
constexpr int kMap16ExpandedFlagPos
constexpr int NumberOfMap16Ex
int GetOverworldScreenChange4Expanded()
absl::StatusOr< std::vector< OverworldEntrance > > LoadEntrances(Rom *rom)
absl::Status SaveItems(Rom *rom, const std::vector< OverworldItem > &items)
constexpr int kOverworldScreenTileMapChangeByScreen1
constexpr int kOverworldMapDataOverflow
int LegacyParentTableIndexForMap(int map_index)
constexpr int kMap32BytesPerPackedGroup
constexpr int kOverworldMapSizeHighByte
absl::StatusOr< std::vector< OverworldItem > > LoadItems(Rom *rom, std::vector< OverworldMap > &overworld_maps)
constexpr int kNumTileTypes
uint8_t ReadRomByteOr(const Rom &rom, int address, uint8_t fallback)
constexpr int NumberOfMap32
constexpr int kOverworldScreenSize
constexpr int kOverworldScreenTileMapChangeByScreen4
constexpr int kNumTile16Individual
constexpr int kNumOverworldHoles
int GetMap32TileTRExpanded()
constexpr int kSpecialWorldMapIdStart
constexpr int OverworldCustomMosaicArray
constexpr int kMap16Tiles
int GetMap32TileBLExpanded()
constexpr int OverworldCustomAnimatedGFXEnabled
constexpr int Map32DefinitionCapacityForStorageBytes(int storage_bytes)
constexpr int OverworldCustomMainPaletteEnabled
constexpr int kNumOverworldMaps
absl::StatusOr< OverworldSpriteBytes > ReadOverworldSpriteList(const Rom &rom, int pointer_address)
constexpr int OverworldCustomMainPaletteArray
constexpr int kOverworldSpecialSpritePaletteExpandedTemp
absl::Status ApplyOverworldSpriteSave(Rom &rom, const OverworldSpriteSavePlan &plan)
constexpr int kOverworldSpecialSpriteGfxGroupExpandedTemp
constexpr int kOverworldMusicBeginning
std::vector< std::vector< uint16_t > > OverworldBlockset
Represents tile32 data for the overworld.
AreaSizeEnum
Area size enumeration for v3+ ROMs.
constexpr int kOverworldTransitionPositionX
std::array< std::array< std::optional< OverworldSpriteBytes >, 160 >, 3 > OverworldSpriteEdits
constexpr int kOverworldMusicDarkWorld
std::array< uint8_t, 17 > OverworldTilesetKey
constexpr int kOverworldSpecialPalGroup
constexpr int kOverworldScreenSizeForLoading
constexpr int kOverworldSpritePaletteIds
std::map< int, AreaGraphicsPreview > OverworldGraphicsPreview
constexpr int overworldTilesType
constexpr int transition_target_westExpanded
int GetOverworldScreenChange1Expanded()
absl::StatusOr< std::vector< OverworldExit > > LoadExits(Rom *rom)
constexpr int kMap32TileCountExpanded
constexpr int kTransitionTargetWest
constexpr int OverworldCustomASMHasBeenApplied
constexpr int kOverworldMusicAgahnim
constexpr int kOverworldMapParentId
absl::StatusOr< std::vector< gfx::Tile16 > > ReadMap16Tiles(const Rom &rom, bool *expanded)
Represents the full Overworld data, light and dark world.
constexpr int kMap32ExpandedFlagPos
constexpr int kMap32DefinitionsPerPackedGroup
int GetOverworldTransitionPositionXExpanded()
@ kZSCustomV1
Basic features, expanded pointers.
@ kVanilla
0xFF in ROM, no ZScream ASM applied
bool IsSameOverworldWorld(int a, int b)
constexpr int kOverworldPalettesScreenToSetNew
constexpr int kOverworldMusicMasterSword
constexpr int kOverworldMusicZelda
constexpr int transition_target_northExpanded
constexpr int NumberOfMap16
constexpr int kOverworldMapSize
constexpr int kOverworldScreenTileMapChangeByScreen2
constexpr int kOverworldSpecialSpritePalette
constexpr int OverworldCustomAnimatedGFXArray
constexpr int kDarkWorldMapIdStart
OverworldRomProfile DetectOverworldRomProfile(const Rom &rom)
absl::Status SaveHoles(Rom *rom, const std::vector< OverworldEntrance > &holes)
bool CanPersistLegacyScreenSize(int map_index)
absl::StatusOr< std::vector< OverworldEntrance > > LoadHoles(Rom *rom)
constexpr int OverworldCustomMosaicEnabled
constexpr int kEssentialMapsPerWorld
constexpr int kOverworldCompressedMapPos
constexpr int kMaxDiggableTileId
int WorldForOverworldMap(int map_index)
bool IsBackgroundSubscreenOverlay(uint16_t overlay_id)
absl::StatusOr< OverworldSpriteSavePlan > PlanOverworldSpriteSave(const Rom &rom, const OverworldSpriteEdits &edits)
constexpr int kOverworldSpecialSpriteGFXGroup
constexpr int kOverworldScreenTileMapChangeByScreen3
constexpr int OverworldCustomTileGFXGroupArray
bool CanPersistLegacyMultiAreaMap(int map_index)
uint8_t LegacyParentTableValueForMap(int parent_index)
int SubscreenOverlayScreen(uint16_t overlay_id)
int Map32StorageBytesForProfile(const OverworldRomProfile &profile)
constexpr int OverworldCustomSubscreenOverlayEnabled
constexpr int OverworldCustomAreaSpecificBGPalette
int GetOverworldMessagesExpanded()
int LegacyScreenSizeTableIndexForMap(int map_index)
OverworldSpriteLayout GetOverworldSpriteLayout(const Rom &rom)
constexpr int kTransitionTargetNorth
constexpr int kOverworldCompressedOverflowPos
int GetOverworldScreenChange2Expanded()
constexpr int kOverlayCodeStart
absl::Status SaveExits(Rom *rom, const std::vector< OverworldExit > &exits)
int GetMap16TilesExpanded()
constexpr int kOverworldMapPaletteIds
int GetOverworldTransitionPositionYExpanded()
constexpr int kOverworldSpecialGfxGroup
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
std::vector< OverworldSpriteWrite > OverworldSpriteSavePlan
constexpr int OverworldCustomSubscreenOverlayArray
uint32_t PcToSnes(uint32_t addr)
uint32_t SnesToPc(uint32_t addr) noexcept
#define RETURN_IF_ERROR(expr)
Room transition destination.
bool kEnableSpecialWorldExpansion
struct yaze::core::FeatureFlags::Flags::Overworld overworld
GraphicsSheetStore sheet_store
Overworld map tile32 data.
OverworldBlockset dark_world
OverworldBlockset special_world
OverworldBlockset light_world
bool has_expanded_entrances
Subscreen overlay layer for one screen, in that screen's palette.
std::vector< uint8_t > pixels
std::array< int, 4 > destinations
int storage_bytes_per_quadrant
ROM data pointers for different game versions.
uint32_t kOverworldTilesType
uint32_t kCompressedAllMap32PointersHigh
uint32_t kCompressedAllMap32PointersLow