yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld.cc
Go to the documentation of this file.
1#include "overworld.h"
2
3#include <algorithm>
4#include <array>
5#include <cstddef>
6#include <cstdint>
7#include <future>
8#include <iostream>
9#include <limits>
10#include <ostream>
11#include <set>
12#include <string>
13#include <unordered_map>
14#include <vector>
15
16#include "absl/status/status.h"
17#include "absl/status/statusor.h"
18#include "absl/strings/str_format.h"
22#include "core/features.h"
23#include "rom/rom.h"
24#include "rom/snes.h"
25#include "rom/transaction.h"
26#include "util/hex.h"
27#include "util/log.h"
28#include "util/macro.h"
29#include "zelda3/common.h"
37
38namespace yaze::zelda3 {
39
40namespace {
41
42std::vector<uint8_t> PrivateOverlaySnapshot(
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));
48 };
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());
58 }
59 return bytes;
60}
61
63 std::array<int, 4> destinations{};
64 int storage_bytes_per_quadrant = 0;
65 int definition_capacity = 0;
66 bool expanded = false;
67};
68
69absl::StatusOr<Map32StorageLayout> ResolveMap32StorageLayout(
70 const Rom& rom, const zelda3_version_pointers& version_constants) {
71 const auto profile = DetectOverworldRomProfile(rom);
72
73 Map32StorageLayout layout;
74 layout.expanded = profile.has_expanded_tile32;
75 layout.destinations = {
76 static_cast<int>(version_constants.kMap32TileTL),
78 : static_cast<int>(version_constants.kMap32TileTR),
80 : static_cast<int>(version_constants.kMap32TileBL),
82 : static_cast<int>(version_constants.kMap32TileBR),
83 };
84
85 int usable_bytes = Map32StorageBytesForProfile(profile);
86 for (int destination : layout.destinations) {
87 if (destination < 0 || static_cast<size_t>(destination) >= rom.size()) {
88 return absl::OutOfRangeError(absl::StrFormat(
89 "Tile32 destination 0x%06X is outside the ROM", destination));
90 }
91 const size_t available = rom.size() - static_cast<size_t>(destination);
92 usable_bytes = std::min(usable_bytes,
93 static_cast<int>(std::min<size_t>(
94 available, std::numeric_limits<int>::max())));
95 }
96
97 // ROM EOF is not the only boundary: quadrant tables must not grow into the
98 // next configured destination. This matters for layouts such as the JP ROM,
99 // whose vanilla TL/TR and BL/BR spans are 0x33C0 rather than 0x33F0 bytes.
100 auto ordered_destinations = layout.destinations;
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];
104 if (span <= 0) {
105 return absl::FailedPreconditionError(
106 "Tile32 destinations overlap or share the same address");
107 }
108 usable_bytes = std::min(usable_bytes, span);
109 }
110
111 // A partial packed group cannot store any additional definitions.
112 usable_bytes -= usable_bytes % kMap32BytesPerPackedGroup;
113 if (usable_bytes <= 0) {
114 return absl::ResourceExhaustedError(
115 "Tile32 destinations have no usable packed storage");
116 }
117
118 layout.storage_bytes_per_quadrant = usable_bytes;
119 layout.definition_capacity =
121 return layout;
122}
123
124absl::Status ValidateMap32DefinitionCount(size_t definition_count,
125 const Map32StorageLayout& layout) {
126 if (definition_count % kMap32DefinitionsPerPackedGroup != 0) {
127 return absl::InvalidArgumentError(absl::StrFormat(
128 "Tile32 definition count %zu is not aligned to a packed group of %d",
129 definition_count, kMap32DefinitionsPerPackedGroup));
130 }
131 if (definition_count > static_cast<size_t>(layout.definition_capacity)) {
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 "
135 "not been written",
136 definition_count, layout.definition_capacity,
137 layout.expanded ? "expanded" : "vanilla"));
138 }
139
140 const size_t required_bytes =
141 (definition_count / kMap32DefinitionsPerPackedGroup) *
143 if (required_bytes > static_cast<size_t>(layout.storage_bytes_per_quadrant)) {
144 return absl::ResourceExhaustedError(absl::StrFormat(
145 "Tile32 definitions require %zu bytes per quadrant; destination "
146 "capacity is %d bytes",
147 required_bytes, layout.storage_bytes_per_quadrant));
148 }
149 return absl::OkStatus();
150}
151
152} // namespace
153
155 game_state_ = std::clamp(state, 0, 2);
156}
157
158absl::Status Overworld::Load(Rom* rom) {
159 gfx::ScopedTimer timer("Overworld::Load");
160
161 if (rom->size() == 0) {
162 return absl::InvalidArgumentError("ROM file not loaded");
163 }
164 if (const auto* manifest = GetResourceLabels().hack_manifest()) {
165 RETURN_IF_ERROR(rom->AttachLyraPrivateReservation(*manifest));
166 }
167 const auto layout = rom->InspectLyraLayout();
168 if (layout.presence == yaze::rom::LyraLayoutPresence::kInvalid) {
169 return absl::FailedPreconditionError(
170 "Invalid Lyra descriptor; overworld loading refused");
171 }
172 if (layout.presence == yaze::rom::LyraLayoutPresence::kValid) {
173 return absl::FailedPreconditionError(
174 "Lyra descriptor canvas loading is not yet supported");
175 }
176 // Reload replaces ROM-derived state. In particular, CreateTile32Tilemap()
177 // may have reordered the in-memory definitions during an unchanged save;
178 // appending the ROM definitions would retain that stale index prefix.
179 // Destroy preserves the bound GameData, selection and graphics preview.
180 Destroy();
181 tiles32_list_.clear();
182 map_tiles_ = {};
185 expanded_tile16_ = false;
186 expanded_tile32_ = false;
188 rom_ = rom;
189
190 const auto profile = DetectOverworldRomProfile(*rom_);
191 // Cache ROM version to avoid repeated detection (called 160+ times otherwise)
192 cached_version_ = profile.version;
193 expanded_entrances_ = profile.has_expanded_entrances;
194 has_tail_map_expansion_ = profile.has_tail_map_expansion;
195
196 // Phase 1: Tile Assembly (can be parallelized)
197 {
198 gfx::ScopedTimer assembly_timer("AssembleTiles");
201 }
202
203 // Phase 2: Map Decompression (major bottleneck - now parallelized)
204 {
205 gfx::ScopedTimer decompression_timer("DecompressAllMapTiles");
207 }
208
209 // Phase 3: Map Object Creation (fast)
210 {
211 gfx::ScopedTimer map_creation_timer("CreateOverworldMapObjects");
212 for (int map_index = 0; map_index < kNumOverworldMaps; ++map_index)
213 overworld_maps_.emplace_back(map_index, rom_, game_data_);
214
215 // Populate map_parent_ array with parent information from each map
216 for (int map_index = 0; map_index < kNumOverworldMaps; ++map_index) {
217 map_parent_[map_index] = overworld_maps_[map_index].parent();
218 }
219 }
220
221 // Phase 4: Map Configuration (uses cached_version_ for performance)
224 } else {
226 }
227
228 // Phase 5: Data Loading (with individual timing)
229 {
230 gfx::ScopedTimer data_loading_timer("LoadOverworldData");
231
232 {
233 gfx::ScopedTimer tile_types_timer("LoadTileTypes");
235 }
236
237 {
238 gfx::ScopedTimer diggable_tiles_timer("LoadDiggableTiles");
240 }
241
242 {
243 gfx::ScopedTimer entrances_timer("LoadEntrances");
245 }
246
247 {
248 gfx::ScopedTimer holes_timer("LoadHoles");
250 }
251
252 {
253 gfx::ScopedTimer exits_timer("LoadExits");
255 }
256
257 {
258 gfx::ScopedTimer items_timer("LoadItems");
260 }
261
262 {
263 gfx::ScopedTimer overworld_maps_timer("LoadOverworldMaps");
265 }
266
267 {
268 gfx::ScopedTimer sprites_timer("LoadSprites");
270 }
271 }
272
274 // Overlay caches are otherwise lazy. Resolve every native map before the
275 // snapshot so browsing an unbuilt map cannot look like an authored edit.
276 for (auto& map : overworld_maps_)
277 RETURN_IF_ERROR(map.LoadOverlay());
278 private_loaded_overlays_ = PrivateOverlaySnapshot(overworld_maps_);
279 }
283 is_loaded_ = true;
284 return absl::OkStatus();
285}
286
288 if (rom == nullptr || rom->size() == 0) {
289 return absl::InvalidArgumentError("ROM file not loaded");
290 }
291 if (is_loaded_) {
292 return absl::FailedPreconditionError(
293 "Lyra terrain preview requires a fresh overworld model");
294 }
295 auto layout = rom->InspectLyraLayout();
296 if (layout.presence != yaze::rom::LyraLayoutPresence::kValid) {
297 return absl::FailedPreconditionError(
298 "Valid Lyra descriptor required for terrain preview");
299 }
300 return BuildLyraTerrainPreview(rom, std::move(layout));
301}
302
304 Rom* rom, rom::LyraLayoutInspection layout) {
305 // This path materializes descriptor terrain and render properties only.
306 // Sprite display and every serializer remain unavailable for this format.
307 rom_ = rom;
308 descriptor_layout_ = std::move(layout);
309 const auto profile = DetectOverworldRomProfile(*rom_);
310 cached_version_ = profile.version;
311 expanded_entrances_ = profile.has_expanded_entrances;
312 has_tail_map_expansion_ = profile.has_tail_map_expansion;
313 tiles16_.clear();
314 tiles32_unique_.clear();
315 overworld_maps_.clear();
316 built_map_lru_.clear();
317 tileset_cache_.clear();
320 for (int map_index = 0; map_index < descriptor_layout_.capacity;
321 ++map_index) {
323 rom_->size(), map_index);
324 if (!tiles) {
325 return absl::FailedPreconditionError(
326 absl::StrFormat("Lyra map %02X stream is invalid", map_index));
327 }
328 if (map_index == 0xA0) {
329 lyra_a0_tiles_ = *tiles;
330 }
331 const int local = map_index & 0x3F;
332 const int sx = local % 8;
333 const int sy = local / 8;
334 auto& world = SelectWorldBlockset(WorldForOverworldMap(map_index));
335 for (int y = 0; y < 16; ++y) {
336 for (int x = 0; x < 16; ++x) {
337 const int tile = (*tiles)[y * 16 + x];
338 if (tile >= static_cast<int>(tiles32_unique_.size())) {
339 return absl::FailedPreconditionError(
340 "Lyra Tile32 definition unavailable");
341 }
342 AssignWorldTiles(x, y, sx, sy, tile, world);
343 }
344 }
346 auto map, OverworldMap::CreateLyraPreview(map_index, rom_, game_data_,
348 overworld_maps_.push_back(std::move(map));
349 if (map_index < kNumOverworldMaps) {
350 map_parent_[map_index] = overworld_maps_.back().parent();
351 }
352 }
355 is_loaded_ = true;
356 return absl::OkStatus();
357}
358
359absl::Status Overworld::SetLyraA0Tile32(int x, int y, uint16_t tile) {
361 return absl::FailedPreconditionError("Lyra A0 terrain is read-only");
362 }
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");
366 }
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");
370 }
371 if (lyra_a0_tiles_[y * 16 + x] == tile) {
372 return absl::OkStatus();
373 }
374 // Reuse one existing Tile32 definition; never rebuild shared definitions.
375 AssignWorldTiles(x, y, 0, 4, tile, map_tiles_.special_world);
376 lyra_a0_tiles_[y * 16 + x] = tile;
377 overworld_maps_[0xA0].SetNotBuilt();
378 return absl::OkStatus();
379}
380
382 // For vanilla/v1/v2 ROMs, parent IDs are already loaded from ROM in the
383 // OverworldMap constructor. This function now uses those ROM values instead
384 // of recalculating them, ensuring custom parent mappings are respected.
385 //
386 // The function determines large_index_ (quadrant) based on the relationship
387 // between map index and its parent, rather than grid-walking.
388
389 // First pass: Set up special world maps using ROM data
390 for (int i = 128; i < kNumOverworldMaps; i++) {
391 int parent = overworld_maps_[i].parent();
392
393 if (overworld_maps_[i].is_large_map()) {
394 // Calculate quadrant based on position relative to parent
395 int quadrant = 0;
396 if (i == parent) {
397 quadrant = 0; // Top-left (parent itself)
398 } else if (i == parent + 1) {
399 quadrant = 1; // Top-right
400 } else if (i == parent + 8) {
401 quadrant = 2; // Bottom-left
402 } else if (i == parent + 9) {
403 quadrant = 3; // Bottom-right
404 }
405 // Use SetAsLargeMap but pass the ROM parent value, not a calculated one
406 overworld_maps_[i].SetAsLargeMap(parent, quadrant);
407 } else {
408 overworld_maps_[i].SetAsSmallMap(i);
409 }
410 }
411
412 // Track visited maps across LW/DW (0x00-0x7F)
413 std::array<bool, kNumOverworldMaps> map_checked{};
414 std::ranges::fill(map_checked, false);
415
416 // Second pass: Process LW/DW maps using ROM parent values
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;
420
421 if (map_checked[i])
422 continue;
423
424 int parent = overworld_maps_[i].parent();
425
426 if (overworld_maps_[i].is_large_map()) {
427 // This map is part of a large area - set up all 4 quadrants
428 // The parent value from ROM tells us which map is the parent
429
430 // Calculate quadrant based on position relative to parent
431 int quadrant = 0;
432 if (i == parent) {
433 quadrant = 0; // This IS the parent (top-left)
434 } else if (i == parent + 1) {
435 quadrant = 1; // Top-right
436 } else if (i == parent + 8) {
437 quadrant = 2; // Bottom-left
438 } else if (i == parent + 9) {
439 quadrant = 3; // Bottom-right
440 }
441
442 overworld_maps_[i].SetAsLargeMap(parent, quadrant);
443 map_checked[i] = true;
444
445 // Mark siblings as checked and set their quadrants
446 // Use the ROM parent value for all siblings
447 // Ensure siblings stay within the same world to prevent cross-world issues
448 std::array<int, 4> siblings = {parent, parent + 1, parent + 8,
449 parent + 9};
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];
454 // Check sibling is within the same world (LW: 0-63, DW: 64-127)
455 if (sibling >= world_start && sibling < world_end &&
456 !map_checked[sibling]) {
457 overworld_maps_[sibling].SetAsLargeMap(parent, q);
458 map_checked[sibling] = true;
459 }
460 }
461 } else {
462 // Small map - parent should be itself
463 overworld_maps_[i].SetAsSmallMap(i);
464 map_checked[i] = true;
465 }
466 }
467 }
468}
469
474void Overworld::AssignMapSizes(std::vector<OverworldMap>& maps) {
475 std::vector<bool> map_checked(kNumOverworldMaps, false);
476
477 int xx = 0;
478 int yy = 0;
479 int world = 0;
480
481 while (true) {
482 int i = world + xx + (yy * 8);
483
484 if (i >= static_cast<int>(map_checked.size())) {
485 break;
486 }
487
488 if (!map_checked[i]) {
489 switch (maps[i].area_size()) {
491 map_checked[i] = true;
492 maps[i].SetAreaSize(AreaSizeEnum::SmallArea);
493 break;
494
496 map_checked[i] = true;
497 maps[i].SetAsLargeMap(i, 0);
498
499 if (i + 1 < static_cast<int>(maps.size())) {
500 map_checked[i + 1] = true;
501 maps[i + 1].SetAsLargeMap(i, 1);
502 }
503
504 if (i + 8 < static_cast<int>(maps.size())) {
505 map_checked[i + 8] = true;
506 maps[i + 8].SetAsLargeMap(i, 2);
507 }
508
509 if (i + 9 < static_cast<int>(maps.size())) {
510 map_checked[i + 9] = true;
511 maps[i + 9].SetAsLargeMap(i, 3);
512 }
513
514 xx++;
515 break;
516
518 map_checked[i] = true;
519 // Map i is parent (left, quadrant 0), map i+1 is child (right,
520 // quadrant 1).
521 maps[i].SetAsWideMap(i, 0);
522
523 if (i + 1 < static_cast<int>(maps.size())) {
524 map_checked[i + 1] = true;
525 maps[i + 1].SetAsWideMap(i, 1);
526 }
527
528 xx++;
529 break;
530
532 map_checked[i] = true;
533 // Map i is parent (top, quadrant 0), map i+8 is child (bottom,
534 // quadrant 2).
535 maps[i].SetAsTallMap(i, 0);
536
537 if (i + 8 < static_cast<int>(maps.size())) {
538 map_checked[i + 8] = true;
539 maps[i + 8].SetAsTallMap(i, 2);
540 }
541 break;
542 }
543 }
544
545 xx++;
546 if (xx >= 8) {
547 xx = 0;
548 yy += 1;
549
550 if (yy >= 8) {
551 yy = 0;
552 world += 0x40;
553 }
554 }
555 }
556}
557
558absl::Status Overworld::ConfigureMultiAreaMap(int parent_index,
559 AreaSizeEnum size) {
560 if (parent_index < 0 || parent_index >= kNumOverworldMaps) {
561 return absl::InvalidArgumentError(
562 absl::StrFormat("Invalid parent index: %d", parent_index));
563 }
564
565 // Version requirements:
566 // - Vanilla (0xFF): Supports Small and Large only
567 // - v1-v2: Supports Small and Large only
568 // - v3+: Supports all 4 sizes (Small, Large, Wide, Tall)
569 if ((size == AreaSizeEnum::WideArea || size == AreaSizeEnum::TallArea) &&
571 return absl::FailedPreconditionError(
572 "Wide and Tall areas require ZSCustomOverworld v3+");
573 }
575 !CanPersistLegacyMultiAreaMap(parent_index)) {
576 return absl::FailedPreconditionError(
577 "Special World multi-area changes require ZSCustomOverworld v3+");
578 }
579
580 LOG_DEBUG("Overworld",
581 "ConfigureMultiAreaMap: parent=%d, current_size=%d, new_size=%d, "
582 "version=%s",
583 parent_index,
584 static_cast<int>(overworld_maps_[parent_index].area_size()),
585 static_cast<int>(size),
587
588 // CRITICAL: First, get OLD siblings (before changing) so we can reset them
589 std::vector<int> old_siblings;
590 auto old_size = overworld_maps_[parent_index].area_size();
591 int old_parent = overworld_maps_[parent_index].parent();
592
593 switch (old_size) {
595 old_siblings = {old_parent, old_parent + 1, old_parent + 8,
596 old_parent + 9};
597 break;
599 old_siblings = {old_parent, old_parent + 1};
600 break;
602 old_siblings = {old_parent, old_parent + 8};
603 break;
604 default:
605 old_siblings = {parent_index}; // Was small, just this map
606 break;
607 }
608
609 // Validate the NEW sibling set before mutating so invalid edge maps do not
610 // partially reset the old area.
611 std::vector<int> new_siblings;
612 switch (size) {
614 new_siblings = {parent_index};
615 break;
617 new_siblings = {parent_index, parent_index + 1, parent_index + 8,
618 parent_index + 9};
619 break;
621 new_siblings = {parent_index, parent_index + 1};
622 break;
624 new_siblings = {parent_index, parent_index + 8};
625 break;
626 }
627 for (int sibling : new_siblings) {
628 if (sibling < 0 || sibling >= kNumOverworldMaps ||
629 !IsSameOverworldWorld(parent_index, sibling)) {
630 return absl::FailedPreconditionError(
631 absl::StrFormat("Area size crosses an overworld boundary: parent=%d "
632 "sibling=%d",
633 parent_index, sibling));
634 }
635 }
636
637 // Reset all old siblings to SmallArea first (clean slate).
638 for (int old_sibling : old_siblings) {
639 if (old_sibling >= 0 && old_sibling < kNumOverworldMaps) {
640 overworld_maps_[old_sibling].SetAsSmallMap(old_sibling);
641 }
642 }
643
644 // Now configure NEW siblings based on requested size.
645 switch (size) {
647 overworld_maps_[parent_index].SetParent(parent_index);
648 overworld_maps_[parent_index].SetAreaSize(AreaSizeEnum::SmallArea);
649 break;
650
652 for (size_t i = 0; i < new_siblings.size(); ++i) {
653 overworld_maps_[new_siblings[i]].SetAsLargeMap(parent_index, i);
654 }
655 break;
656
658 overworld_maps_[new_siblings[0]].SetAsWideMap(parent_index, 0);
659 overworld_maps_[new_siblings[1]].SetAsWideMap(parent_index, 1);
660 break;
661
663 overworld_maps_[new_siblings[0]].SetAsTallMap(parent_index, 0);
664 overworld_maps_[new_siblings[1]].SetAsTallMap(parent_index, 2);
665 break;
666 }
667
668 // Update ROM data for ALL affected siblings (old + new)
669 std::set<int> all_affected;
670 for (int sibling : old_siblings) {
671 all_affected.insert(sibling);
672 }
673 for (int sibling : new_siblings) {
674 all_affected.insert(sibling);
675 }
676
678 // v3+: Update expanded tables
679 for (int sibling : all_affected) {
680 if (sibling < 0 || sibling >= kNumOverworldMaps)
681 continue;
682
684 rom()->WriteByte(GetOverworldMapParentIdExpanded() + sibling,
685 overworld_maps_[sibling].parent()));
686 RETURN_IF_ERROR(rom()->WriteByte(
687 kOverworldScreenSize + sibling,
688 static_cast<uint8_t>(overworld_maps_[sibling].area_size())));
689 }
690 } else {
691 // Vanilla/v1/v2: the parent and size tables are 64-entry legacy tables
692 // shared by Light World and Dark World. Store local indices only; writing
693 // full map ids would run into adjacent ROM data and cannot persist Special
694 // World layout changes.
695 for (int sibling : all_affected) {
696 if (!CanPersistLegacyMultiAreaMap(sibling)) {
697 continue;
698 }
699
700 const int parent_table_index = LegacyParentTableIndexForMap(sibling);
701 RETURN_IF_ERROR(rom()->WriteByte(
702 kOverworldMapParentId + parent_table_index,
704 if (CanPersistLegacyScreenSize(sibling)) {
705 const int screen_size_index = LegacyScreenSizeTableIndexForMap(sibling);
706 RETURN_IF_ERROR(rom()->WriteByte(
707 kOverworldScreenSize + screen_size_index,
708 (overworld_maps_[sibling].area_size() == AreaSizeEnum::LargeArea)
709 ? 0x00
710 : 0x01));
711 }
712 }
713 }
714
715 LOG_DEBUG("Overworld",
716 "Configured %s area: parent=%d, old_siblings=%zu, new_siblings=%zu",
717 (size == AreaSizeEnum::LargeArea) ? "Large"
718 : (size == AreaSizeEnum::WideArea) ? "Wide"
719 : (size == AreaSizeEnum::TallArea) ? "Tall"
720 : "Small",
721 parent_index, old_siblings.size(), new_siblings.size());
722
723 return absl::OkStatus();
724}
725
726absl::StatusOr<uint16_t> Overworld::GetTile16ForTile32(
727 int index, int quadrant, int dimension, const uint32_t* map32address) {
729 auto arg1, rom()->ReadByte(map32address[dimension] + quadrant + (index)));
730 ASSIGN_OR_RETURN(auto arg2,
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));
735}
736
738 constexpr int kMap32TilesLength = 0x33F0;
739 int num_tile32 = kMap32TilesLength;
740 uint32_t map32address[4] = {
743
744 // Check if expanded tile32 data is actually present in ROM
745 // The flag position should contain 0x04 for vanilla, something else for
746 // expanded
747 const auto profile = DetectOverworldRomProfile(*rom());
748 uint8_t expanded_flag = ReadRomByteOr(*rom(), kMap32ExpandedFlagPos, 0x04);
749 util::logf("Expanded tile32 flag: %d", expanded_flag);
750 if (profile.has_expanded_tile32) {
751 // ROM has expanded tile32 data - use expanded addresses
752 map32address[0] = version_constants().kMap32TileTL;
753 map32address[1] = GetMap32TileTRExpanded();
754 map32address[2] = GetMap32TileBLExpanded();
755 map32address[3] = GetMap32TileBRExpanded();
756 num_tile32 = kMap32TileCountExpanded;
757 expanded_tile32_ = true;
758 }
759 // Otherwise use vanilla addresses (already set above)
760
761 // Loop through each 32x32 pixel tile in the rom
762 for (int i = 0; i < num_tile32; i += 6) {
763 // Loop through each quadrant of the 32x32 pixel tile.
764 for (int k = 0; k < 4; k++) {
765 // Generate the 16-bit tile for the current quadrant of the current
766 // 32x32 pixel tile.
768 uint16_t tl,
769 GetTile16ForTile32(i, k, (int)Dimension::map32TilesTL, map32address));
771 uint16_t tr,
772 GetTile16ForTile32(i, k, (int)Dimension::map32TilesTR, map32address));
774 uint16_t bl,
775 GetTile16ForTile32(i, k, (int)Dimension::map32TilesBL, map32address));
777 uint16_t br,
778 GetTile16ForTile32(i, k, (int)Dimension::map32TilesBR, map32address));
779
780 // Add the generated 16-bit tiles to the tiles32 vector.
781 tiles32_unique_.emplace_back(gfx::Tile32(tl, tr, bl, br));
782 }
783 }
784
785 map_tiles_.light_world.resize(0x200);
786 map_tiles_.dark_world.resize(0x200);
787 map_tiles_.special_world.resize(0x200);
788 for (int i = 0; i < 0x200; i++) {
789 map_tiles_.light_world[i].resize(0x200);
790 map_tiles_.dark_world[i].resize(0x200);
791 map_tiles_.special_world[i].resize(0x200);
792 }
793
794 return absl::OkStatus();
795}
796
797absl::StatusOr<std::vector<gfx::Tile16>> ReadMap16Tiles(const Rom& rom,
798 bool* expanded) {
799 int tpos = kMap16Tiles;
800 int num_tile16 = kNumTile16Individual;
801
802 // Check if expanded tile16 data is actually present in ROM
803 // The flag position should contain 0x0F for vanilla, something else for
804 // expanded
805 const auto profile = DetectOverworldRomProfile(rom);
806 uint8_t expanded_flag = ReadRomByteOr(rom, kMap16ExpandedFlagPos, 0x0F);
807 util::logf("Expanded tile16 flag: %d", expanded_flag);
808 if (expanded != nullptr) {
809 *expanded = profile.has_expanded_tile16;
810 }
811 if (profile.has_expanded_tile16) {
812 // ROM has expanded tile16 data - use expanded addresses
813 tpos = GetMap16TilesExpanded();
814 num_tile16 = NumberOfMap16Ex;
815 }
816 // Otherwise use vanilla addresses (already set above)
817
818 std::vector<gfx::Tile16> tiles16;
819 tiles16.reserve(num_tile16);
820 for (int i = 0; i < num_tile16; i += 1) {
821 ASSIGN_OR_RETURN(auto t0_data, rom.ReadWord(tpos));
822 gfx::TileInfo t0 = gfx::GetTilesInfo(t0_data);
823 tpos += 2;
824 ASSIGN_OR_RETURN(auto t1_data, rom.ReadWord(tpos));
825 gfx::TileInfo t1 = gfx::GetTilesInfo(t1_data);
826 tpos += 2;
827 ASSIGN_OR_RETURN(auto t2_data, rom.ReadWord(tpos));
828 gfx::TileInfo t2 = gfx::GetTilesInfo(t2_data);
829 tpos += 2;
830 ASSIGN_OR_RETURN(auto t3_data, rom.ReadWord(tpos));
831 gfx::TileInfo t3 = gfx::GetTilesInfo(t3_data);
832 tpos += 2;
833 tiles16.emplace_back(t0, t1, t2, t3);
834 }
835 return tiles16;
836}
837
839 bool expanded = false;
840 ASSIGN_OR_RETURN(auto tiles16, ReadMap16Tiles(*rom(), &expanded));
841 if (expanded) {
842 expanded_tile16_ = true;
843 }
844 for (auto& tile16 : tiles16) {
845 tiles16_.push_back(tile16);
846 }
847 return absl::OkStatus();
848}
849
850void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
851 OverworldBlockset& world) {
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);
856 world[position_x1][position_y1] = tiles32_unique_[tpos].tile0_;
857 world[position_x2][position_y1] = tiles32_unique_[tpos].tile1_;
858 world[position_x1][position_y2] = tiles32_unique_[tpos].tile2_;
859 world[position_x2][position_y2] = tiles32_unique_[tpos].tile3_;
860}
861
863 switch (world_type) {
864 case 0:
865 return map_tiles_.light_world;
866 case 1:
867 return map_tiles_.dark_world;
868 default:
870 }
871}
872
873void Overworld::FillBlankMapTiles(int map_index) {
874 int world_type = 0;
875 if (map_index >= kDarkWorldMapIdStart &&
876 map_index < kSpecialWorldMapIdStart) {
877 world_type = 1;
878 } else if (map_index >= kSpecialWorldMapIdStart) {
879 world_type = 2;
880 }
881
882 int local_index = map_index % 64;
883 int sx = local_index % 8;
884 int sy = local_index / 8;
885
886 auto& world = SelectWorldBlockset(world_type);
887 // Fill the 32x32 tile16 region for this map with tile 0
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;
891 }
892 }
893}
894
895void Overworld::OrganizeMapTiles(std::vector<uint8_t>& bytes,
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]);
901 if (int tpos = tidD; tpos < tiles32_unique_.size()) {
902 if (i < kDarkWorldMapIdStart) {
903 AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.light_world);
904 } else if (i < kSpecialWorldMapIdStart && i >= kDarkWorldMapIdStart) {
905 AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.dark_world);
906 } else {
907 AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.special_world);
908 }
909 }
910 ttpos += 1;
911 }
912 }
913}
914
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)]);
920 return SnesToPc(p);
921 };
922
923 constexpr uint32_t kBaseLowest = 0x0FFFFF;
924 constexpr uint32_t kBaseHighest = 0x0F8000;
925
926 uint32_t lowest = kBaseLowest;
927 uint32_t highest = kBaseHighest;
928 int sx = 0;
929 int sy = 0;
930 int c = 0;
931 // Tail maps (0xA0-0xBF) require BOTH:
932 // 1. Feature flag enabled in settings
933 // 2. TailMapExpansion.asm patch applied to ROM (marker at 0x1423FF)
934 const bool allow_special_tail =
937
938 for (int i = 0; i < kNumOverworldMaps; i++) {
939 // Guard: skip building tail special maps unless expansion is available
940 if (!allow_special_tail &&
941 i >= kSpecialWorldMapIdStart + 0x20) { // 0xA0-0xBF
943 sx++;
944 if (sx >= 8) {
945 sy++;
946 sx = 0;
947 }
948 c++;
949 if (c >= 64) {
950 sx = 0;
951 sy = 0;
952 c = 0;
953 }
954 continue;
955 }
956
957 auto p1 = get_ow_map_gfx_ptr(
958 i, version_constants().kCompressedAllMap32PointersHigh);
959 auto p2 = get_ow_map_gfx_ptr(
960 i, version_constants().kCompressedAllMap32PointersLow);
961
962 int ttpos = 0;
963
964 bool pointers_valid =
965 (p1 > 0 && p2 > 0 && p1 < rom()->size() && p2 < rom()->size());
966 if (!pointers_valid) {
967 // Missing/invalid pointers -> use blank map tiles to avoid crashes
969 sx++;
970 if (sx >= 8) {
971 sy++;
972 sx = 0;
973 }
974 c++;
975 if (c >= 64) {
976 sx = 0;
977 sy = 0;
978 c = 0;
979 }
980 continue;
981 }
982
983 if (p1 >= highest)
984 highest = p1;
985 if (p2 >= highest)
986 highest = p2;
987
988 if (p1 <= lowest && p1 > kBaseHighest)
989 lowest = p1;
990 if (p2 <= lowest && p2 > kBaseHighest)
991 lowest = p2;
992
993 int size1, size2;
994 size_t max_size_p2 = rom()->size() - p2;
995 auto bytes =
996 gfx::HyruleMagicDecompress(rom()->data() + p2, &size1, 1, max_size_p2);
997 size_t max_size_p1 = rom()->size() - p1;
998 auto bytes2 =
999 gfx::HyruleMagicDecompress(rom()->data() + p1, &size2, 1, max_size_p1);
1000
1001 // If decompression fails, use blank tiles to keep map index usable
1002 if (bytes.empty() || bytes2.empty()) {
1004 } else {
1005 OrganizeMapTiles(bytes, bytes2, i, sx, sy, ttpos);
1006 }
1007
1008 sx++;
1009 if (sx >= 8) {
1010 sy++;
1011 sx = 0;
1012 }
1013
1014 c++;
1015 if (c >= 64) {
1016 sx = 0;
1017 sy = 0;
1018 c = 0;
1019 }
1020 }
1021
1022 return absl::OkStatus();
1023}
1024
1026 [[maybe_unused]] auto size = tiles16_.size(); // native async path
1027
1028 // Performance optimization: Only build essential maps initially
1029 // Essential maps are the first few maps of each world that are commonly
1030 // accessed
1031 constexpr int kLightWorldEssential = yaze::zelda3::kEssentialMapsPerWorld;
1032 constexpr int kDarkWorldEssential =
1034 constexpr int kSpecialWorldEssential =
1036 std::vector<int> essential_map_ids;
1037 essential_map_ids.reserve(yaze::zelda3::kEssentialMapsPerWorld * 3);
1038
1039 util::logf(
1040 "Building essential maps only (first %d maps per world) for faster "
1041 "loading",
1043
1044#ifdef __EMSCRIPTEN__
1045 // WASM: Use sequential loading to avoid spawning excessive Web Workers
1046 // and blocking the main thread. std::async creates new pthreads which
1047 // become Web Workers, and future.wait() blocks the main thread which
1048 // is dangerous in browsers.
1049 for (int i = 0; i < kNumOverworldMaps; ++i) {
1050 bool is_essential = false;
1051
1052 if (i < kLightWorldEssential) {
1053 is_essential = true;
1054 } else if (i >= kDarkWorldMapIdStart && i < kDarkWorldEssential) {
1055 is_essential = true;
1056 } else if (i >= kSpecialWorldMapIdStart && i < kSpecialWorldEssential) {
1057 is_essential = true;
1058 }
1059
1060 if (is_essential) {
1061 essential_map_ids.push_back(i);
1062 int world_type = 0;
1064 world_type = 1;
1065 } else if (i >= kSpecialWorldMapIdStart) {
1066 world_type = 2;
1067 }
1068
1069 // CRITICAL: Set game_state_ BEFORE LoadAreaGraphics() because
1070 // LoadSpritesBlocksets() uses game_state_ to determine static_graphics_[12-15]
1071 overworld_maps_[i].set_game_state(game_state_);
1072
1073 // Apply large map child handling BEFORE computing hash
1074 // Must match LoadAreaInfo() logic exactly for SW maps
1075 auto* map = &overworld_maps_[i];
1076 if (map->is_large_map() &&
1078 if (map->parent() != i && !map->is_initialized()) {
1079 if (i >= kSpecialWorldMapIdStart && i <= 0x8A && i != 0x88) {
1080 // Zora's Domain children - also set sprite_graphics
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(
1086 (map->parent() - kSpecialWorldMapIdStart)]);
1087 map->set_area_palette((*rom_)[kOverworldSpecialPalGroup + 1]);
1088 } else if (i == 0x88 || i == 0x93) {
1089 map->set_area_graphics(0x51);
1090 map->set_area_palette(0x00);
1091 } else if (i < kSpecialWorldMapIdStart) {
1092 // LW/DW large map child - use parent's graphics
1093 map->set_area_graphics((*rom_)[kAreaGfxIdPtr + map->parent()]);
1094 map->set_area_palette(
1095 (*rom_)[kOverworldMapPaletteIds + map->parent()]);
1096 }
1097 // Note: Other SW maps (>0x8A) keep their LoadAreaInfo values
1098 }
1099 }
1100
1101 // Reuse cached tilesets to reduce load time on WASM
1104 built_map_lru_.push_front(i);
1105 } else {
1106 overworld_maps_[i].SetNotBuilt();
1107 }
1108 }
1109#else
1110 // Native: Use parallel loading with std::async for faster performance
1111 std::vector<std::future<absl::Status>> futures;
1112
1113 // Build essential maps only
1114 for (int i = 0; i < kNumOverworldMaps; ++i) {
1115 bool is_essential = false;
1116
1117 // Check if this is an essential map
1118 if (i < kLightWorldEssential) {
1119 is_essential = true;
1120 } else if (i >= kDarkWorldMapIdStart && i < kDarkWorldEssential) {
1121 is_essential = true;
1122 } else if (i >= kSpecialWorldMapIdStart && i < kSpecialWorldEssential) {
1123 is_essential = true;
1124 }
1125
1126 if (is_essential) {
1127 int world_type = 0;
1129 world_type = 1;
1130 } else if (i >= kSpecialWorldMapIdStart) {
1131 world_type = 2;
1132 }
1133
1135 auto task_function = [this, i, size, world_type]() {
1136 return overworld_maps_[i].BuildMap(size, game_state_, world_type,
1137 tiles16_, GetMapTiles(world_type));
1138 };
1139 futures.emplace_back(std::async(std::launch::async, task_function));
1140 } else {
1141 // Mark non-essential maps as not built yet
1142 overworld_maps_[i].SetNotBuilt();
1143 }
1144 }
1145
1146 // Wait for essential maps to complete
1147 for (auto& future : futures) {
1148 future.wait();
1149 RETURN_IF_ERROR(future.get());
1150 }
1151
1152 built_map_lru_.clear();
1153 for (int map_index : essential_map_ids) {
1154 built_map_lru_.push_front(map_index);
1155 }
1156#endif
1157
1158 util::logf("Essential maps built. Remaining maps will be built on-demand.");
1159 return absl::OkStatus();
1160}
1161
1162absl::Status Overworld::EnsureMapBuilt(int map_index) {
1163 const bool descriptor_preview = is_lyra_preview();
1164 const int map_count =
1165 descriptor_preview ? descriptor_layout_.capacity : kNumOverworldMaps;
1166 if (map_index < 0 || map_index >= map_count) {
1167 return absl::InvalidArgumentError("Invalid map index");
1168 }
1169
1170 // Tail maps (0xA0-0xBF) require BOTH:
1171 // 1. Feature flag enabled in settings
1172 // 2. TailMapExpansion.asm patch applied to ROM (marker at 0x1423FF)
1173 const bool allow_special_tail =
1176 if (!descriptor_preview && !allow_special_tail &&
1177 map_index >= kSpecialWorldMapIdStart + 0x20) { // 0xA0-0xBF
1178 // Do not attempt to build disabled special-tail maps; keep them blank-safe.
1179 // This prevents pointer table corruption from attempting to access
1180 // non-existent entries beyond vanilla's 160-entry limit.
1181 FillBlankMapTiles(map_index);
1182 return absl::OkStatus();
1183 }
1184
1185 // Check if map is already built
1186 if (overworld_maps_[map_index].is_built()) {
1187 // Move to front of LRU (most recently used)
1188 auto it =
1189 std::find(built_map_lru_.begin(), built_map_lru_.end(), map_index);
1190 if (it != built_map_lru_.end()) {
1191 built_map_lru_.erase(it);
1192 }
1193 built_map_lru_.push_front(map_index);
1194 return absl::OkStatus();
1195 }
1196
1197 // Evict oldest maps if cache is full (LRU eviction)
1198 while (static_cast<int>(built_map_lru_.size()) >= kMaxBuiltMaps) {
1199 int oldest_map = built_map_lru_.back();
1200 built_map_lru_.pop_back();
1201 // Destroy the oldest map to free memory. Its shared tileset cache entry
1202 // stays: entries are keyed by sheet ids, not by map.
1203 overworld_maps_[oldest_map].Destroy();
1204 }
1205
1206 // Build the map on-demand
1207 int world_type = 0;
1208 if (map_index >= kDarkWorldMapIdStart &&
1209 map_index < kSpecialWorldMapIdStart) {
1210 world_type = 1;
1211 } else if (map_index >= kSpecialWorldMapIdStart) {
1212 world_type = 2;
1213 }
1214
1215 // CRITICAL: Set game_state_ BEFORE LoadAreaGraphics() because
1216 // LoadSpritesBlocksets() uses game_state_ to determine static_graphics_[12-15]
1217 overworld_maps_[map_index].set_game_state(game_state_);
1218
1219 // Apply large map child handling BEFORE computing hash
1220 // This mirrors the logic in BuildMapWithCache that modifies area_graphics_
1221 // for large map children in vanilla ROMs - must happen before hash
1222 auto* map = &overworld_maps_[map_index];
1223 if (map->is_large_map() && cached_version_ == OverworldVersion::kVanilla) {
1224 if (map->parent() != map_index && !map->is_initialized()) {
1225 // Large map child in vanilla ROM - apply special graphics handling
1226 // Must match LoadAreaInfo() logic exactly for SW maps
1227 if (map_index >= kSpecialWorldMapIdStart && map_index <= 0x8A &&
1228 map_index != 0x88) {
1229 // Zora's Domain children - also set sprite_graphics
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(
1235 (map->parent() - kSpecialWorldMapIdStart)]);
1236 map->set_area_palette((*rom_)[kOverworldSpecialPalGroup + 1]);
1237 } else if (map_index == 0x88 || map_index == 0x93) {
1238 map->set_area_graphics(0x51);
1239 map->set_area_palette(0x00);
1240 } else if (map_index < kSpecialWorldMapIdStart) {
1241 // LW/DW large map child - use parent's graphics
1242 map->set_area_graphics((*rom_)[kAreaGfxIdPtr + map->parent()]);
1243 map->set_area_palette((*rom_)[kOverworldMapPaletteIds + map->parent()]);
1244 }
1245 // Note: Other SW maps (>0x8A) keep their LoadAreaInfo values
1246 }
1247 }
1248
1249 // Child screens render with the area parent's settings (game reads $8A).
1250 if (map_index < kNumOverworldMaps) {
1251 SyncAreaProperties(map_index);
1252 }
1253
1254 auto status = BuildMapWithTilesetCache(map_index, world_type);
1255 if (status.ok()) {
1256 // Add to front of LRU cache
1257 built_map_lru_.push_front(map_index);
1258 }
1259 return status;
1260}
1261
1263 uint64_t hash = 0xcbf29ce484222325ULL;
1264 auto mix = [&hash](uint64_t value) {
1265 hash ^= value;
1266 hash *= 0x100000001b3ULL;
1267 };
1268 mix(tiles16_.size());
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));
1275 }
1276 }
1277 return hash;
1278}
1279
1280// Builds a map, reusing the 64KB tileset and 1MB tile16 blockset of any
1281// earlier map that loaded the same 17 sheets. Only the palette, overlay data,
1282// and the map's own bitmap are rebuilt on a cache hit.
1283absl::Status Overworld::BuildMapWithTilesetCache(int map_index,
1284 int world_type) {
1285 auto& map = overworld_maps_[map_index];
1286 map.LoadAreaGraphics();
1287 // Preview-only DMA pixels must never enter the shared ordinary-sheet cache.
1288 if (map.HasGraphicsPreview()) {
1289 return map.BuildMapWithCache(static_cast<int>(tiles16_.size()), game_state_,
1290 world_type, tiles16_, GetMapTiles(world_type),
1291 nullptr);
1292 }
1293 const OverworldTilesetKey key = map.tileset_key();
1294 const uint64_t fingerprint = Tiles16Fingerprint();
1295
1296 const TilesetRevisions revisions = SheetRevisionsForKey(key);
1297
1298 const std::vector<uint8_t>* cached_gfx = nullptr;
1299 const std::vector<uint8_t>* cached_blockset = nullptr;
1300 auto it = tileset_cache_.find(key);
1301 if (it != tileset_cache_.end() && it->second.sheet_revisions != revisions) {
1302 // An unsaved Graphics editor edit changed one of the key's sheets since
1303 // this entry was built; drop it so the map re-reads the store.
1304 tileset_cache_.erase(it);
1305 it = tileset_cache_.end();
1306 }
1307 if (it != tileset_cache_.end()) {
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;
1312 }
1313 }
1314
1315 RETURN_IF_ERROR(map.BuildMapWithCache(
1316 static_cast<int>(tiles16_.size()), game_state_, world_type, tiles16_,
1317 GetMapTiles(world_type), cached_gfx, cached_blockset));
1318
1319 if (it == tileset_cache_.end()) {
1320 while (tileset_cache_.size() >= kMaxCachedTilesets) {
1321 auto oldest = tileset_cache_.begin();
1322 for (auto e = tileset_cache_.begin(); e != tileset_cache_.end(); ++e) {
1323 if (e->second.last_use < oldest->second.last_use) {
1324 oldest = e;
1325 }
1326 }
1327 tileset_cache_.erase(oldest);
1328 }
1329 it = tileset_cache_.emplace(key, TilesetCacheEntry{}).first;
1330 it->second.current_gfx = map.current_graphics();
1331 it->second.sheet_revisions = revisions;
1332 }
1333 if (cached_blockset == nullptr) {
1334 it->second.tile16_blockset = map.current_tile16_blockset();
1335 it->second.tiles16_fingerprint = fingerprint;
1336 }
1337 it->second.last_use = ++tileset_cache_clock_;
1338 return absl::OkStatus();
1339}
1340
1342 const OverworldTilesetKey& key) const {
1343 TilesetRevisions revisions{};
1344 if (game_data_ == nullptr) {
1345 return revisions;
1346 }
1347 for (size_t i = 0; i < key.size(); ++i) {
1348 if (key[i] != 0) {
1349 revisions[i] = game_data_->sheet_store.Revision(key[i]);
1350 }
1351 }
1352 return revisions;
1353}
1354
1356 for (auto it = tileset_cache_.begin(); it != tileset_cache_.end();) {
1357 const auto& key = it->first;
1358 if (std::find(key.begin(), key.end(), static_cast<uint8_t>(sheet)) !=
1359 key.end()) {
1360 it = tileset_cache_.erase(it);
1361 } else {
1362 ++it;
1363 }
1364 }
1365}
1366
1368 graphics_preview_ = std::move(preview);
1369 for (int i = 0; i < static_cast<int>(overworld_maps_.size()); ++i) {
1371 }
1372}
1373
1375 if (map_index < 0 ||
1376 static_cast<size_t>(map_index) >= overworld_maps_.size()) {
1377 return;
1378 }
1379 auto& map = overworld_maps_[map_index];
1380 const int parent = map.parent();
1381 const auto preview = graphics_preview_.find(parent);
1382 map.SetGraphicsPreview(preview == graphics_preview_.end()
1384 : preview->second);
1385 if (parent == map_index || parent < 0 ||
1386 static_cast<size_t>(parent) >= overworld_maps_.size()) {
1387 map.ClearInheritedAreaProperties();
1388 return;
1389 }
1390 map.InheritAreaProperties(overworld_maps_[parent]);
1391}
1392
1393absl::StatusOr<Overworld::SubscreenOverlayLayer>
1395 if (map_index < 0 || map_index >= kNumOverworldMaps) {
1396 return absl::InvalidArgumentError("Invalid map index");
1397 }
1398 RETURN_IF_ERROR(EnsureMapBuilt(map_index));
1399 const auto& map = overworld_maps_[map_index];
1401 layer.overlay_id = map.render_subscreen_overlay();
1402 const int screen = SubscreenOverlayScreen(layer.overlay_id);
1403 if (screen < 0 || screen >= kNumOverworldMaps || screen == map_index) {
1404 return layer;
1405 }
1406 layer.overlay_screen = screen;
1409 map.BuildSubscreenOverlayLayer(GetMapTiles(WorldForOverworldMap(screen)),
1410 screen, layer.background, &layer.pixels));
1411 return layer;
1412}
1413
1415 all_tiles_types_.fill(0);
1416 if (!rom_) {
1417 return;
1418 }
1419 const auto layout = zscream::DetectLayout(*rom_);
1420 if (layout == zscream::LayoutDialect::kZScreamV5 ||
1422 // There is no global 512-byte collision table for this dialect. Consumers
1423 // must request the selected map's resolved sheets; never show vanilla bytes.
1424 return;
1425 }
1426 const size_t offset = version_constants().kOverworldTilesType;
1427 if (offset > rom_->vector().size() ||
1428 rom_->vector().size() - offset < all_tiles_types_.size()) {
1429 return;
1430 }
1431 std::copy_n(rom_->vector().begin() + offset, all_tiles_types_.size(),
1432 all_tiles_types_.begin());
1433}
1434
1435absl::StatusOr<std::array<uint8_t, kNumTileTypes>>
1437 if (!rom_) {
1438 return absl::FailedPreconditionError("No ROM for collision preview");
1439 }
1440 if (map_id < 0 || map_id >= inspected_map_count()) {
1441 return absl::InvalidArgumentError("Invalid map ID for collision preview");
1442 }
1443 if (is_lyra_preview()) {
1444 return absl::FailedPreconditionError(
1445 "Descriptor collision preview is not supported by this reader");
1446 }
1447 const auto layout = zscream::DetectLayout(*rom_);
1448 if (layout == zscream::LayoutDialect::kUnknown) {
1449 return absl::FailedPreconditionError(
1450 "Unknown ZScream collision layout; no vanilla fallback");
1451 }
1452 if (layout == zscream::LayoutDialect::kZScreamV5) {
1454 const auto* map = overworld_map(map_id);
1455 if (!map) {
1456 return absl::FailedPreconditionError(
1457 "Map must be loaded before resolving per-sheet collisions");
1458 }
1460 for (size_t slot = 0; slot < sheets.size(); ++slot) {
1461 // Public provenance resolves the same private ResolveStaticGraphics()
1462 // slots as the display. Slots 0..7 are BG PriorSheets; 8..15 are sprites.
1463 sheets[slot] =
1464 map->MainSlotProvenance(static_cast<int>(slot)).effective_sheet;
1465 }
1466 return zscream::ComposeTile8Collisions(*rom_, sheets);
1467 }
1468 const size_t offset = version_constants().kOverworldTilesType;
1469 if (offset > rom_->vector().size() ||
1470 rom_->vector().size() - offset < all_tiles_types_.size()) {
1471 return absl::OutOfRangeError("Incomplete legacy collision table");
1472 }
1473 std::array<uint8_t, kNumTileTypes> types{};
1474 std::copy_n(rom_->vector().begin() + offset, types.size(), types.begin());
1475 return types;
1476}
1477
1479 if (map_index < 0 || map_index >= kNumOverworldMaps) {
1480 return;
1481 }
1482
1483 // Drop the shared tileset entry for this map's sheet set so the next build
1484 // re-reads the graphics buffer.
1485 if (static_cast<size_t>(map_index) < overworld_maps_.size()) {
1486 tileset_cache_.erase(overworld_maps_[map_index].tileset_key());
1487 }
1488
1489 // Also mark the map as needing rebuild
1490 if (static_cast<size_t>(map_index) < overworld_maps_.size()) {
1491 overworld_maps_[map_index].SetNotBuilt();
1492 }
1493}
1494
1496 if (map_index < 0 || map_index >= kNumOverworldMaps) {
1497 return;
1498 }
1499
1500 auto* map = mutable_overworld_map(map_index);
1501 if (!map)
1502 return;
1503
1504 // Get parent and determine all sibling maps
1505 int parent_id = map->parent();
1506 std::vector<int> siblings;
1507
1509
1510 if (use_v3_sizes) {
1511 // v3: Use area_size enum
1512 switch (map->area_size()) {
1514 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
1515 break;
1517 siblings = {parent_id, parent_id + 1};
1518 break;
1520 siblings = {parent_id, parent_id + 8};
1521 break;
1522 default:
1523 siblings = {map_index}; // Small area - just this map
1524 break;
1525 }
1526 } else {
1527 // Vanilla/v1/v2: Use large_map flag
1528 if (map->is_large_map()) {
1529 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
1530 } else {
1531 siblings = {map_index}; // Small area - just this map
1532 }
1533 }
1534
1535 // Invalidate cache for all siblings
1536 for (int sibling : siblings) {
1537 if (sibling >= 0 && sibling < kNumOverworldMaps) {
1538 InvalidateMapCache(sibling);
1539 }
1540 }
1541}
1542
1544 // Table locations and per-state map counts come from the ROM's ASM version:
1545 // vanilla has 64/144/144 entries; ZSCustomOverworld v3 expands every game
1546 // state to 160 entries (state 0 then covers the Dark World too).
1547 const auto layout = GetOverworldSpriteLayout(*rom_);
1548
1549#ifdef __EMSCRIPTEN__
1550 // WASM: Sequential loading to avoid Web Worker explosion
1551 for (int state = 0; state < 3; ++state) {
1553 LoadSpritesFromMap(layout.tables[state], layout.counts[state], state));
1554 }
1555#else
1556 // Native: Parallel loading for performance
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]() {
1561 return LoadSpritesFromMap(layout.tables[state], layout.counts[state],
1562 state);
1563 }));
1564 }
1565
1566 for (auto& future : futures) {
1567 future.wait();
1568 RETURN_IF_ERROR(future.get());
1569 }
1570#endif
1571 return absl::OkStatus();
1572}
1573
1574absl::Status Overworld::LoadSpritesFromMap(int sprites_per_gamestate_ptr,
1575 int num_maps_per_gamestate,
1576 int game_state) {
1577 if (!rom_ || game_state < 0 || game_state >= 3 ||
1578 num_maps_per_gamestate < 0 || num_maps_per_gamestate > 160 ||
1579 overworld_maps_.size() < static_cast<size_t>(num_maps_per_gamestate)) {
1580 return absl::FailedPreconditionError(
1581 "Sprite loading requires initialized maps");
1582 }
1583 std::vector<Sprite> loaded;
1584 std::array<bool, 160> loaded_maps{};
1585 for (int i = 0; i < num_maps_per_gamestate; ++i) {
1586 if (map_parent_[i] != i)
1587 continue;
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];
1595 loaded.emplace_back(*overworld_maps_[i].mutable_current_graphics(),
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);
1600 if (!overworld_maps_[i].current_graphics().empty())
1601 loaded.back().Draw();
1602 }
1603 }
1604 all_sprites_[game_state] = std::move(loaded);
1605 sprite_maps_loaded_[game_state] = loaded_maps;
1606 return absl::OkStatus();
1607}
1608
1609absl::StatusOr<OverworldSpriteSavePlan> Overworld::PrepareSpriteSave() const {
1610 if (!rom_)
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) {
1616 if (sprite_maps_loaded_[state][map]) {
1617 edits[state][map] = OverworldSpriteBytes{};
1618 loaded = true;
1619 }
1620 }
1621 for (const auto& sprite : all_sprites_[state]) {
1622 if (sprite.deleted())
1623 continue;
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");
1628 }
1629 // World-space position is authoritative: drag/property edits do not
1630 // update Sprite::nx()/ny(). Encode relative to the owning parent map.
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 "
1636 "area");
1637 }
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());
1644 }
1645 for (auto& bytes : edits[state])
1646 if (bytes)
1647 bytes->push_back(0xFF);
1648 }
1649 if (!loaded)
1650 return OverworldSpriteSavePlan{};
1651 return PlanOverworldSpriteSave(*rom_, edits);
1652}
1653
1655 ASSIGN_OR_RETURN(auto plan, PrepareSpriteSave());
1656 return ApplyOverworldSpriteSave(*rom_, plan);
1657}
1658
1659absl::Status Overworld::Save(Rom* rom) {
1660 if (rom == nullptr) {
1661 return absl::FailedPreconditionError("No ROM for overworld sprites");
1662 }
1663 RETURN_IF_ERROR(rom->CheckLayoutWritable());
1664 rom_ = rom;
1666 ASSIGN_OR_RETURN(auto sprite_plan, PrepareSpriteSave());
1667 ScopedRomTransaction transaction(*rom_);
1669 if (expanded_tile16_) {
1671 } else {
1673 }
1674 if (expanded_tile32_) {
1676 } else {
1678 }
1689 transaction.Commit();
1690 return absl::OkStatus();
1691}
1692
1695 return absl::OkStatus();
1698 return absl::FailedPreconditionError(
1699 "Private Lyra producer reservation refuses changed native terrain "
1700 "before serialization");
1701 }
1702 if (PrivateOverlaySnapshot(overworld_maps_) != private_loaded_overlays_) {
1703 return absl::FailedPreconditionError(
1704 "Private Lyra reservation refuses changed overlay model before "
1705 "serialization");
1706 }
1707 return absl::OkStatus();
1708}
1709
1716
1721
1731
1736
1740 return absl::OkStatus();
1741 util::logf("Saving Overworld Maps");
1742
1743 if (MapTilesUnchanged()) {
1744 util::logf("Overworld tiles unchanged; preserving compressed map pointers");
1747 return absl::OkStatus();
1748 }
1749
1750 if (tiles32_list_.size() < NumberOfMap32) {
1752 }
1753
1754 // Initialize map pointers
1755 std::fill(map_pointers1_id.begin(), map_pointers1_id.end(), -1);
1756 std::fill(map_pointers2_id.begin(), map_pointers2_id.end(), -1);
1757
1758 // Compress and save each map
1760 for (int i = 0; i < kNumOverworldMaps; i++) {
1761 std::vector<uint8_t> single_map_1(512);
1762 std::vector<uint8_t> single_map_2(512);
1763
1764 // Copy tiles32 data to single_map_1 and single_map_2
1765 int npos = 0;
1766 for (int y = 0; y < 16; y++) {
1767 for (int x = 0; x < 16; x++) {
1768 auto packed = tiles32_list_[npos + (i * 256)];
1769 single_map_1[npos] = packed & 0xFF; // Lower 8 bits
1770 single_map_2[npos] = (packed >> 8) & 0xFF; // Next 8 bits
1771 npos++;
1772 }
1773 }
1774
1775 int size_a, size_b;
1776 // Compress single_map_1 and single_map_2
1777 auto a = gfx::HyruleMagicCompress(single_map_1.data(), 256, &size_a, 1);
1778 auto b = gfx::HyruleMagicCompress(single_map_2.data(), 256, &size_b, 1);
1779 if (a.empty() || b.empty()) {
1780 return absl::AbortedError("Error compressing map gfx.");
1781 }
1782
1783 // Save compressed data and pointers
1784 map_data_p1[i] = std::vector<uint8_t>(size_a);
1785 map_data_p2[i] = std::vector<uint8_t>(size_b);
1786
1787 if ((pos + size_a) >= 0x5FE70 && (pos + size_a) <= 0x60000) {
1788 pos = 0x60000;
1789 }
1790
1791 if ((pos + size_a) >= 0x6411F && (pos + size_a) <= 0x70000) {
1792 util::logf("Pos set to overflow region for map %s at %s",
1793 std::to_string(i), util::HexLong(pos));
1794 pos = kOverworldMapDataOverflow; // 0x0F8780;
1795 }
1796
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()) {
1800 return false;
1801 }
1802
1803 for (size_t i = 0; i < array1.size(); i++) {
1804 if (array1[i] != array2[i]) {
1805 return false;
1806 }
1807 }
1808
1809 return true;
1810 };
1811
1812 for (int j = 0; j < i; j++) {
1813 if (compare_array(a, map_data_p1[j])) {
1814 // Reuse pointer id j for P1 (a)
1815 map_pointers1_id[i] = j;
1816 }
1817
1818 if (compare_array(b, map_data_p2[j])) {
1819 map_pointers2_id[i] = j;
1820 // Reuse pointer id j for P2 (b)
1821 }
1822 }
1823
1824 if (map_pointers1_id[i] == -1) {
1825 // Save compressed data and pointer for map1
1826 std::copy(a.begin(), a.end(), map_data_p1[i].begin());
1827 int snes_pos = PcToSnes(pos);
1828 map_pointers1[i] = snes_pos;
1829 util::logf("Saving map pointers1 and compressed data for map %s at %s",
1830 util::HexByte(i), util::HexLong(snes_pos));
1831 RETURN_IF_ERROR(rom()->WriteLong(
1832 version_constants().kCompressedAllMap32PointersLow + (3 * i),
1833 snes_pos));
1834 RETURN_IF_ERROR(rom()->WriteVector(pos, a));
1835 pos += size_a;
1836 } else {
1837 // Save pointer for map1
1838 int snes_pos = map_pointers1[map_pointers1_id[i]];
1839 util::logf("Saving map pointers1 for map %s at %s", util::HexByte(i),
1840 util::HexLong(snes_pos));
1841 RETURN_IF_ERROR(rom()->WriteLong(
1842 version_constants().kCompressedAllMap32PointersLow + (3 * i),
1843 snes_pos));
1844 }
1845
1846 if ((pos + b.size()) >= 0x5FE70 && (pos + b.size()) <= 0x60000) {
1847 pos = 0x60000;
1848 }
1849
1850 if ((pos + b.size()) >= 0x6411F && (pos + b.size()) <= 0x70000) {
1851 util::logf("Pos set to overflow region for map %s at %s",
1852 util::HexByte(i), util::HexLong(pos));
1854 }
1855
1856 if (map_pointers2_id[i] == -1) {
1857 // Save compressed data and pointer for map2
1858 std::copy(b.begin(), b.end(), map_data_p2[i].begin());
1859 int snes_pos = PcToSnes(pos);
1860 map_pointers2[i] = snes_pos;
1861 util::logf("Saving map pointers2 and compressed data for map %s at %s",
1862 util::HexByte(i), util::HexLong(snes_pos));
1863 RETURN_IF_ERROR(rom()->WriteLong(
1864 version_constants().kCompressedAllMap32PointersHigh + (3 * i),
1865 snes_pos));
1866 RETURN_IF_ERROR(rom()->WriteVector(pos, b));
1867 pos += size_b;
1868 } else {
1869 // Save pointer for map2
1870 int snes_pos = map_pointers2[map_pointers2_id[i]];
1871 util::logf("Saving map pointers2 for map %s at %s", util::HexByte(i),
1872 util::HexLong(snes_pos));
1873 RETURN_IF_ERROR(rom()->WriteLong(
1874 version_constants().kCompressedAllMap32PointersHigh + (3 * i),
1875 snes_pos));
1876 }
1877 }
1878
1879 // Check if too many maps data
1881 util::logf("Too many maps data %s", util::HexLong(pos));
1882 return absl::AbortedError("Too many maps data " + std::to_string(pos));
1883 }
1884
1887 return absl::OkStatus();
1888}
1889
1890std::vector<std::pair<uint32_t, uint32_t>> Overworld::GetProjectedWriteRanges()
1891 const {
1892 std::vector<std::pair<uint32_t, uint32_t>> ranges;
1893
1894 // 1) Map32 Tiles (4 quadrants)
1895 uint32_t map32address[4] = {
1898 int map32_len = 0x33F0; // Vanilla length
1899
1900 // Mirror AssembleMap32Tiles() logic: if expanded tile32 is present, the three
1901 // non-TL quadrants are relocated and the logical length changes.
1902 if (expanded_tile32_) {
1903 map32address[1] = GetMap32TileTRExpanded();
1904 map32address[2] = GetMap32TileBLExpanded();
1905 map32address[3] = GetMap32TileBRExpanded();
1906 map32_len = kMap32TileCountExpanded;
1907 }
1908
1909 for (int i = 0; i < 4; ++i) {
1910 if (map32address[i] > 0) { // Valid address
1911 ranges.emplace_back(map32address[i], map32address[i] + map32_len);
1912 }
1913 }
1914
1915 // 2) Map16 Tiles
1916 int map16_addr = kMap16Tiles;
1917 int map16_len = kNumTile16Individual * 8; // Vanilla: 4096 * 8 = 32KB
1918 if (expanded_tile16_) {
1919 map16_addr = GetMap16TilesExpanded();
1920 map16_len = NumberOfMap16Ex * 8;
1921 }
1922 ranges.emplace_back(map16_addr, map16_addr + map16_len);
1923
1924 // 3) Overworld map compressed pointer tables + data regions.
1925 // SaveOverworldMaps() writes two 3-byte pointer tables for all maps, plus the
1926 // compressed data itself. This is a conservative superset of the possible
1927 // write footprint.
1928 constexpr uint32_t kCompressedBank0bEnd = 0x5FE70;
1929 constexpr uint32_t kCompressedBank0cStart = 0x60000;
1930 constexpr uint32_t kCompressedBank0cEnd = 0x6411F;
1931
1932 const uint32_t ptr_low = version_constants().kCompressedAllMap32PointersLow;
1933 const uint32_t ptr_high = version_constants().kCompressedAllMap32PointersHigh;
1934 ranges.emplace_back(ptr_low, ptr_low + (3 * kNumOverworldMaps));
1935 ranges.emplace_back(ptr_high, ptr_high + (3 * kNumOverworldMaps));
1936
1937 ranges.emplace_back(kOverworldCompressedMapPos,
1938 kCompressedBank0bEnd); // Bank 0B
1939 ranges.emplace_back(kCompressedBank0cStart, kCompressedBank0cEnd); // Bank 0C
1940 ranges.emplace_back(kOverworldMapDataOverflow,
1941 kOverworldCompressedOverflowPos); // Overflow Area
1942
1943 return ranges;
1944}
1945
1947 util::logf("Saving Large Maps");
1948
1949 // Check if this is a v3+ ROM to use expanded transition system
1950 bool use_expanded_transitions =
1952
1953 if (use_expanded_transitions) {
1954 // Use new v3+ complex transition system with neighbor awareness
1955 return SaveLargeMapsExpanded();
1956 }
1957
1958 // Original vanilla/v2 logic preserved
1959 std::vector<uint8_t> checked_map;
1960
1961 for (int i = 0; i < kNumMapsPerWorld; ++i) {
1962 int y_pos = i / 8;
1963 int x_pos = i % 8;
1964 int parent_y_pos = overworld_maps_[i].parent() / 8;
1965 int parent_x_pos = overworld_maps_[i].parent() % 8;
1966
1967 // Always write the map parent since it should not matter
1968 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + i,
1969 overworld_maps_[i].parent()))
1970
1971 if (std::find(checked_map.begin(), checked_map.end(), i) !=
1972 checked_map.end()) {
1973 continue;
1974 }
1975
1976 // If it's large then save parent pos *
1977 // 0x200 otherwise pos * 0x200
1978 if (overworld_maps_[i].is_large_map()) {
1979 const uint8_t large_map_offsets[] = {0, 1, 8, 9};
1980 for (const auto& offset : large_map_offsets) {
1981 // Check 1
1982 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i + offset, 0x20));
1983 // Check 2
1985 rom()->WriteByte(kOverworldMapSizeHighByte + i + offset, 0x03));
1986 // Check 3
1988 rom()->WriteByte(kOverworldScreenSize + i + offset, 0x00));
1990 rom()->WriteByte(kOverworldScreenSize + i + offset + 64, 0x00));
1991 // Check 4
1992 RETURN_IF_ERROR(rom()->WriteByte(
1993 kOverworldScreenSizeForLoading + i + offset, 0x04));
1994 RETURN_IF_ERROR(rom()->WriteByte(
1996 0x04));
1998 offset + kSpecialWorldMapIdStart,
1999 0x04));
2000 }
2001
2002 // Check 5 and 6 - transition targets
2004 rom()->WriteShort(kTransitionTargetNorth + (i * 2),
2005 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
2007 rom()->WriteShort(kTransitionTargetWest + (i * 2),
2008 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
2009
2011 rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 2,
2012 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
2014 rom()->WriteShort(kTransitionTargetWest + (i * 2) + 2,
2015 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
2016
2018 rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 16,
2019 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
2021 rom()->WriteShort(kTransitionTargetWest + (i * 2) + 16,
2022 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
2023
2025 rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 18,
2026 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
2028 rom()->WriteShort(kTransitionTargetWest + (i * 2) + 18,
2029 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
2030
2031 // Check 7 and 8 - transition positions
2032 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionX + (i * 2),
2033 (parent_x_pos * 0x200)));
2034 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionY + (i * 2),
2035 (parent_y_pos * 0x200)));
2036
2038 rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 02,
2039 (parent_x_pos * 0x200)));
2041 rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 02,
2042 (parent_y_pos * 0x200)));
2043
2045 rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 16,
2046 (parent_x_pos * 0x200)));
2048 rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 16,
2049 (parent_y_pos * 0x200)));
2050
2052 rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 18,
2053 (parent_x_pos * 0x200)));
2055 rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 18,
2056 (parent_y_pos * 0x200)));
2057
2058 // Check 9 - simple vanilla large area transitions
2059 RETURN_IF_ERROR(rom()->WriteShort(
2060 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 00, 0x0060));
2061 RETURN_IF_ERROR(rom()->WriteShort(
2062 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 02, 0x0060));
2063
2064 // If parentX == 0 then lower submaps == 0x0060 too
2065 if (parent_x_pos == 0) {
2066 RETURN_IF_ERROR(rom()->WriteShort(
2067 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 16, 0x0060));
2068 RETURN_IF_ERROR(rom()->WriteShort(
2069 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 18, 0x0060));
2070 } else {
2071 // Otherwise lower submaps == 0x1060
2072 RETURN_IF_ERROR(rom()->WriteShort(
2073 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 16, 0x1060));
2074 RETURN_IF_ERROR(rom()->WriteShort(
2075 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 18, 0x1060));
2076
2077 // If the area to the left is a large map, we don't need to add an
2078 // offset to it. otherwise leave it the same. Just to make sure where
2079 // don't try to read outside of the array.
2080 if ((i - 1) >= 0) {
2081 // If the area to the left is a large area.
2082 if (overworld_maps_[i - 1].is_large_map()) {
2083 // If the area to the left is the bottom right of a large area.
2084 if (overworld_maps_[i - 1].large_index() == 1) {
2085 RETURN_IF_ERROR(rom()->WriteShort(
2087 0x0060));
2088 }
2089 }
2090 }
2091 }
2092
2093 // Always 0x0080
2094 RETURN_IF_ERROR(rom()->WriteShort(
2095 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 00, 0x0080));
2096 RETURN_IF_ERROR(rom()->WriteShort(
2097 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 2, 0x0080));
2098 // Lower always 0x1080
2099 RETURN_IF_ERROR(rom()->WriteShort(
2100 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 16, 0x1080));
2101 RETURN_IF_ERROR(rom()->WriteShort(
2102 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 18, 0x1080));
2103
2104 // If the area to the right is a large map, we don't need to add an offset
2105 // to it. otherwise leave it the same. Just to make sure where don't try
2106 // to read outside of the array.
2107 if ((i + 2) < 64) {
2108 // If the area to the right is a large area.
2109 if (overworld_maps_[i + 2].is_large_map()) {
2110 // If the area to the right is the top left of a large area.
2111 if (overworld_maps_[i + 2].large_index() == 0) {
2112 RETURN_IF_ERROR(rom()->WriteShort(
2113 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 18, 0x0080));
2114 }
2115 }
2116 }
2117
2118 // Always 0x1800
2119 RETURN_IF_ERROR(rom()->WriteShort(
2120 kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x1800));
2121 RETURN_IF_ERROR(rom()->WriteShort(
2122 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 16, 0x1800));
2123 // Right side is always 0x1840
2124 RETURN_IF_ERROR(rom()->WriteShort(
2125 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 2, 0x1840));
2126 RETURN_IF_ERROR(rom()->WriteShort(
2127 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 18, 0x1840));
2128
2129 // If the area above is a large map, we don't need to add an offset to it.
2130 // otherwise leave it the same.
2131 // Just to make sure where don't try to read outside of the array.
2132 if (i - 8 >= 0) {
2133 // If the area just above us is a large area.
2134 if (overworld_maps_[i - 8].is_large_map()) {
2135 // If the area just above us is the bottom left of a large area.
2136 if (overworld_maps_[i - 8].large_index() == 2) {
2137 RETURN_IF_ERROR(rom()->WriteShort(
2138 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 02, 0x1800));
2139 }
2140 }
2141 }
2142
2143 // Always 0x2000
2144 RETURN_IF_ERROR(rom()->WriteShort(
2145 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 00, 0x2000));
2146 RETURN_IF_ERROR(rom()->WriteShort(
2147 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 16, 0x2000));
2148 // Right side always 0x2040
2149 RETURN_IF_ERROR(rom()->WriteShort(
2150 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 2, 0x2040));
2151 RETURN_IF_ERROR(rom()->WriteShort(
2152 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 18, 0x2040));
2153
2154 // If the area below is a large map, we don't need to add an offset to it.
2155 // otherwise leave it the same.
2156 // Just to make sure where don't try to read outside of the array.
2157 if (i + 16 < 64) {
2158 // If the area just below us is a large area.
2159 if (overworld_maps_[i + 16].is_large_map()) {
2160 // If the area just below us is the top left of a large area.
2161 if (overworld_maps_[i + 16].large_index() == 0) {
2162 RETURN_IF_ERROR(rom()->WriteShort(
2163 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 18, 0x2000));
2164 }
2165 }
2166 }
2167
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));
2172
2173 } else {
2174 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i, 0x00));
2175 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSizeHighByte + i, 0x01));
2176
2177 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, 0x01));
2178 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i + 64, 0x01));
2179
2181 rom()->WriteByte(kOverworldScreenSizeForLoading + i, 0x02));
2182 RETURN_IF_ERROR(rom()->WriteByte(
2184 RETURN_IF_ERROR(rom()->WriteByte(
2186
2187 RETURN_IF_ERROR(rom()->WriteShort(
2188 kOverworldScreenTileMapChangeByScreen1 + (i * 2), 0x0060));
2189
2190 // If the area to the left is a large map, we don't need to add an offset
2191 // to it. otherwise leave it the same.
2192 // Just to make sure where don't try to read outside of the array.
2193 if (i - 1 >= 0 && parent_x_pos != 0) {
2194 if (overworld_maps_[i - 1].is_large_map()) {
2195 if (overworld_maps_[i - 1].large_index() == 3) {
2196 RETURN_IF_ERROR(rom()->WriteShort(
2197 kOverworldScreenTileMapChangeByScreen1 + (i * 2), 0xF060));
2198 }
2199 }
2200 }
2201
2202 RETURN_IF_ERROR(rom()->WriteShort(
2203 kOverworldScreenTileMapChangeByScreen2 + (i * 2), 0x0040));
2204
2205 if (i + 1 < 64 && parent_x_pos != 7) {
2206 if (overworld_maps_[i + 1].is_large_map()) {
2207 if (overworld_maps_[i + 1].large_index() == 2) {
2208 RETURN_IF_ERROR(rom()->WriteShort(
2209 kOverworldScreenTileMapChangeByScreen2 + (i * 2), 0xF040));
2210 }
2211 }
2212 }
2213
2214 RETURN_IF_ERROR(rom()->WriteShort(
2215 kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x1800));
2216
2217 // If the area above is a large map, we don't need to add an offset to it.
2218 // otherwise leave it the same.
2219 // Just to make sure where don't try to read outside of the array.
2220 if (i - 8 >= 0) {
2221 // If the area just above us is a large area.
2222 if (overworld_maps_[i - 8].is_large_map()) {
2223 // If we are under the bottom right of the large area.
2224 if (overworld_maps_[i - 8].large_index() == 3) {
2225 RETURN_IF_ERROR(rom()->WriteShort(
2226 kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x17C0));
2227 }
2228 }
2229 }
2230
2231 RETURN_IF_ERROR(rom()->WriteShort(
2232 kOverworldScreenTileMapChangeByScreen4 + (i * 2), 0x1000));
2233
2234 // If the area below is a large map, we don't need to add an offset to it.
2235 // otherwise leave it the same.
2236 // Just to make sure where don't try to read outside of the array.
2237 if (i + 8 < 64) {
2238 // If the area just below us is a large area.
2239 if (overworld_maps_[i + 8].is_large_map()) {
2240 // If we are on top of the top right of the large area.
2241 if (overworld_maps_[i + 8].large_index() == 1) {
2242 RETURN_IF_ERROR(rom()->WriteShort(
2243 kOverworldScreenTileMapChangeByScreen4 + (i * 2), 0x0FC0));
2244 }
2245 }
2246 }
2247
2248 RETURN_IF_ERROR(rom()->WriteShort(kTransitionTargetNorth + (i * 2),
2249 (uint16_t)((y_pos * 0x200) - 0xE0)));
2250 RETURN_IF_ERROR(rom()->WriteShort(kTransitionTargetWest + (i * 2),
2251 (uint16_t)((x_pos * 0x200) - 0x100)));
2252
2253 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionX + (i * 2),
2254 (x_pos * 0x200)));
2255 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionY + (i * 2),
2256 (y_pos * 0x200)));
2257
2258 checked_map.emplace_back(i);
2259 }
2260 }
2261
2262 // The legacy parent and transition tables are 64-entry local tables, but the
2263 // screen-size bytes are stored per Light/Dark world. The original loop above
2264 // mirrors Light World sizes into Dark World while calculating transitions;
2265 // correct those world-specific bytes from the actual map objects before
2266 // returning so a Light/Special edit cannot silently clobber Dark World sizes.
2267 const auto legacy_size_byte = [this](int map_index) -> uint8_t {
2268 return overworld_maps_[map_index].is_large_map() ? 0x00 : 0x01;
2269 };
2270 const auto legacy_loading_size_byte = [this](int map_index) -> uint8_t {
2271 return overworld_maps_[map_index].is_large_map() ? 0x04 : 0x02;
2272 };
2273
2274 for (int map_index = 0; map_index < kSpecialWorldMapIdStart; ++map_index) {
2275 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + map_index,
2276 legacy_size_byte(map_index)));
2277 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSizeForLoading + map_index,
2278 legacy_loading_size_byte(map_index)));
2279 }
2280
2281 for (int map_index = kSpecialWorldMapIdStart; map_index < kNumOverworldMaps;
2282 ++map_index) {
2283 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSizeForLoading + map_index,
2284 legacy_loading_size_byte(map_index)));
2285 }
2286
2287 constexpr int OverworldScreenTileMapChangeMask = 0x1262C;
2288
2290 rom()->WriteShort(OverworldScreenTileMapChangeMask + 0, 0x1F80));
2292 rom()->WriteShort(OverworldScreenTileMapChangeMask + 2, 0x1F80));
2294 rom()->WriteShort(OverworldScreenTileMapChangeMask + 4, 0x007F));
2296 rom()->WriteShort(OverworldScreenTileMapChangeMask + 6, 0x007F));
2297
2298 return absl::OkStatus();
2299}
2300
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) {
2306 // Set basic transition targets
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)));
2313
2315 rom()->WriteShort(transition_pos_x + (i * 2), parent_x_pos * 0x0200));
2317 rom()->WriteShort(transition_pos_y + (i * 2), parent_y_pos * 0x0200));
2318
2319 // byScreen1 = Transitioning right
2320 uint16_t by_screen1_small = 0x0060;
2321
2322 // Check west neighbor for transition adjustments
2323 if ((i % 0x40) - 1 >= 0) {
2324 auto& west_neighbor = overworld_maps_[i - 1];
2325
2326 // Transition from bottom right quadrant of large area to small area
2327 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2328 west_neighbor.large_index() == 3) {
2329 by_screen1_small = 0xF060;
2330 }
2331 // Transition from bottom quadrant of tall area to small area
2332 else if (west_neighbor.area_size() == AreaSizeEnum::TallArea &&
2333 west_neighbor.large_index() == 2) {
2334 by_screen1_small = 0xF060;
2335 }
2336 }
2337
2339 rom()->WriteShort(screen_change_1 + (i * 2), by_screen1_small));
2340
2341 // byScreen2 = Transitioning left
2342 uint16_t by_screen2_small = 0x0040;
2343
2344 // Check east neighbor for transition adjustments
2345 if ((i % 0x40) + 1 < 0x40 && i + 1 < kNumOverworldMaps) {
2346 auto& east_neighbor = overworld_maps_[i + 1];
2347
2348 // Transition from bottom left quadrant of large area to small area
2349 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2350 east_neighbor.large_index() == 2) {
2351 by_screen2_small = 0xF040;
2352 }
2353 // Transition from bottom quadrant of tall area to small area
2354 else if (east_neighbor.area_size() == AreaSizeEnum::TallArea &&
2355 east_neighbor.large_index() == 2) {
2356 by_screen2_small = 0xF040;
2357 }
2358 }
2359
2361 rom()->WriteShort(screen_change_2 + (i * 2), by_screen2_small));
2362
2363 // byScreen3 = Transitioning down
2364 uint16_t by_screen3_small = 0x1800;
2365
2366 // Check north neighbor for transition adjustments
2367 if ((i % 0x40) - 8 >= 0) {
2368 auto& north_neighbor = overworld_maps_[i - 8];
2369
2370 // Transition from bottom right quadrant of large area to small area
2371 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2372 north_neighbor.large_index() == 3) {
2373 by_screen3_small = 0x17C0;
2374 }
2375 // Transition from right quadrant of wide area to small area
2376 else if (north_neighbor.area_size() == AreaSizeEnum::WideArea &&
2377 north_neighbor.large_index() == 1) {
2378 by_screen3_small = 0x17C0;
2379 }
2380 }
2381
2383 rom()->WriteShort(screen_change_3 + (i * 2), by_screen3_small));
2384
2385 // byScreen4 = Transitioning up
2386 uint16_t by_screen4_small = 0x1000;
2387
2388 // Check south neighbor for transition adjustments
2389 if ((i % 0x40) + 8 < 0x40 && i + 8 < kNumOverworldMaps) {
2390 auto& south_neighbor = overworld_maps_[i + 8];
2391
2392 // Transition from top right quadrant of large area to small area
2393 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2394 south_neighbor.large_index() == 1) {
2395 by_screen4_small = 0x0FC0;
2396 }
2397 // Transition from right quadrant of wide area to small area
2398 else if (south_neighbor.area_size() == AreaSizeEnum::WideArea &&
2399 south_neighbor.large_index() == 1) {
2400 by_screen4_small = 0x0FC0;
2401 }
2402 }
2403
2405 rom()->WriteShort(screen_change_4 + (i * 2), by_screen4_small));
2406
2407 return absl::OkStatus();
2408}
2409
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) {
2415 // Set transition targets for all 4 quadrants
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)));
2424 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset,
2425 parent_x_pos * 0x0200));
2426 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset,
2427 parent_y_pos * 0x0200));
2428 }
2429
2430 // Complex neighbor-aware transition calculations for large areas
2431 // byScreen1 = Transitioning right
2432 std::array<uint16_t, 4> by_screen1_large = {0x0060, 0x0060, 0x1060, 0x1060};
2433
2434 // Check west neighbor
2435 if ((i % 0x40) - 1 >= 0) {
2436 auto& west_neighbor = overworld_maps_[i - 1];
2437
2438 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2439 switch (west_neighbor.large_index()) {
2440 case 1: // From bottom right to bottom left of large area
2441 by_screen1_large[2] = 0x0060;
2442 break;
2443 case 3: // From bottom right to top left of large area
2444 by_screen1_large[0] = 0xF060;
2445 break;
2446 }
2447 } else if (west_neighbor.area_size() == AreaSizeEnum::TallArea) {
2448 switch (west_neighbor.large_index()) {
2449 case 0: // From bottom of tall to bottom left of large
2450 by_screen1_large[2] = 0x0060;
2451 break;
2452 case 2: // From bottom of tall to top left of large
2453 by_screen1_large[0] = 0xF060;
2454 break;
2455 }
2456 }
2457 }
2458
2459 for (int j = 0; j < 4; j++) {
2460 RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j],
2461 by_screen1_large[j]));
2462 }
2463
2464 // byScreen2 = Transitioning left
2465 std::array<uint16_t, 4> by_screen2_large = {0x0080, 0x0080, 0x1080, 0x1080};
2466
2467 // Check east neighbor
2468 if ((i % 0x40) + 2 < 0x40 && i + 2 < kNumOverworldMaps) {
2469 auto& east_neighbor = overworld_maps_[i + 2];
2470
2471 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2472 switch (east_neighbor.large_index()) {
2473 case 0: // From bottom left to bottom right of large area
2474 by_screen2_large[3] = 0x0080;
2475 break;
2476 case 2: // From bottom left to top right of large area
2477 by_screen2_large[1] = 0xF080;
2478 break;
2479 }
2480 } else if (east_neighbor.area_size() == AreaSizeEnum::TallArea) {
2481 switch (east_neighbor.large_index()) {
2482 case 0: // From bottom of tall to bottom right of large
2483 by_screen2_large[3] = 0x0080;
2484 break;
2485 case 2: // From bottom of tall to top right of large
2486 by_screen2_large[1] = 0xF080;
2487 break;
2488 }
2489 }
2490 }
2491
2492 for (int j = 0; j < 4; j++) {
2493 RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j],
2494 by_screen2_large[j]));
2495 }
2496
2497 // byScreen3 = Transitioning down
2498 std::array<uint16_t, 4> by_screen3_large = {0x1800, 0x1840, 0x1800, 0x1840};
2499
2500 // Check north neighbor
2501 if ((i % 0x40) - 8 >= 0) {
2502 auto& north_neighbor = overworld_maps_[i - 8];
2503
2504 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2505 switch (north_neighbor.large_index()) {
2506 case 2: // From bottom right to top right of large area
2507 by_screen3_large[1] = 0x1800;
2508 break;
2509 case 3: // From bottom right to top left of large area
2510 by_screen3_large[0] = 0x17C0;
2511 break;
2512 }
2513 } else if (north_neighbor.area_size() == AreaSizeEnum::WideArea) {
2514 switch (north_neighbor.large_index()) {
2515 case 0: // From right of wide to top right of large
2516 by_screen3_large[1] = 0x1800;
2517 break;
2518 case 1: // From right of wide to top left of large
2519 by_screen3_large[0] = 0x17C0;
2520 break;
2521 }
2522 }
2523 }
2524
2525 for (int j = 0; j < 4; j++) {
2526 RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j],
2527 by_screen3_large[j]));
2528 }
2529
2530 // byScreen4 = Transitioning up
2531 std::array<uint16_t, 4> by_screen4_large = {0x2000, 0x2040, 0x2000, 0x2040};
2532
2533 // Check south neighbor
2534 if ((i % 0x40) + 16 < 0x40 && i + 16 < kNumOverworldMaps) {
2535 auto& south_neighbor = overworld_maps_[i + 16];
2536
2537 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2538 switch (south_neighbor.large_index()) {
2539 case 0: // From top right to bottom right of large area
2540 by_screen4_large[3] = 0x2000;
2541 break;
2542 case 1: // From top right to bottom left of large area
2543 by_screen4_large[2] = 0x1FC0;
2544 break;
2545 }
2546 } else if (south_neighbor.area_size() == AreaSizeEnum::WideArea) {
2547 switch (south_neighbor.large_index()) {
2548 case 0: // From right of wide to bottom right of large
2549 by_screen4_large[3] = 0x2000;
2550 break;
2551 case 1: // From right of wide to bottom left of large
2552 by_screen4_large[2] = 0x1FC0;
2553 break;
2554 }
2555 }
2556 }
2557
2558 for (int j = 0; j < 4; j++) {
2559 RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j],
2560 by_screen4_large[j]));
2561 }
2562
2563 return absl::OkStatus();
2564}
2565
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) {
2571 // Set transition targets for both quadrants
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)));
2580 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset,
2581 parent_x_pos * 0x0200));
2582 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset,
2583 parent_y_pos * 0x0200));
2584 }
2585
2586 // byScreen1 = Transitioning right
2587 std::array<uint16_t, 2> by_screen1_wide = {0x0060, 0x0060};
2588
2589 // Check west neighbor
2590 if ((i % 0x40) - 1 >= 0) {
2591 auto& west_neighbor = overworld_maps_[i - 1];
2592
2593 // From bottom right of large to left of wide
2594 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2595 west_neighbor.large_index() == 3) {
2596 by_screen1_wide[0] = 0xF060;
2597 }
2598 // From bottom of tall to left of wide
2599 else if (west_neighbor.area_size() == AreaSizeEnum::TallArea &&
2600 west_neighbor.large_index() == 2) {
2601 by_screen1_wide[0] = 0xF060;
2602 }
2603 }
2604
2605 for (int j = 0; j < 2; j++) {
2606 RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j],
2607 by_screen1_wide[j]));
2608 }
2609
2610 // byScreen2 = Transitioning left
2611 std::array<uint16_t, 2> by_screen2_wide = {0x0080, 0x0080};
2612
2613 // Check east neighbor
2614 if ((i % 0x40) + 2 < 0x40 && i + 2 < kNumOverworldMaps) {
2615 auto& east_neighbor = overworld_maps_[i + 2];
2616
2617 // From bottom left of large to right of wide
2618 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2619 east_neighbor.large_index() == 2) {
2620 by_screen2_wide[1] = 0xF080;
2621 }
2622 // From bottom of tall to right of wide
2623 else if (east_neighbor.area_size() == AreaSizeEnum::TallArea &&
2624 east_neighbor.large_index() == 2) {
2625 by_screen2_wide[1] = 0xF080;
2626 }
2627 }
2628
2629 for (int j = 0; j < 2; j++) {
2630 RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j],
2631 by_screen2_wide[j]));
2632 }
2633
2634 // byScreen3 = Transitioning down
2635 std::array<uint16_t, 2> by_screen3_wide = {0x1800, 0x1840};
2636
2637 // Check north neighbor
2638 if ((i % 0x40) - 8 >= 0) {
2639 auto& north_neighbor = overworld_maps_[i - 8];
2640
2641 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2642 switch (north_neighbor.large_index()) {
2643 case 2: // From bottom right of large to right of wide
2644 by_screen3_wide[1] = 0x1800;
2645 break;
2646 case 3: // From bottom right of large to left of wide
2647 by_screen3_wide[0] = 0x17C0;
2648 break;
2649 }
2650 } else if (north_neighbor.area_size() == AreaSizeEnum::WideArea) {
2651 switch (north_neighbor.large_index()) {
2652 case 0: // From right of wide to right of wide
2653 by_screen3_wide[1] = 0x1800;
2654 break;
2655 case 1: // From right of wide to left of wide
2656 by_screen3_wide[0] = 0x17C0;
2657 break;
2658 }
2659 }
2660 }
2661
2662 for (int j = 0; j < 2; j++) {
2663 RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j],
2664 by_screen3_wide[j]));
2665 }
2666
2667 // byScreen4 = Transitioning up
2668 std::array<uint16_t, 2> by_screen4_wide = {0x1000, 0x1040};
2669
2670 // Check south neighbor
2671 if ((i % 0x40) + 8 < 0x40 && i + 8 < kNumOverworldMaps) {
2672 auto& south_neighbor = overworld_maps_[i + 8];
2673
2674 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2675 switch (south_neighbor.large_index()) {
2676 case 0: // From top right of large to right of wide
2677 by_screen4_wide[1] = 0x1000;
2678 break;
2679 case 1: // From top right of large to left of wide
2680 by_screen4_wide[0] = 0x0FC0;
2681 break;
2682 }
2683 } else if (south_neighbor.area_size() == AreaSizeEnum::WideArea) {
2684 if (south_neighbor.large_index() == 1) {
2685 by_screen4_wide[0] = 0x0FC0;
2686 }
2687 switch (south_neighbor.large_index()) {
2688 case 0: // From right of wide to right of wide
2689 by_screen4_wide[1] = 0x1000;
2690 break;
2691 case 1: // From right of wide to left of wide
2692 by_screen4_wide[0] = 0x0FC0;
2693 break;
2694 }
2695 }
2696 }
2697
2698 for (int j = 0; j < 2; j++) {
2699 RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j],
2700 by_screen4_wide[j]));
2701 }
2702
2703 return absl::OkStatus();
2704}
2705
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) {
2711 // Set transition targets for both quadrants
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)));
2720 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset,
2721 parent_x_pos * 0x0200));
2722 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset,
2723 parent_y_pos * 0x0200));
2724 }
2725
2726 // byScreen1 = Transitioning right
2727 std::array<uint16_t, 2> by_screen1_tall = {0x0060, 0x1060};
2728
2729 // Check west neighbor
2730 if ((i % 0x40) - 1 >= 0) {
2731 auto& west_neighbor = overworld_maps_[i - 1];
2732
2733 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2734 switch (west_neighbor.large_index()) {
2735 case 1: // From bottom right of large to bottom of tall
2736 by_screen1_tall[1] = 0x0060;
2737 break;
2738 case 3: // From bottom right of large to top of tall
2739 by_screen1_tall[0] = 0xF060;
2740 break;
2741 }
2742 } else if (west_neighbor.area_size() == AreaSizeEnum::TallArea) {
2743 switch (west_neighbor.large_index()) {
2744 case 0: // From bottom of tall to bottom of tall
2745 by_screen1_tall[1] = 0x0060;
2746 break;
2747 case 2: // From bottom of tall to top of tall
2748 by_screen1_tall[0] = 0xF060;
2749 break;
2750 }
2751 }
2752 }
2753
2754 for (int j = 0; j < 2; j++) {
2755 RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j],
2756 by_screen1_tall[j]));
2757 }
2758
2759 // byScreen2 = Transitioning left
2760 std::array<uint16_t, 2> by_screen2_tall = {0x0040, 0x1040};
2761
2762 // Check east neighbor
2763 if ((i % 0x40) + 1 < 0x40 && i + 1 < kNumOverworldMaps) {
2764 auto& east_neighbor = overworld_maps_[i + 1];
2765
2766 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2767 switch (east_neighbor.large_index()) {
2768 case 0: // From bottom left of large to bottom of tall
2769 by_screen2_tall[1] = 0x0040;
2770 break;
2771 case 2: // From bottom left of large to top of tall
2772 by_screen2_tall[0] = 0xF040;
2773 break;
2774 }
2775 } else if (east_neighbor.area_size() == AreaSizeEnum::TallArea) {
2776 switch (east_neighbor.large_index()) {
2777 case 0: // From bottom of tall to bottom of tall
2778 by_screen2_tall[1] = 0x0040;
2779 break;
2780 case 2: // From bottom of tall to top of tall
2781 by_screen2_tall[0] = 0xF040;
2782 break;
2783 }
2784 }
2785 }
2786
2787 for (int j = 0; j < 2; j++) {
2788 RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j],
2789 by_screen2_tall[j]));
2790 }
2791
2792 // byScreen3 = Transitioning down
2793 std::array<uint16_t, 2> by_screen3_tall = {0x1800, 0x1800};
2794
2795 // Check north neighbor
2796 if ((i % 0x40) - 8 >= 0) {
2797 auto& north_neighbor = overworld_maps_[i - 8];
2798
2799 // From bottom right of large to top of tall
2800 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2801 north_neighbor.large_index() == 3) {
2802 by_screen3_tall[0] = 0x17C0;
2803 }
2804 // From right of wide to top of tall
2805 else if (north_neighbor.area_size() == AreaSizeEnum::WideArea &&
2806 north_neighbor.large_index() == 1) {
2807 by_screen3_tall[0] = 0x17C0;
2808 }
2809 }
2810
2811 for (int j = 0; j < 2; j++) {
2812 RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j],
2813 by_screen3_tall[j]));
2814 }
2815
2816 // byScreen4 = Transitioning up
2817 std::array<uint16_t, 2> by_screen4_tall = {0x2000, 0x2000};
2818
2819 // Check south neighbor
2820 if ((i % 0x40) + 16 < 0x40 && i + 16 < kNumOverworldMaps) {
2821 auto& south_neighbor = overworld_maps_[i + 16];
2822
2823 // From top right of large to bottom of tall
2824 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2825 south_neighbor.large_index() == 1) {
2826 by_screen4_tall[1] = 0x1FC0;
2827 }
2828 // From right of wide to bottom of tall
2829 else if (south_neighbor.area_size() == AreaSizeEnum::WideArea &&
2830 south_neighbor.large_index() == 1) {
2831 by_screen4_tall[1] = 0x1FC0;
2832 }
2833 }
2834
2835 for (int j = 0; j < 2; j++) {
2836 RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j],
2837 by_screen4_tall[j]));
2838 }
2839
2840 return absl::OkStatus();
2841}
2842
2844 util::logf("Saving Large Maps (v3+ Expanded)");
2845
2846 // Use expanded memory locations for v3+
2847 int transition_target_north = zelda3::transition_target_northExpanded;
2848 int transition_target_west = zelda3::transition_target_westExpanded;
2849 int transition_pos_x = zelda3::GetOverworldTransitionPositionXExpanded();
2850 int transition_pos_y = zelda3::GetOverworldTransitionPositionYExpanded();
2851 int screen_change_1 = zelda3::GetOverworldScreenChange1Expanded();
2852 int screen_change_2 = zelda3::GetOverworldScreenChange2Expanded();
2853 int screen_change_3 = zelda3::GetOverworldScreenChange3Expanded();
2854 int screen_change_4 = zelda3::GetOverworldScreenChange4Expanded();
2855
2856 std::vector<uint8_t> checked_map;
2857
2858 // Process all overworld maps (0xA0 for v3)
2859 for (int i = 0; i < kNumOverworldMaps; ++i) {
2860 // Skip if this map was already processed as part of a multi-area structure
2861 if (std::find(checked_map.begin(), checked_map.end(), i) !=
2862 checked_map.end()) {
2863 continue;
2864 }
2865
2866 int parent_y_pos = (overworld_maps_[i].parent() % 0x40) / 8;
2867 int parent_x_pos = (overworld_maps_[i].parent() % 0x40) % 8;
2868
2869 // Write the map parent ID to expanded parent table
2872 overworld_maps_[i].parent()));
2873
2874 // Handle transitions based on area size
2875 switch (overworld_maps_[i].area_size()) {
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,
2881 screen_change_4));
2882 checked_map.emplace_back(i);
2883 break;
2884
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,
2890 screen_change_4));
2891 // Mark all 4 quadrants as processed
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);
2896 break;
2897
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,
2903 screen_change_4));
2904 // Mark both horizontal quadrants as processed
2905 checked_map.emplace_back(i);
2906 checked_map.emplace_back(i + 1);
2907 break;
2908
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,
2914 screen_change_4));
2915 // Mark both vertical quadrants as processed
2916 checked_map.emplace_back(i);
2917 checked_map.emplace_back(i + 8);
2918 break;
2919 }
2920 }
2921
2922 return absl::OkStatus();
2923}
2924
2925namespace {
2926std::vector<uint64_t> GetAllTile16(OverworldMapTiles& map_tiles_) {
2927 std::vector<uint64_t> all_tile_16; // Ensure it's 64 bits
2928
2929 int sx = 0;
2930 int sy = 0;
2931 int c = 0;
2932 OverworldBlockset tiles_used;
2933 for (int i = 0; i < kNumOverworldMaps; i++) {
2934 if (i < kDarkWorldMapIdStart) {
2935 tiles_used = map_tiles_.light_world;
2936 } else if (i < kSpecialWorldMapIdStart && i >= kDarkWorldMapIdStart) {
2937 tiles_used = map_tiles_.dark_world;
2938 } else {
2939 tiles_used = map_tiles_.special_world;
2940 }
2941
2942 for (int y = 0; y < 32; y += 2) {
2943 for (int x = 0; x < 32; x += 2) {
2944 gfx::Tile32 current_tile(
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)]);
2949
2950 all_tile_16.emplace_back(current_tile.GetPackedValue());
2951 }
2952 }
2953
2954 sx++;
2955 if (sx >= 8) {
2956 sy++;
2957 sx = 0;
2958 }
2959
2960 c++;
2961 if (c >= 64) {
2962 sx = 0;
2963 sy = 0;
2964 c = 0;
2965 }
2966 }
2967
2968 return all_tile_16;
2969}
2970} // namespace
2971
2975 return absl::OkStatus();
2976 tiles32_unique_.clear();
2977 tiles32_list_.clear();
2978
2979 ASSIGN_OR_RETURN(const auto storage_layout,
2980 ResolveMap32StorageLayout(*rom(), version_constants()));
2981
2982 // Get all tiles16 and packs them into tiles32
2983 std::vector<uint64_t> all_tile_16 = GetAllTile16(map_tiles_);
2984
2985 // Convert to set then back to vector
2986 std::set<uint64_t> unique_tiles_set(all_tile_16.begin(), all_tile_16.end());
2987
2988 std::vector<uint64_t> unique_tiles(all_tile_16);
2989 unique_tiles.assign(unique_tiles_set.begin(), unique_tiles_set.end());
2990
2991 // Create the indexed tiles list
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)});
2996 }
2997
2998 // Add all tiles32 from all maps.
2999 // Convert all tiles32 non-unique IDs into unique array of IDs.
3000 for (int j = 0; j < NumberOfMap32; j++) {
3001 tiles32_list_.emplace_back(all_tiles_indexed[all_tile_16[j]]);
3002 }
3003
3004 // Create the unique tiles list
3005 for (size_t i = 0; i < unique_tiles.size(); ++i) {
3006 tiles32_unique_.emplace_back(gfx::Tile32(unique_tiles[i]));
3007 }
3008
3009 while (tiles32_unique_.size() % 4 != 0) {
3010 gfx::Tile32 padding_tile(0, 0, 0, 0);
3011 tiles32_unique_.emplace_back(padding_tile.GetPackedValue());
3012 }
3013
3015 ValidateMap32DefinitionCount(tiles32_unique_.size(), storage_layout));
3016
3017 if (core::FeatureFlags::get().kLogToConsole) {
3018 std::cout << "Number of unique Tiles32: " << tiles32_unique_.size()
3019 << " Saved:" << tiles32_unique_.size()
3020 << " Out of: " << storage_layout.definition_capacity << std::endl;
3021 }
3022
3023 return absl::OkStatus();
3024}
3025
3029 return absl::OkStatus();
3030 if (MapTilesUnchanged()) {
3031 util::logf("Overworld tiles unchanged; preserving expanded tile32");
3032 return absl::OkStatus();
3033 }
3034 ASSIGN_OR_RETURN(const auto storage_layout,
3035 ResolveMap32StorageLayout(*rom(), version_constants()));
3036 if (!storage_layout.expanded) {
3037 return absl::FailedPreconditionError(
3038 "Expanded Tile32 save requested for a vanilla Tile32 layout");
3039 }
3040 // Capacity and alignment must be checked before updating relocation pointers
3041 // or writing any quadrant bytes.
3043 ValidateMap32DefinitionCount(tiles32_unique_.size(), storage_layout));
3044
3045 const int bottomLeft = GetMap32TileBLExpanded();
3046 const int bottomRight = GetMap32TileBRExpanded();
3047 const int topRight = GetMap32TileTRExpanded();
3048
3049 // Point the bank $02 tile32 loaders at the expanded quadrants. An operand
3050 // that already addresses its target is left alone: ZScream and Oracle of
3051 // Secrets write the FastROM mirror ($84:8000), and rewriting it as the
3052 // equivalent $04:8000 would change ASM-owned code bytes for no effect.
3053 const auto relocate = [this](int operand_pc, int target_pc) -> absl::Status {
3054 ASSIGN_OR_RETURN(const uint32_t current, rom()->ReadLong(operand_pc));
3055 if (SnesToPc(current) == static_cast<uint32_t>(target_pc)) {
3056 return absl::OkStatus();
3057 }
3058 return rom()->WriteLong(operand_pc, PcToSnes(target_pc));
3059 };
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) {
3067 RETURN_IF_ERROR(relocate(kTopRightOperands[i], topRight + i));
3068 RETURN_IF_ERROR(relocate(kBottomLeftOperands[i], bottomLeft + i));
3069 RETURN_IF_ERROR(relocate(kBottomRightOperands[i], bottomRight + i));
3070 }
3071
3072 int unique_tile_index = 0;
3073 const int encoded_bytes = static_cast<int>(tiles32_unique_.size() /
3076
3077 for (int i = 0; i < encoded_bytes; i += kMap32BytesPerPackedGroup) {
3078 // Top Left.
3079 auto top_left = version_constants().kMap32TileTL;
3080 RETURN_IF_ERROR(rom()->WriteByte(
3081 top_left + i,
3082 (uint8_t)(tiles32_unique_[unique_tile_index].tile0_ & 0xFF)));
3083 RETURN_IF_ERROR(rom()->WriteByte(
3084 top_left + (i + 1),
3085 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile0_ & 0xFF)));
3086 RETURN_IF_ERROR(rom()->WriteByte(
3087 top_left + (i + 2),
3088 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile0_ & 0xFF)));
3089 RETURN_IF_ERROR(rom()->WriteByte(
3090 top_left + (i + 3),
3091 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile0_ & 0xFF)));
3092
3093 RETURN_IF_ERROR(rom()->WriteByte(
3094 top_left + (i + 4),
3095 (uint8_t)(((tiles32_unique_[unique_tile_index].tile0_ >> 4) & 0xF0) +
3096 ((tiles32_unique_[unique_tile_index + 1].tile0_ >> 8) &
3097 0x0F))));
3098 RETURN_IF_ERROR(rom()->WriteByte(
3099 top_left + (i + 5),
3100 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile0_ >> 4) &
3101 0xF0) +
3102 ((tiles32_unique_[unique_tile_index + 3].tile0_ >> 8) &
3103 0x0F))));
3104
3105 // Top Right.
3106 auto top_right = topRight;
3107 RETURN_IF_ERROR(rom()->WriteByte(
3108 top_right + i,
3109 (uint8_t)(tiles32_unique_[unique_tile_index].tile1_ & 0xFF)));
3110 RETURN_IF_ERROR(rom()->WriteByte(
3111 top_right + (i + 1),
3112 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile1_ & 0xFF)));
3113 RETURN_IF_ERROR(rom()->WriteByte(
3114 top_right + (i + 2),
3115 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile1_ & 0xFF)));
3116 RETURN_IF_ERROR(rom()->WriteByte(
3117 top_right + (i + 3),
3118 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile1_ & 0xFF)));
3119
3120 RETURN_IF_ERROR(rom()->WriteByte(
3121 top_right + (i + 4),
3122 (uint8_t)(((tiles32_unique_[unique_tile_index].tile1_ >> 4) & 0xF0) |
3123 ((tiles32_unique_[unique_tile_index + 1].tile1_ >> 8) &
3124 0x0F))));
3125 RETURN_IF_ERROR(rom()->WriteByte(
3126 top_right + (i + 5),
3127 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile1_ >> 4) &
3128 0xF0) |
3129 ((tiles32_unique_[unique_tile_index + 3].tile1_ >> 8) &
3130 0x0F))));
3131
3132 // Bottom Left.
3133 auto bottom_left = bottomLeft;
3134 RETURN_IF_ERROR(rom()->WriteByte(
3135 bottom_left + i,
3136 (uint8_t)(tiles32_unique_[unique_tile_index].tile2_ & 0xFF)));
3137 RETURN_IF_ERROR(rom()->WriteByte(
3138 bottom_left + (i + 1),
3139 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile2_ & 0xFF)));
3140 RETURN_IF_ERROR(rom()->WriteByte(
3141 bottom_left + (i + 2),
3142 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile2_ & 0xFF)));
3143 RETURN_IF_ERROR(rom()->WriteByte(
3144 bottom_left + (i + 3),
3145 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile2_ & 0xFF)));
3146
3147 RETURN_IF_ERROR(rom()->WriteByte(
3148 bottom_left + (i + 4),
3149 (uint8_t)(((tiles32_unique_[unique_tile_index].tile2_ >> 4) & 0xF0) |
3150 ((tiles32_unique_[unique_tile_index + 1].tile2_ >> 8) &
3151 0x0F))));
3152 RETURN_IF_ERROR(rom()->WriteByte(
3153 bottom_left + (i + 5),
3154 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile2_ >> 4) &
3155 0xF0) |
3156 ((tiles32_unique_[unique_tile_index + 3].tile2_ >> 8) &
3157 0x0F))));
3158
3159 // Bottom Right.
3160 auto bottom_right = bottomRight;
3161 RETURN_IF_ERROR(rom()->WriteByte(
3162 bottom_right + i,
3163 (uint8_t)(tiles32_unique_[unique_tile_index].tile3_ & 0xFF)));
3164 RETURN_IF_ERROR(rom()->WriteByte(
3165 bottom_right + (i + 1),
3166 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile3_ & 0xFF)));
3167 RETURN_IF_ERROR(rom()->WriteByte(
3168 bottom_right + (i + 2),
3169 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile3_ & 0xFF)));
3170 RETURN_IF_ERROR(rom()->WriteByte(
3171 bottom_right + (i + 3),
3172 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile3_ & 0xFF)));
3173
3174 RETURN_IF_ERROR(rom()->WriteByte(
3175 bottom_right + (i + 4),
3176 (uint8_t)(((tiles32_unique_[unique_tile_index].tile3_ >> 4) & 0xF0) |
3177 ((tiles32_unique_[unique_tile_index + 1].tile3_ >> 8) &
3178 0x0F))));
3179 RETURN_IF_ERROR(rom()->WriteByte(
3180 bottom_right + (i + 5),
3181 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile3_ >> 4) &
3182 0xF0) |
3183 ((tiles32_unique_[unique_tile_index + 3].tile3_ >> 8) &
3184 0x0F))));
3185
3186 unique_tile_index += 4;
3187 }
3188
3189 return absl::OkStatus();
3190}
3191
3195 return absl::OkStatus();
3196 if (MapTilesUnchanged()) {
3197 util::logf("Overworld tiles unchanged; preserving tile32 definitions");
3198 return absl::OkStatus();
3199 }
3200 util::logf("Saving Map32 Tiles");
3201 ASSIGN_OR_RETURN(const auto storage_layout,
3202 ResolveMap32StorageLayout(*rom(), version_constants()));
3203 if (storage_layout.expanded) {
3204 return absl::FailedPreconditionError(
3205 "Vanilla Tile32 save requested for an expanded Tile32 layout");
3206 }
3208 ValidateMap32DefinitionCount(tiles32_unique_.size(), storage_layout));
3209
3210 int unique_tile_index = 0;
3211 const int encoded_bytes = static_cast<int>(tiles32_unique_.size() /
3214
3215 for (int i = 0; i < encoded_bytes; i += kMap32BytesPerPackedGroup) {
3216 // Top Left.
3217 auto top_left = version_constants().kMap32TileTL;
3218
3219 RETURN_IF_ERROR(rom()->WriteByte(
3220 top_left + i,
3221 (uint8_t)(tiles32_unique_[unique_tile_index].tile0_ & 0xFF)));
3222 RETURN_IF_ERROR(rom()->WriteByte(
3223 top_left + (i + 1),
3224 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile0_ & 0xFF)));
3225 RETURN_IF_ERROR(rom()->WriteByte(
3226 top_left + (i + 2),
3227 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile0_ & 0xFF)));
3228 RETURN_IF_ERROR(rom()->WriteByte(
3229 top_left + (i + 3),
3230 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile0_ & 0xFF)));
3231
3232 RETURN_IF_ERROR(rom()->WriteByte(
3233 top_left + (i + 4),
3234 (uint8_t)(((tiles32_unique_[unique_tile_index].tile0_ >> 4) & 0xF0) +
3235 ((tiles32_unique_[unique_tile_index + 1].tile0_ >> 8) &
3236 0x0F))));
3237 RETURN_IF_ERROR(rom()->WriteByte(
3238 top_left + (i + 5),
3239 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile0_ >> 4) &
3240 0xF0) +
3241 ((tiles32_unique_[unique_tile_index + 3].tile0_ >> 8) &
3242 0x0F))));
3243
3244 // Top Right.
3245 auto top_right = version_constants().kMap32TileTR;
3246 RETURN_IF_ERROR(rom()->WriteByte(
3247 top_right + i,
3248 (uint8_t)(tiles32_unique_[unique_tile_index].tile1_ & 0xFF)));
3249 RETURN_IF_ERROR(rom()->WriteByte(
3250 top_right + (i + 1),
3251 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile1_ & 0xFF)));
3252 RETURN_IF_ERROR(rom()->WriteByte(
3253 top_right + (i + 2),
3254 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile1_ & 0xFF)));
3255 RETURN_IF_ERROR(rom()->WriteByte(
3256 top_right + (i + 3),
3257 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile1_ & 0xFF)));
3258
3259 RETURN_IF_ERROR(rom()->WriteByte(
3260 top_right + (i + 4),
3261 (uint8_t)(((tiles32_unique_[unique_tile_index].tile1_ >> 4) & 0xF0) |
3262 ((tiles32_unique_[unique_tile_index + 1].tile1_ >> 8) &
3263 0x0F))));
3264 RETURN_IF_ERROR(rom()->WriteByte(
3265 top_right + (i + 5),
3266 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile1_ >> 4) &
3267 0xF0) |
3268 ((tiles32_unique_[unique_tile_index + 3].tile1_ >> 8) &
3269 0x0F))));
3270
3271 // Bottom Left.
3273 RETURN_IF_ERROR(rom()->WriteByte(
3274 map32TilesBL + i,
3275 (uint8_t)(tiles32_unique_[unique_tile_index].tile2_ & 0xFF)));
3276 RETURN_IF_ERROR(rom()->WriteByte(
3277 map32TilesBL + (i + 1),
3278 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile2_ & 0xFF)));
3279 RETURN_IF_ERROR(rom()->WriteByte(
3280 map32TilesBL + (i + 2),
3281 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile2_ & 0xFF)));
3282 RETURN_IF_ERROR(rom()->WriteByte(
3283 map32TilesBL + (i + 3),
3284 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile2_ & 0xFF)));
3285
3286 RETURN_IF_ERROR(rom()->WriteByte(
3287 map32TilesBL + (i + 4),
3288 (uint8_t)(((tiles32_unique_[unique_tile_index].tile2_ >> 4) & 0xF0) |
3289 ((tiles32_unique_[unique_tile_index + 1].tile2_ >> 8) &
3290 0x0F))));
3291 RETURN_IF_ERROR(rom()->WriteByte(
3292 map32TilesBL + (i + 5),
3293 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile2_ >> 4) &
3294 0xF0) |
3295 ((tiles32_unique_[unique_tile_index + 3].tile2_ >> 8) &
3296 0x0F))));
3297
3298 // Bottom Right.
3300 RETURN_IF_ERROR(rom()->WriteByte(
3301 map32TilesBR + i,
3302 (uint8_t)(tiles32_unique_[unique_tile_index].tile3_ & 0xFF)));
3303 RETURN_IF_ERROR(rom()->WriteByte(
3304 map32TilesBR + (i + 1),
3305 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile3_ & 0xFF)));
3306 RETURN_IF_ERROR(rom()->WriteByte(
3307 map32TilesBR + (i + 2),
3308 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile3_ & 0xFF)));
3309 RETURN_IF_ERROR(rom()->WriteByte(
3310 map32TilesBR + (i + 3),
3311 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile3_ & 0xFF)));
3312
3313 RETURN_IF_ERROR(rom()->WriteByte(
3314 map32TilesBR + (i + 4),
3315 (uint8_t)(((tiles32_unique_[unique_tile_index].tile3_ >> 4) & 0xF0) |
3316 ((tiles32_unique_[unique_tile_index + 1].tile3_ >> 8) &
3317 0x0F))));
3318 RETURN_IF_ERROR(rom()->WriteByte(
3319 map32TilesBR + (i + 5),
3320 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile3_ >> 4) &
3321 0xF0) |
3322 ((tiles32_unique_[unique_tile_index + 3].tile3_ >> 8) &
3323 0x0F))));
3324
3325 unique_tile_index += 4;
3326 }
3327
3328 return absl::OkStatus();
3329}
3330
3334 return absl::OkStatus();
3335 const int map16_expanded = GetMap16TilesExpanded();
3336 const auto write_pointer = [this](int address,
3337 uint32_t target) -> absl::Status {
3338 ASSIGN_OR_RETURN(auto current, rom()->ReadLong(address));
3339 // FastROM and low-bank aliases address the same data. Retain the original
3340 // instruction operand when no relocation is needed.
3341 if (SnesToPc(current) == SnesToPc(target)) {
3342 return absl::OkStatus();
3343 }
3344 return rom()->WriteLong(address, target);
3345 };
3346 const auto write_bank = [this](int address, uint8_t bank) -> absl::Status {
3347 ASSIGN_OR_RETURN(auto current, rom()->ReadByte(address));
3348 if ((current & 0x7F) == (bank & 0x7F)) {
3349 return absl::OkStatus();
3350 }
3351 return rom()->WriteByte(address, bank);
3352 };
3353 RETURN_IF_ERROR(write_pointer(SnesToPc(0x008865), PcToSnes(map16_expanded)));
3354 RETURN_IF_ERROR(write_pointer(SnesToPc(0x0EDE4F), PcToSnes(map16_expanded)));
3355 RETURN_IF_ERROR(write_pointer(SnesToPc(0x0EDEE9), PcToSnes(map16_expanded)));
3356
3358 write_pointer(SnesToPc(0x1BBC2D), PcToSnes(map16_expanded + 2)));
3359 RETURN_IF_ERROR(write_pointer(SnesToPc(0x1BBC4C), PcToSnes(map16_expanded)));
3361 write_pointer(SnesToPc(0x1BBCC2), PcToSnes(map16_expanded + 4)));
3363 write_pointer(SnesToPc(0x1BBCCB), PcToSnes(map16_expanded + 6)));
3364
3365 RETURN_IF_ERROR(write_pointer(SnesToPc(0x1BBEF6), PcToSnes(map16_expanded)));
3366 RETURN_IF_ERROR(write_pointer(SnesToPc(0x1BBF23), PcToSnes(map16_expanded)));
3367 RETURN_IF_ERROR(write_pointer(SnesToPc(0x1BC041), PcToSnes(map16_expanded)));
3368 RETURN_IF_ERROR(write_pointer(SnesToPc(0x1BC9B3), PcToSnes(map16_expanded)));
3369
3371 write_pointer(SnesToPc(0x1BC9BA), PcToSnes(map16_expanded + 2)));
3373 write_pointer(SnesToPc(0x1BC9C1), PcToSnes(map16_expanded + 4)));
3375 write_pointer(SnesToPc(0x1BC9C8), PcToSnes(map16_expanded + 6)));
3376
3377 RETURN_IF_ERROR(write_pointer(SnesToPc(0x1BCA40), PcToSnes(map16_expanded)));
3379 write_pointer(SnesToPc(0x1BCA47), PcToSnes(map16_expanded + 2)));
3381 write_pointer(SnesToPc(0x1BCA4E), PcToSnes(map16_expanded + 4)));
3383 write_pointer(SnesToPc(0x1BCA55), PcToSnes(map16_expanded + 6)));
3384
3385 RETURN_IF_ERROR(write_pointer(SnesToPc(0x02F457), PcToSnes(map16_expanded)));
3387 write_pointer(SnesToPc(0x02F45E), PcToSnes(map16_expanded + 2)));
3389 write_pointer(SnesToPc(0x02F467), PcToSnes(map16_expanded + 4)));
3391 write_pointer(SnesToPc(0x02F46E), PcToSnes(map16_expanded + 6)));
3392 RETURN_IF_ERROR(write_pointer(SnesToPc(0x02F51F), PcToSnes(map16_expanded)));
3394 write_pointer(SnesToPc(0x02F526), PcToSnes(map16_expanded + 4)));
3396 write_pointer(SnesToPc(0x02F52F), PcToSnes(map16_expanded + 2)));
3398 write_pointer(SnesToPc(0x02F536), PcToSnes(map16_expanded + 6)));
3399
3401 rom()->WriteShort(SnesToPc(0x02FE1C), PcToSnes(map16_expanded)));
3403 rom()->WriteShort(SnesToPc(0x02FE23), PcToSnes(map16_expanded + 4)));
3405 rom()->WriteShort(SnesToPc(0x02FE2C), PcToSnes(map16_expanded + 2)));
3407 rom()->WriteShort(SnesToPc(0x02FE33), PcToSnes(map16_expanded + 6)));
3408
3410 write_bank(SnesToPc(0x02FD28),
3411 static_cast<uint8_t>(PcToSnes(map16_expanded) >> 16)));
3413 write_bank(SnesToPc(0x02FD39),
3414 static_cast<uint8_t>(PcToSnes(map16_expanded) >> 16)));
3415
3416 int tpos = map16_expanded;
3417 for (int i = 0; i < NumberOfMap16Ex; i += 1) // 4096
3418 {
3420 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_)));
3421 tpos += 2;
3423 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_)));
3424 tpos += 2;
3426 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_)));
3427 tpos += 2;
3429 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_)));
3430 tpos += 2;
3431 }
3432
3433 return absl::OkStatus();
3434}
3435
3439 return absl::OkStatus();
3440 util::logf("Saving Map16 Tiles");
3441 int tpos = kMap16Tiles;
3442 // 3760
3443 for (int i = 0; i < NumberOfMap16; i += 1) {
3445 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_)))
3446 tpos += 2;
3448 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_)))
3449 tpos += 2;
3451 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_)))
3452 tpos += 2;
3454 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_)))
3455 tpos += 2;
3456 }
3457 return absl::OkStatus();
3458}
3459
3461 if (!rom_) {
3462 return absl::InvalidArgumentError("Cannot save entrances without a ROM");
3463 }
3464 const bool expanded = DetectOverworldRomProfile(*rom_).has_expanded_entrances;
3465 if (expanded) {
3466 if (all_holes_.size() != kNumOverworldHoles) {
3467 return absl::FailedPreconditionError(
3468 "Legacy hole source rows are missing");
3469 }
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");
3474 }
3475 }
3476 }
3479 if (!expanded) {
3481 }
3482 return absl::OkStatus();
3483}
3484
3485absl::Status Overworld::SaveExits() {
3487 return absl::OkStatus();
3488}
3489
3490absl::Status Overworld::SaveItems() {
3491 // A no-op item save must retain the existing allocation and pointer aliases.
3492 // Compare serialized fields (including duplicates and map identity). Record
3493 // order within each map controls the first matching secret, while
3494 // interleaving records from different maps has no effect. Read from current
3495 // ROM bytes so this also supports edits and Undo after an earlier successful
3496 // save.
3497 ASSIGN_OR_RETURN(auto stored_items, LoadItems(rom_, overworld_maps_));
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) {
3502 records.push_back(
3503 {item.room_map_id_, item.game_x_, item.game_y_, item.id_});
3504 }
3505 }
3506 std::stable_sort(records.begin(), records.end(),
3507 [](const auto& a, const auto& b) { return a[0] < b[0]; });
3508 return records;
3509 };
3510 if (serialized_items(stored_items) == serialized_items(all_items_)) {
3511 return absl::OkStatus();
3512 }
3514
3515 // Compact the in-memory vector: physically remove items flagged as deleted
3516 // so they don't accumulate across save cycles or leak through the API.
3517 all_items_.erase(
3518 std::remove_if(all_items_.begin(), all_items_.end(),
3519 [](const OverworldItem& item) { return item.deleted; }),
3520 all_items_.end());
3521
3522 return absl::OkStatus();
3523}
3524
3527 // This private transfer does not admit the legacy renderer generator.
3528 // Preserve a loaded no-op and reject edits rather than installing code.
3530 return absl::OkStatus();
3531 }
3532
3533 util::logf("Saving Map Overlays");
3534
3535 // Generate the new overlay code that handles interactive overlays
3536 std::vector<uint8_t> new_overlay_code = {
3537 0xC2, 0x30, // REP #$30
3538 0xA5, 0x8A, // LDA $8A
3539 0x0A, 0x18, // ASL : CLC
3540 0x65, 0x8A, // ADC $8A
3541 0xAA, // TAX
3542 0xBF, 0x00, 0x00, 0x00, // LDA, X
3543 0x85, 0x00, // STA $00
3544 0xBF, 0x00, 0x00, 0x00, // LDA, X +2
3545 0x85, 0x02, // STA $02
3546 0x4B, // PHK
3547 0xF4, 0x00, 0x00, // This position +3 ?
3548 0xDC, 0x00, 0x00, // JML [$00 00]
3549 0xE2, 0x30, // SEP #$30
3550 0xAB, // PLB
3551 0x6B, // RTL
3552 };
3553
3554 // Write overlay code to ROM
3555 constexpr int kOverlayCodeStart = 0x077657;
3556 RETURN_IF_ERROR(rom()->WriteVector(kOverlayCodeStart, new_overlay_code));
3557
3558 // Set up overlay pointers
3559 int ptr_start = kOverlayCodeStart + 0x20;
3560 int snes_ptr_start = PcToSnes(ptr_start);
3561
3562 // Write overlay pointer addresses in the code
3563 RETURN_IF_ERROR(rom()->WriteLong(kOverlayCodeStart + 10, snes_ptr_start));
3564 RETURN_IF_ERROR(rom()->WriteLong(kOverlayCodeStart + 16, snes_ptr_start + 2));
3565
3566 int pea_addr = PcToSnes(kOverlayCodeStart + 27);
3567 RETURN_IF_ERROR(rom()->WriteShort(kOverlayCodeStart + 23, pea_addr));
3568
3569 // Write overlay data to expanded space
3570 constexpr int kExpandedOverlaySpace = 0x120000;
3571 int pos = kExpandedOverlaySpace;
3572 int ptr_pos = kOverlayCodeStart + 32;
3573
3574 for (int i = 0; i < kNumOverworldMaps; i++) {
3575 int snes_addr = PcToSnes(pos);
3576 RETURN_IF_ERROR(rom()->WriteLong(ptr_pos, snes_addr & 0xFFFFFF));
3577 ptr_pos += 3;
3578
3579 // Write overlay data for each map that has overlays
3580 if (overworld_maps_[i].has_overlay()) {
3581 const auto& overlay_data = overworld_maps_[i].overlay_data();
3582 for (size_t t = 0; t < overlay_data.size(); t += 3) {
3583 if (t + 2 < overlay_data.size()) {
3584 // Generate LDA/STA sequence for each overlay tile
3585 RETURN_IF_ERROR(rom()->WriteByte(pos, 0xA9)); // LDA #$
3586 RETURN_IF_ERROR(rom()->WriteShort(
3587 pos + 1, overlay_data[t] | (overlay_data[t + 1] << 8)));
3588 pos += 3;
3589
3590 RETURN_IF_ERROR(rom()->WriteByte(pos, 0x8D)); // STA $xxxx
3591 RETURN_IF_ERROR(rom()->WriteShort(pos + 1, overlay_data[t + 2]));
3592 pos += 3;
3593 }
3594 }
3595 }
3596
3597 RETURN_IF_ERROR(rom()->WriteByte(pos, 0x6B)); // RTL
3598 pos++;
3599 }
3600
3601 return absl::OkStatus();
3602}
3603
3605 if (!rom_) {
3606 return absl::FailedPreconditionError("No ROM for collision save");
3607 }
3608 const auto layout = zscream::DetectLayout(*rom_);
3609 if (layout == zscream::LayoutDialect::kZScreamV5 ||
3611 return absl::FailedPreconditionError(
3612 "Legacy collision save is disabled for per-sheet or unknown ZScream "
3613 "layouts");
3614 }
3615 if (static_cast<size_t>(overworldTilesType) > rom_->vector().size() ||
3616 rom_->vector().size() - static_cast<size_t>(overworldTilesType) <
3617 all_tiles_types_.size()) {
3618 return absl::OutOfRangeError("Incomplete legacy collision save range");
3619 }
3620 util::logf("Saving Overworld Tiles Types");
3621
3622 for (int i = 0; i < kNumTileTypes; i++) {
3624 rom()->WriteByte(overworldTilesType + i, all_tiles_types_[i]));
3625 }
3626
3627 return absl::OkStatus();
3628}
3629
3631 // Diggable tiles have no ROM storage yet. The addresses in
3632 // diggable_tiles.h are ZSCustomOverworld v3 data: 0x140149 sits in its
3633 // settings block, and 0x140980-0x1409BF is OverworldCustomDefaultGFXGroups
3634 // followed by the expanded parent table (kOverworldMapParentIdExpanded).
3635 // Reading them as a diggable bitfield yields garbage, so the table always
3636 // starts from the vanilla list.
3638 return absl::OkStatus();
3639}
3640
3642 // Later steps of Save() read cached_version_.
3644 // Writing the diggable table would overwrite the ZSCustomOverworld v3
3645 // tables described in LoadDiggableTiles(), corrupting default GFX groups
3646 // and the bridge color on hacks such as Oracle of Secrets. Diggable edits
3647 // ship through the exported ASM patch until a non-colliding table exists.
3648 return absl::OkStatus();
3649}
3650
3652 util::logf("Auto-detecting Diggable Tiles");
3653
3655
3656 // Iterate through all Map16 tiles and check if they're diggable
3657 for (uint16_t tile_id = 0; tile_id < static_cast<uint16_t>(tiles16_.size()) &&
3658 tile_id < kMaxDiggableTileId;
3659 ++tile_id) {
3661 diggable_tiles_.SetDiggable(tile_id, true);
3662 }
3663 }
3664
3665 util::logf("Auto-detected %d diggable tiles",
3667 return absl::OkStatus();
3668}
3669
3670absl::Status Overworld::SaveCustomOverworldASM(bool enable_bg_color,
3671 bool enable_main_palette,
3672 bool enable_mosaic,
3673 bool enable_gfx_groups,
3674 bool enable_subscreen_overlay,
3675 bool enable_animated) {
3676 // Check ROM version - this function requires at least v2 for basic features
3679 return absl::OkStatus(); // Cannot apply custom ASM settings to vanilla/v1
3680 }
3681
3682 util::logf("Applying Custom Overworld ASM");
3683
3684 const auto write_enable_flag = [this](int address,
3685 bool enabled) -> absl::Status {
3686 ASSIGN_OR_RETURN(const uint8_t current, rom()->ReadByte(address));
3687 if ((current != 0x00) == enabled) {
3688 return absl::OkStatus();
3689 }
3690 return rom()->WriteByte(address, enabled ? 0xFF : 0x00);
3691 };
3692
3693 // v2+ features: BG color, main palette enable flags
3696 enable_bg_color));
3698 enable_main_palette));
3699
3700 // Write the main palette table
3701 if (enable_main_palette) {
3702 for (int i = 0; i < kNumOverworldMaps; i++) {
3704 overworld_maps_[i].main_palette()));
3705 }
3706 }
3707 }
3708
3709 // v3+ features: mosaic, gfx groups, animated, overlays
3712 write_enable_flag(OverworldCustomMosaicEnabled, enable_mosaic));
3714 enable_gfx_groups));
3716 write_enable_flag(OverworldCustomAnimatedGFXEnabled, enable_animated));
3718 enable_subscreen_overlay));
3719
3720 // Write the mosaic table
3721 if (enable_mosaic) {
3722 for (int i = 0; i < kNumOverworldMaps; i++) {
3723 const auto& mosaic = overworld_maps_[i].mosaic_expanded();
3724 // .... udlr bit format
3725 uint8_t mosaic_byte = (mosaic[0] ? 0x08 : 0x00) | // up
3726 (mosaic[1] ? 0x04 : 0x00) | // down
3727 (mosaic[2] ? 0x02 : 0x00) | // left
3728 (mosaic[3] ? 0x01 : 0x00); // right
3729
3731 rom()->WriteByte(OverworldCustomMosaicArray + i, mosaic_byte));
3732 }
3733 }
3734
3735 if (enable_gfx_groups) {
3736 for (int i = 0; i < kNumOverworldMaps; i++) {
3737 for (int j = 0; j < 8; j++) {
3739 rom()->WriteByte(OverworldCustomTileGFXGroupArray + (i * 8) + j,
3740 overworld_maps_[i].custom_tileset(j)));
3741 }
3742 }
3743 }
3744 if (enable_animated) {
3745 for (int i = 0; i < kNumOverworldMaps; i++) {
3747 overworld_maps_[i].animated_gfx()));
3748 }
3749 }
3750
3751 // Write the subscreen overlay table
3752 if (enable_subscreen_overlay) {
3753 for (int i = 0; i < kNumOverworldMaps; i++) {
3755 rom()->WriteShort(OverworldCustomSubscreenOverlayArray + (i * 2),
3756 overworld_maps_[i].subscreen_overlay()));
3757 }
3758 }
3759 }
3760
3761 return absl::OkStatus();
3762}
3763
3765 // Only write to custom address space for v2+ ROMs
3767 return absl::OkStatus(); // Vanilla/v1 ROM - skip custom address writes
3768 }
3769
3770 util::logf("Saving Area Specific Background Colors");
3771
3772 // Write area-specific background colors if enabled
3773 for (int i = 0; i < kNumOverworldMaps; i++) {
3774 uint16_t bg_color = overworld_maps_[i].area_specific_bg_color();
3775 RETURN_IF_ERROR(rom()->WriteShort(
3776 OverworldCustomAreaSpecificBGPalette + (i * 2), bg_color));
3777 }
3778
3779 return absl::OkStatus();
3780}
3781
3783 if (!rom_ || !rom_->is_loaded()) {
3784 return absl::FailedPreconditionError("ROM not loaded");
3785 }
3786
3790 return absl::OkStatus();
3791 }
3792
3793 const auto enabled = [this](int address) {
3794 return ReadRomByteOr(*rom_, address, 0x00) != 0x00;
3795 };
3796 const bool bg_color_enabled = enabled(OverworldCustomAreaSpecificBGEnabled);
3797 const bool main_palette_enabled = enabled(OverworldCustomMainPaletteEnabled);
3798 const bool mosaic_enabled = enabled(OverworldCustomMosaicEnabled);
3799 const bool gfx_groups_enabled = enabled(OverworldCustomTileGFXGroupEnabled);
3800 const bool subscreen_overlay_enabled =
3802 const bool animated_enabled = enabled(OverworldCustomAnimatedGFXEnabled);
3804 bg_color_enabled, main_palette_enabled, mosaic_enabled,
3805 gfx_groups_enabled, subscreen_overlay_enabled, animated_enabled));
3806 if (bg_color_enabled) {
3808 }
3810 return absl::OkStatus();
3811}
3812
3814 util::logf("Saving Map Properties");
3815 const auto version = OverworldVersionHelper::GetVersion(*rom_);
3816 const bool use_zscustom_palette_table =
3818 const bool use_zscustom_special_tables =
3820 const int palette_table = use_zscustom_palette_table
3823
3824 for (int i = 0; i < kDarkWorldMapIdStart; i++) {
3825 RETURN_IF_ERROR(rom()->WriteByte(
3826 kAreaGfxIdPtr + i, overworld_maps_[i].area_graphics_for_save()));
3827 RETURN_IF_ERROR(rom()->WriteByte(
3828 palette_table + i, overworld_maps_[i].area_palette_for_save()));
3830 rom()->WriteByte(kOverworldSpriteset + i,
3831 overworld_maps_[i].sprite_graphics_for_save(0)));
3833 rom()->WriteByte(kOverworldSpriteset + kDarkWorldMapIdStart + i,
3834 overworld_maps_[i].sprite_graphics_for_save(1)));
3837 overworld_maps_[i].sprite_graphics_for_save(2)));
3839 rom()->WriteByte(kOverworldSpritePaletteIds + i,
3840 overworld_maps_[i].sprite_palette_for_save(0)));
3843 overworld_maps_[i].sprite_palette_for_save(1)));
3844 RETURN_IF_ERROR(rom()->WriteByte(
3846 overworld_maps_[i].sprite_palette_for_save(2)));
3847 }
3848
3849 for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) {
3850 RETURN_IF_ERROR(rom()->WriteByte(
3851 kAreaGfxIdPtr + i, overworld_maps_[i].area_graphics_for_save()));
3854 overworld_maps_[i].sprite_graphics_for_save(0)));
3855 RETURN_IF_ERROR(rom()->WriteByte(
3856 palette_table + i, overworld_maps_[i].area_palette_for_save()));
3857 RETURN_IF_ERROR(rom()->WriteByte(
3859 overworld_maps_[i].sprite_palette_for_save(0)));
3860 }
3861
3862 for (int i = kSpecialWorldMapIdStart; i < kSpecialWorldMapIdStart + 0x20;
3863 i++) {
3864 if (use_zscustom_special_tables) {
3865 RETURN_IF_ERROR(rom()->WriteByte(
3866 kAreaGfxIdPtr + i, overworld_maps_[i].area_graphics_for_save()));
3867 }
3868 if (use_zscustom_palette_table) {
3869 RETURN_IF_ERROR(rom()->WriteByte(
3870 palette_table + i, overworld_maps_[i].area_palette_for_save()));
3871 }
3872
3873 const int local_index = i - kSpecialWorldMapIdStart;
3874 const int sprite_gfx_table =
3875 use_zscustom_special_tables
3878 const int sprite_palette_table =
3879 use_zscustom_special_tables ? kOverworldSpecialSpritePaletteExpandedTemp
3882 rom()->WriteByte(sprite_gfx_table + local_index,
3883 overworld_maps_[i].sprite_graphics_for_save(0)));
3885 rom()->WriteByte(sprite_palette_table + local_index,
3886 overworld_maps_[i].sprite_palette_for_save(0)));
3887 }
3888
3889 return absl::OkStatus();
3890}
3891
3892absl::Status Overworld::SaveMusic() {
3893 util::logf("Saving Music Data");
3894
3895 // Music is loaded and played by parent screen. A child of a large, wide, or
3896 // tall area carries its parent's track in memory, so writing that track into
3897 // the child slot replaces bytes the game does not use as music. Oracle keeps
3898 // overworld ASM hooks in part of the light-world beginning table.
3899 const auto owns_slot = [this](int map_index) {
3900 const int parent = overworld_maps_[map_index].parent();
3901 // A parent of 0xFF is not a table index. Music for that screen was not
3902 // loaded from its own slot, so leave the slot untouched.
3903 return parent == map_index;
3904 };
3905
3906 for (int i = 0; i < kDarkWorldMapIdStart; i++) {
3907 if (!owns_slot(i)) {
3908 continue;
3909 }
3911 overworld_maps_[i].area_music(0)));
3912 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicZelda + i,
3913 overworld_maps_[i].area_music(1)));
3915 overworld_maps_[i].area_music(2)));
3917 overworld_maps_[i].area_music(3)));
3918 }
3919
3920 for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) {
3921 if (!owns_slot(i)) {
3922 continue;
3923 }
3926 overworld_maps_[i].area_music(0)));
3927 }
3928
3929 return absl::OkStatus();
3930}
3931
3933 util::logf("Saving V3 Area Sizes");
3934
3935 // Check if this is a v3 ROM
3936 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
3937 if (asm_version < 3 || asm_version == 0xFF) {
3938 return absl::OkStatus(); // Not a v3 ROM, nothing to do
3939 }
3940
3941 // Save area sizes to the expanded table
3942 for (int i = 0; i < kNumOverworldMaps; i++) {
3943 uint8_t area_size_byte =
3944 static_cast<uint8_t>(overworld_maps_[i].area_size());
3945 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, area_size_byte));
3946 }
3947
3948 // Save message IDs to expanded table
3949 for (int i = 0; i < kNumOverworldMaps; i++) {
3950 uint16_t message_id = overworld_maps_[i].message_id();
3951 RETURN_IF_ERROR(rom()->WriteShort(GetOverworldMessagesExpanded() + (i * 2),
3952 message_id));
3953 }
3954
3955 return absl::OkStatus();
3956}
3957
3958} // namespace yaze::zelda3
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:52
const auto & vector() const
Definition rom.h:217
absl::StatusOr< uint16_t > ReadWord(int offset) const
Definition rom.cc:1044
auto data() const
Definition rom.h:213
auto size() const
Definition rom.h:212
absl::Status ValidateLyraPrivateReservation() const
Definition rom.cc:555
bool is_loaded() const
Definition rom.h:199
bool has_lyra_private_reservation() const
Definition rom.h:238
static Flags & get()
Definition features.h:122
RAII timer for automatic timing management.
Tile composition of four 16x16 tiles.
Definition snes_tile.h:93
uint64_t GetPackedValue() const
Definition snes_tile.h:123
SNES 16-bit tile metadata container.
Definition snes_tile.h:52
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()
Definition overworld.cc:915
std::vector< uint16_t > tiles32_list_
Definition overworld.h:982
void InvalidateTilesetCacheForSheet(int sheet)
Drop cached tilesets that load sheet (graphics sheet edits). Built maps keep their pixels; mark them ...
rom::LyraLayoutInspection descriptor_layout_
Definition overworld.h:923
absl::Status Load(Rom *rom)
Load all overworld data from ROM.
Definition overworld.cc:158
std::vector< OverworldItem > all_items_
Definition overworld.h:976
void OrganizeMapTiles(std::vector< uint8_t > &bytes, std::vector< uint8_t > &bytes2, int i, int sx, int sy, int &ttpos)
Definition overworld.cc:895
zelda3_version_pointers version_constants() const
Get version-specific ROM addresses.
Definition overworld.h:430
std::array< int, kNumOverworldMaps > map_pointers1
Definition overworld.h:994
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_
Definition overworld.h:980
absl::Status SetLyraA0Tile32(int x, int y, uint16_t tile)
Definition overworld.cc:359
const std::vector< uint8_t > & current_graphics() const
Definition overworld.h:797
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_
Definition overworld.h:960
std::vector< OverworldEntrance > all_entrances_
Definition overworld.h:973
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_
Definition overworld.h:922
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.
const std::vector< gfx::Tile16 > & tiles16() const
Definition overworld.h:784
std::array< uint8_t, kNumOverworldMaps > map_parent_
Definition overworld.h:985
void AssignWorldTiles(int x, int y, int sx, int sy, int tpos, OverworldBlockset &world)
Definition overworld.cc:850
absl::Status SaveDiggableTiles()
void InvalidateMapCache(int map_index)
Invalidate cached tileset for a specific map.
bool lyra_a0_terrain_writable() const
Definition overworld.h:457
std::array< uint16_t, 256 > lyra_a0_tiles_
Definition overworld.h:924
bool is_lyra_preview() const
Definition overworld.h:444
std::array< uint8_t, kNumTileTypes > all_tiles_types_
Definition overworld.h:986
int inspected_map_count() const
Definition overworld.h:447
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
Definition overworld.h:992
auto overworld_map(int i) const
Definition overworld.h:770
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)
Definition overworld.cc:862
bool HasExpandedPointerTables() const
Check if the ROM has expanded pointer tables for tail maps.
Definition overworld.h:713
uint64_t tileset_cache_clock_
Definition overworld.h:961
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)
Definition overworld.cc:873
auto mutable_overworld_map(int i)
Definition overworld.h:776
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.
absl::Status EnsureMapBuilt(int map_index)
Build a map on-demand if it hasn't been built yet.
std::vector< OverworldMap > overworld_maps_
Definition overworld.h:971
OverworldMapTiles loaded_map_tiles_
Definition overworld.h:932
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)
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)
Definition overworld.cc:287
absl::Status SaveOverworldMaps()
Save compressed map tile data to ROM.
std::array< std::vector< Sprite >, 3 > all_sprites_
Definition overworld.h:987
OverworldVersion cached_version_
Definition overworld.h:920
std::array< int, kNumOverworldMaps > map_pointers2_id
Definition overworld.h:993
absl::Status LoadOverworldMaps()
Load overworld map tile data.
std::vector< gfx::Tile16 > tiles16_
Definition overworld.h:978
absl::Status AutoDetectDiggableTiles()
TilesetRevisions SheetRevisionsForKey(const OverworldTilesetKey &key) const
OverworldGraphicsPreview graphics_preview_
Definition overworld.h:972
absl::Status AssembleMap16Tiles()
Definition overworld.cc:838
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_
Definition overworld.h:931
void set_game_state(int state)
Definition overworld.cc:154
std::array< std::vector< uint8_t >, kNumOverworldMaps > map_data_p1
Definition overworld.h:990
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+)
Definition overworld.cc:474
absl::Status SaveMap16Expanded()
Save expanded tile16 definitions (v1+ ROMs)
absl::Status BuildLyraTerrainPreview(Rom *rom, rom::LyraLayoutInspection layout)
Definition overworld.cc:303
std::vector< OverworldExit > all_exits_
Definition overworld.h:975
std::array< int, kNumOverworldMaps > map_pointers2
Definition overworld.h:995
absl::StatusOr< uint16_t > GetTile16ForTile32(int index, int quadrant, int dimension, const uint32_t *map32address)
Definition overworld.cc:726
std::vector< gfx::Tile16 > private_loaded_tiles16_
Definition overworld.h:930
std::array< std::vector< uint8_t >, kNumOverworldMaps > map_data_p2
Definition overworld.h:991
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_
Definition overworld.h:945
absl::Status SaveMapOverlays()
Save interactive overlay data to ROM.
absl::Status AssembleMap32Tiles()
Definition overworld.cc:737
static constexpr size_t kMaxCachedTilesets
Definition overworld.h:965
absl::StatusOr< std::array< uint8_t, kNumTileTypes > > GetTile8CollisionTypes(int map_id) const
DiggableTiles diggable_tiles_
Definition overworld.h:989
static constexpr int kMaxBuiltMaps
Definition overworld.h:944
std::array< std::array< bool, 160 >, 3 > sprite_maps_loaded_
Definition overworld.h:988
OverworldBlockset & GetMapTiles(int world_type)
Definition overworld.h:754
OverworldMapTiles staged_map_tiles_
Definition overworld.h:934
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)
Definition overworld.cc:558
std::vector< OverworldEntrance > all_holes_
Definition overworld.h:974
std::array< uint64_t, std::tuple_size_v< OverworldTilesetKey > > TilesetRevisions
Definition overworld.h:949
struct destination destination
Room transition destination.
#define LOG_DEBUG(category, format,...)
Definition log.h:133
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
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)
Definition snes_tile.cc:412
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)
Definition hex.cc:33
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
Definition log.h:146
std::string HexLong(uint32_t dword, HexStringParams params)
Definition hex.cc:55
std::vector< uint8_t > PrivateOverlaySnapshot(const std::vector< OverworldMap > &maps)
Definition overworld.cc:42
std::vector< uint64_t > GetAllTile16(OverworldMapTiles &map_tiles_)
absl::StatusOr< Map32StorageLayout > ResolveMap32StorageLayout(const Rom &rom, const zelda3_version_pointers &version_constants)
Definition overworld.cc:69
absl::Status ValidateMap32DefinitionCount(size_t definition_count, const Map32StorageLayout &layout)
Definition overworld.cc:124
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
Definition overworld.h:125
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
Definition overworld.h:149
constexpr int kNumMapsPerWorld
Definition overworld.h:271
constexpr int kOverworldSpriteset
Definition overworld.h:118
int GetMap32TileBRExpanded()
Definition overworld.h:212
int GetOverworldScreenChange3Expanded()
constexpr int kMap16ExpandedFlagPos
Definition overworld.h:173
constexpr int NumberOfMap16Ex
Definition overworld.h:247
int GetOverworldScreenChange4Expanded()
absl::StatusOr< std::vector< OverworldEntrance > > LoadEntrances(Rom *rom)
absl::Status SaveItems(Rom *rom, const std::vector< OverworldItem > &items)
constexpr int kOverworldScreenTileMapChangeByScreen1
Definition overworld.h:154
constexpr int kOverworldMapDataOverflow
Definition overworld.h:159
int LegacyParentTableIndexForMap(int map_index)
Definition overworld.h:391
constexpr int kMap32BytesPerPackedGroup
Definition overworld.h:252
constexpr int kOverworldMapSizeHighByte
Definition overworld.h:140
absl::StatusOr< std::vector< OverworldItem > > LoadItems(Rom *rom, std::vector< OverworldMap > &overworld_maps)
constexpr int kNumTileTypes
Definition overworld.h:241
uint8_t ReadRomByteOr(const Rom &rom, int address, uint8_t fallback)
Definition overworld.h:312
constexpr int NumberOfMap32
Definition overworld.h:270
constexpr int kOverworldScreenSize
Definition overworld.h:151
constexpr int kOverworldScreenTileMapChangeByScreen4
Definition overworld.h:157
constexpr int kNumTile16Individual
Definition overworld.h:244
constexpr int kNumOverworldHoles
int GetMap32TileTRExpanded()
Definition overworld.h:202
constexpr int kSpecialWorldMapIdStart
constexpr int OverworldCustomMosaicArray
constexpr int kMap16Tiles
Definition overworld.h:242
int GetMap32TileBLExpanded()
Definition overworld.h:207
constexpr int OverworldCustomAnimatedGFXEnabled
constexpr int Map32DefinitionCapacityForStorageBytes(int storage_bytes)
Definition overworld.h:256
constexpr int OverworldCustomMainPaletteEnabled
constexpr int kNumOverworldMaps
Definition common.h:85
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
Definition overworld.h:128
std::vector< std::vector< uint16_t > > OverworldBlockset
Represents tile32 data for the overworld.
AreaSizeEnum
Area size enumeration for v3+ ROMs.
constexpr int kOverworldTransitionPositionX
Definition overworld.h:150
std::array< std::array< std::optional< OverworldSpriteBytes >, 160 >, 3 > OverworldSpriteEdits
constexpr int kOverworldMusicDarkWorld
Definition overworld.h:132
std::array< uint8_t, 17 > OverworldTilesetKey
constexpr int kOverworldSpecialPalGroup
Definition overworld.h:120
constexpr int kOverworldScreenSizeForLoading
Definition overworld.h:152
constexpr int kOverworldSpritePaletteIds
Definition overworld.h:116
std::map< int, AreaGraphicsPreview > OverworldGraphicsPreview
constexpr int overworldTilesType
Definition overworld.h:193
constexpr int transition_target_westExpanded
int GetOverworldScreenChange1Expanded()
absl::StatusOr< std::vector< OverworldExit > > LoadExits(Rom *rom)
constexpr int kMap32TileCountExpanded
Definition overworld.h:171
constexpr int kTransitionTargetWest
Definition overworld.h:162
constexpr int OverworldCustomASMHasBeenApplied
Definition common.h:90
constexpr int kOverworldMusicAgahnim
Definition overworld.h:131
constexpr int kOverworldMapParentId
Definition overworld.h:148
absl::StatusOr< std::vector< gfx::Tile16 > > ReadMap16Tiles(const Rom &rom, bool *expanded)
Represents the full Overworld data, light and dark world.
Definition overworld.cc:797
constexpr int kMap32ExpandedFlagPos
Definition overworld.h:172
constexpr int kMap32DefinitionsPerPackedGroup
Definition overworld.h:251
int GetOverworldTransitionPositionXExpanded()
@ kZSCustomV1
Basic features, expanded pointers.
@ kVanilla
0xFF in ROM, no ZScream ASM applied
bool IsSameOverworldWorld(int a, int b)
Definition overworld.h:379
constexpr int kOverworldPalettesScreenToSetNew
constexpr int kOverworldMusicMasterSword
Definition overworld.h:130
constexpr int kOverworldMusicZelda
Definition overworld.h:129
constexpr int transition_target_northExpanded
constexpr int NumberOfMap16
Definition overworld.h:246
constexpr int kOverworldMapSize
Definition overworld.h:137
constexpr int kOverworldScreenTileMapChangeByScreen2
Definition overworld.h:155
constexpr int kOverworldSpecialSpritePalette
constexpr int OverworldCustomAnimatedGFXArray
constexpr int kDarkWorldMapIdStart
OverworldRomProfile DetectOverworldRomProfile(const Rom &rom)
Definition overworld.h:325
absl::Status SaveHoles(Rom *rom, const std::vector< OverworldEntrance > &holes)
bool CanPersistLegacyScreenSize(int map_index)
Definition overworld.h:387
absl::StatusOr< std::vector< OverworldEntrance > > LoadHoles(Rom *rom)
constexpr int OverworldCustomMosaicEnabled
constexpr int kEssentialMapsPerWorld
Definition overworld.h:103
constexpr int kOverworldCompressedMapPos
Definition overworld.h:238
constexpr int kMaxDiggableTileId
int WorldForOverworldMap(int map_index)
Definition overworld.h:369
bool IsBackgroundSubscreenOverlay(uint16_t overlay_id)
absl::StatusOr< OverworldSpriteSavePlan > PlanOverworldSpriteSave(const Rom &rom, const OverworldSpriteEdits &edits)
constexpr int kOverworldSpecialSpriteGFXGroup
constexpr int kOverworldScreenTileMapChangeByScreen3
Definition overworld.h:156
constexpr int OverworldCustomTileGFXGroupArray
bool CanPersistLegacyMultiAreaMap(int map_index)
Definition overworld.h:383
uint8_t LegacyParentTableValueForMap(int parent_index)
Definition overworld.h:395
int SubscreenOverlayScreen(uint16_t overlay_id)
int Map32StorageBytesForProfile(const OverworldRomProfile &profile)
Definition overworld.h:297
constexpr int OverworldCustomSubscreenOverlayEnabled
constexpr int OverworldCustomAreaSpecificBGPalette
int GetOverworldMessagesExpanded()
int LegacyScreenSizeTableIndexForMap(int map_index)
Definition overworld.h:399
OverworldSpriteLayout GetOverworldSpriteLayout(const Rom &rom)
constexpr int kTransitionTargetNorth
Definition overworld.h:161
constexpr int kOverworldCompressedOverflowPos
Definition overworld.h:239
int GetOverworldScreenChange2Expanded()
constexpr int kOverlayCodeStart
absl::Status SaveExits(Rom *rom, const std::vector< OverworldExit > &exits)
int GetMap16TilesExpanded()
Definition overworld.h:197
constexpr int kOverworldMapPaletteIds
Definition overworld.h:115
int GetOverworldTransitionPositionYExpanded()
constexpr int kOverworldSpecialGfxGroup
Definition overworld.h:119
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
std::vector< OverworldSpriteWrite > OverworldSpriteSavePlan
constexpr int OverworldCustomSubscreenOverlayArray
uint32_t PcToSnes(uint32_t addr)
Definition snes.h:17
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Room transition destination.
Definition zelda.h:448
struct yaze::core::FeatureFlags::Flags::Overworld overworld
Overworld map tile32 data.
Subscreen overlay layer for one screen, in that screen's palette.
Definition overworld.h:508
ROM data pointers for different game versions.
Definition zelda.h:71
uint32_t kOverworldTilesType
Definition zelda.h:101
uint32_t kMap32TileTR
Definition zelda.h:106
uint32_t kCompressedAllMap32PointersHigh
Definition zelda.h:96
uint32_t kMap32TileTL
Definition zelda.h:105
uint32_t kMap32TileBL
Definition zelda.h:107
uint32_t kMap32TileBR
Definition zelda.h:108
uint32_t kCompressedAllMap32PointersLow
Definition zelda.h:97