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