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 "util/hex.h"
26#include "util/log.h"
27#include "util/macro.h"
28#include "zelda3/common.h"
34
35namespace yaze::zelda3 {
36
37namespace {
38
40 std::array<int, 4> destinations{};
41 int storage_bytes_per_quadrant = 0;
42 int definition_capacity = 0;
43 bool expanded = false;
44};
45
46absl::StatusOr<Map32StorageLayout> ResolveMap32StorageLayout(
47 const Rom& rom, const zelda3_version_pointers& version_constants) {
48 const auto profile = DetectOverworldRomProfile(rom);
49
50 Map32StorageLayout layout;
51 layout.expanded = profile.has_expanded_tile32;
52 layout.destinations = {
53 static_cast<int>(version_constants.kMap32TileTL),
55 : static_cast<int>(version_constants.kMap32TileTR),
57 : static_cast<int>(version_constants.kMap32TileBL),
59 : static_cast<int>(version_constants.kMap32TileBR),
60 };
61
62 int usable_bytes = Map32StorageBytesForProfile(profile);
63 for (int destination : layout.destinations) {
64 if (destination < 0 || static_cast<size_t>(destination) >= rom.size()) {
65 return absl::OutOfRangeError(absl::StrFormat(
66 "Tile32 destination 0x%06X is outside the ROM", destination));
67 }
68 const size_t available = rom.size() - static_cast<size_t>(destination);
69 usable_bytes = std::min(usable_bytes,
70 static_cast<int>(std::min<size_t>(
71 available, std::numeric_limits<int>::max())));
72 }
73
74 // ROM EOF is not the only boundary: quadrant tables must not grow into the
75 // next configured destination. This matters for layouts such as the JP ROM,
76 // whose vanilla TL/TR and BL/BR spans are 0x33C0 rather than 0x33F0 bytes.
77 auto ordered_destinations = layout.destinations;
78 std::sort(ordered_destinations.begin(), ordered_destinations.end());
79 for (size_t i = 1; i < ordered_destinations.size(); ++i) {
80 const int span = ordered_destinations[i] - ordered_destinations[i - 1];
81 if (span <= 0) {
82 return absl::FailedPreconditionError(
83 "Tile32 destinations overlap or share the same address");
84 }
85 usable_bytes = std::min(usable_bytes, span);
86 }
87
88 // A partial packed group cannot store any additional definitions.
89 usable_bytes -= usable_bytes % kMap32BytesPerPackedGroup;
90 if (usable_bytes <= 0) {
91 return absl::ResourceExhaustedError(
92 "Tile32 destinations have no usable packed storage");
93 }
94
95 layout.storage_bytes_per_quadrant = usable_bytes;
96 layout.definition_capacity =
98 return layout;
99}
100
101absl::Status ValidateMap32DefinitionCount(size_t definition_count,
102 const Map32StorageLayout& layout) {
103 if (definition_count % kMap32DefinitionsPerPackedGroup != 0) {
104 return absl::InvalidArgumentError(absl::StrFormat(
105 "Tile32 definition count %zu is not aligned to a packed group of %d",
106 definition_count, kMap32DefinitionsPerPackedGroup));
107 }
108 if (definition_count > static_cast<size_t>(layout.definition_capacity)) {
109 return absl::ResourceExhaustedError(absl::StrFormat(
110 "Number of unique Tiles32: %zu Out of: %d\n"
111 "Unique Tile32 count exceeds the %s destination capacity; the ROM has "
112 "not been written",
113 definition_count, layout.definition_capacity,
114 layout.expanded ? "expanded" : "vanilla"));
115 }
116
117 const size_t required_bytes =
118 (definition_count / kMap32DefinitionsPerPackedGroup) *
120 if (required_bytes > static_cast<size_t>(layout.storage_bytes_per_quadrant)) {
121 return absl::ResourceExhaustedError(absl::StrFormat(
122 "Tile32 definitions require %zu bytes per quadrant; destination "
123 "capacity is %d bytes",
124 required_bytes, layout.storage_bytes_per_quadrant));
125 }
126 return absl::OkStatus();
127}
128
129} // namespace
130
131absl::Status Overworld::Load(Rom* rom) {
132 gfx::ScopedTimer timer("Overworld::Load");
133
134 if (rom->size() == 0) {
135 return absl::InvalidArgumentError("ROM file not loaded");
136 }
137 rom_ = rom;
138
139 const auto profile = DetectOverworldRomProfile(*rom_);
140 // Cache ROM version to avoid repeated detection (called 160+ times otherwise)
141 cached_version_ = profile.version;
142 expanded_entrances_ = profile.has_expanded_entrances;
143
144 // Phase 1: Tile Assembly (can be parallelized)
145 {
146 gfx::ScopedTimer assembly_timer("AssembleTiles");
149 }
150
151 // Phase 2: Map Decompression (major bottleneck - now parallelized)
152 {
153 gfx::ScopedTimer decompression_timer("DecompressAllMapTiles");
155 }
156
157 // Phase 3: Map Object Creation (fast)
158 {
159 gfx::ScopedTimer map_creation_timer("CreateOverworldMapObjects");
160 for (int map_index = 0; map_index < kNumOverworldMaps; ++map_index)
161 overworld_maps_.emplace_back(map_index, rom_, game_data_);
162
163 // Populate map_parent_ array with parent information from each map
164 for (int map_index = 0; map_index < kNumOverworldMaps; ++map_index) {
165 map_parent_[map_index] = overworld_maps_[map_index].parent();
166 }
167 }
168
169 // Phase 4: Map Configuration (uses cached_version_ for performance)
172 } else {
174 }
175
176 // Phase 5: Data Loading (with individual timing)
177 {
178 gfx::ScopedTimer data_loading_timer("LoadOverworldData");
179
180 {
181 gfx::ScopedTimer tile_types_timer("LoadTileTypes");
183 }
184
185 {
186 gfx::ScopedTimer diggable_tiles_timer("LoadDiggableTiles");
188 }
189
190 {
191 gfx::ScopedTimer entrances_timer("LoadEntrances");
193 }
194
195 {
196 gfx::ScopedTimer holes_timer("LoadHoles");
198 }
199
200 {
201 gfx::ScopedTimer exits_timer("LoadExits");
203 }
204
205 {
206 gfx::ScopedTimer items_timer("LoadItems");
208 }
209
210 {
211 gfx::ScopedTimer overworld_maps_timer("LoadOverworldMaps");
213 }
214
215 {
216 gfx::ScopedTimer sprites_timer("LoadSprites");
218 }
219 }
220
221 is_loaded_ = true;
222 return absl::OkStatus();
223}
224
226 // For vanilla/v1/v2 ROMs, parent IDs are already loaded from ROM in the
227 // OverworldMap constructor. This function now uses those ROM values instead
228 // of recalculating them, ensuring custom parent mappings are respected.
229 //
230 // The function determines large_index_ (quadrant) based on the relationship
231 // between map index and its parent, rather than grid-walking.
232
233 // First pass: Set up special world maps using ROM data
234 for (int i = 128; i < kNumOverworldMaps; i++) {
235 int parent = overworld_maps_[i].parent();
236
237 if (overworld_maps_[i].is_large_map()) {
238 // Calculate quadrant based on position relative to parent
239 int quadrant = 0;
240 if (i == parent) {
241 quadrant = 0; // Top-left (parent itself)
242 } else if (i == parent + 1) {
243 quadrant = 1; // Top-right
244 } else if (i == parent + 8) {
245 quadrant = 2; // Bottom-left
246 } else if (i == parent + 9) {
247 quadrant = 3; // Bottom-right
248 }
249 // Use SetAsLargeMap but pass the ROM parent value, not a calculated one
250 overworld_maps_[i].SetAsLargeMap(parent, quadrant);
251 } else {
252 overworld_maps_[i].SetAsSmallMap(i);
253 }
254 }
255
256 // Track visited maps across LW/DW (0x00-0x7F)
257 std::array<bool, kNumOverworldMaps> map_checked{};
258 std::ranges::fill(map_checked, false);
259
260 // Second pass: Process LW/DW maps using ROM parent values
261 for (int world_offset = 0; world_offset < 128; world_offset += 64) {
262 for (int local = 0; local < 64; local++) {
263 int i = world_offset + local;
264
265 if (map_checked[i])
266 continue;
267
268 int parent = overworld_maps_[i].parent();
269
270 if (overworld_maps_[i].is_large_map()) {
271 // This map is part of a large area - set up all 4 quadrants
272 // The parent value from ROM tells us which map is the parent
273
274 // Calculate quadrant based on position relative to parent
275 int quadrant = 0;
276 if (i == parent) {
277 quadrant = 0; // This IS the parent (top-left)
278 } else if (i == parent + 1) {
279 quadrant = 1; // Top-right
280 } else if (i == parent + 8) {
281 quadrant = 2; // Bottom-left
282 } else if (i == parent + 9) {
283 quadrant = 3; // Bottom-right
284 }
285
286 overworld_maps_[i].SetAsLargeMap(parent, quadrant);
287 map_checked[i] = true;
288
289 // Mark siblings as checked and set their quadrants
290 // Use the ROM parent value for all siblings
291 // Ensure siblings stay within the same world to prevent cross-world issues
292 std::array<int, 4> siblings = {parent, parent + 1, parent + 8,
293 parent + 9};
294 int world_start = world_offset;
295 int world_end = world_offset + 64;
296 for (int q = 0; q < 4; q++) {
297 int sibling = siblings[q];
298 // Check sibling is within the same world (LW: 0-63, DW: 64-127)
299 if (sibling >= world_start && sibling < world_end &&
300 !map_checked[sibling]) {
301 overworld_maps_[sibling].SetAsLargeMap(parent, q);
302 map_checked[sibling] = true;
303 }
304 }
305 } else {
306 // Small map - parent should be itself
307 overworld_maps_[i].SetAsSmallMap(i);
308 map_checked[i] = true;
309 }
310 }
311 }
312}
313
318void Overworld::AssignMapSizes(std::vector<OverworldMap>& maps) {
319 std::vector<bool> map_checked(kNumOverworldMaps, false);
320
321 int xx = 0;
322 int yy = 0;
323 int world = 0;
324
325 while (true) {
326 int i = world + xx + (yy * 8);
327
328 if (i >= static_cast<int>(map_checked.size())) {
329 break;
330 }
331
332 if (!map_checked[i]) {
333 switch (maps[i].area_size()) {
335 map_checked[i] = true;
336 maps[i].SetAreaSize(AreaSizeEnum::SmallArea);
337 break;
338
340 map_checked[i] = true;
341 maps[i].SetAsLargeMap(i, 0);
342
343 if (i + 1 < static_cast<int>(maps.size())) {
344 map_checked[i + 1] = true;
345 maps[i + 1].SetAsLargeMap(i, 1);
346 }
347
348 if (i + 8 < static_cast<int>(maps.size())) {
349 map_checked[i + 8] = true;
350 maps[i + 8].SetAsLargeMap(i, 2);
351 }
352
353 if (i + 9 < static_cast<int>(maps.size())) {
354 map_checked[i + 9] = true;
355 maps[i + 9].SetAsLargeMap(i, 3);
356 }
357
358 xx++;
359 break;
360
362 map_checked[i] = true;
363 // CRITICAL FIX: Set parent for wide area maps
364 // Map i is parent (left), map i+1 is child (right)
365 maps[i].SetParent(i); // Parent points to itself
366 maps[i].SetAreaSize(AreaSizeEnum::WideArea);
367
368 if (i + 1 < static_cast<int>(maps.size())) {
369 map_checked[i + 1] = true;
370 maps[i + 1].SetParent(i); // Child points to parent
371 maps[i + 1].SetAreaSize(AreaSizeEnum::WideArea);
372 }
373
374 xx++;
375 break;
376
378 map_checked[i] = true;
379 // CRITICAL FIX: Set parent for tall area maps
380 // Map i is parent (top), map i+8 is child (bottom)
381 maps[i].SetParent(i); // Parent points to itself
382 maps[i].SetAreaSize(AreaSizeEnum::TallArea);
383
384 if (i + 8 < static_cast<int>(maps.size())) {
385 map_checked[i + 8] = true;
386 maps[i + 8].SetParent(i); // Child points to parent
387 maps[i + 8].SetAreaSize(AreaSizeEnum::TallArea);
388 }
389 break;
390 }
391 }
392
393 xx++;
394 if (xx >= 8) {
395 xx = 0;
396 yy += 1;
397
398 if (yy >= 8) {
399 yy = 0;
400 world += 0x40;
401 }
402 }
403 }
404}
405
406absl::Status Overworld::ConfigureMultiAreaMap(int parent_index,
407 AreaSizeEnum size) {
408 if (parent_index < 0 || parent_index >= kNumOverworldMaps) {
409 return absl::InvalidArgumentError(
410 absl::StrFormat("Invalid parent index: %d", parent_index));
411 }
412
413 // Version requirements:
414 // - Vanilla (0xFF): Supports Small and Large only
415 // - v1-v2: Supports Small and Large only
416 // - v3+: Supports all 4 sizes (Small, Large, Wide, Tall)
417 if ((size == AreaSizeEnum::WideArea || size == AreaSizeEnum::TallArea) &&
419 return absl::FailedPreconditionError(
420 "Wide and Tall areas require ZSCustomOverworld v3+");
421 }
423 !CanPersistLegacyMultiAreaMap(parent_index)) {
424 return absl::FailedPreconditionError(
425 "Special World multi-area changes require ZSCustomOverworld v3+");
426 }
427
428 LOG_DEBUG("Overworld",
429 "ConfigureMultiAreaMap: parent=%d, current_size=%d, new_size=%d, "
430 "version=%s",
431 parent_index,
432 static_cast<int>(overworld_maps_[parent_index].area_size()),
433 static_cast<int>(size),
435
436 // CRITICAL: First, get OLD siblings (before changing) so we can reset them
437 std::vector<int> old_siblings;
438 auto old_size = overworld_maps_[parent_index].area_size();
439 int old_parent = overworld_maps_[parent_index].parent();
440
441 switch (old_size) {
443 old_siblings = {old_parent, old_parent + 1, old_parent + 8,
444 old_parent + 9};
445 break;
447 old_siblings = {old_parent, old_parent + 1};
448 break;
450 old_siblings = {old_parent, old_parent + 8};
451 break;
452 default:
453 old_siblings = {parent_index}; // Was small, just this map
454 break;
455 }
456
457 // Validate the NEW sibling set before mutating so invalid edge maps do not
458 // partially reset the old area.
459 std::vector<int> new_siblings;
460 switch (size) {
462 new_siblings = {parent_index};
463 break;
465 new_siblings = {parent_index, parent_index + 1, parent_index + 8,
466 parent_index + 9};
467 break;
469 new_siblings = {parent_index, parent_index + 1};
470 break;
472 new_siblings = {parent_index, parent_index + 8};
473 break;
474 }
475 for (int sibling : new_siblings) {
476 if (sibling < 0 || sibling >= kNumOverworldMaps ||
477 !IsSameOverworldWorld(parent_index, sibling)) {
478 return absl::FailedPreconditionError(
479 absl::StrFormat("Area size crosses an overworld boundary: parent=%d "
480 "sibling=%d",
481 parent_index, sibling));
482 }
483 }
484
485 // Reset all old siblings to SmallArea first (clean slate).
486 for (int old_sibling : old_siblings) {
487 if (old_sibling >= 0 && old_sibling < kNumOverworldMaps) {
488 overworld_maps_[old_sibling].SetAsSmallMap(old_sibling);
489 }
490 }
491
492 // Now configure NEW siblings based on requested size.
493 switch (size) {
495 overworld_maps_[parent_index].SetParent(parent_index);
496 overworld_maps_[parent_index].SetAreaSize(AreaSizeEnum::SmallArea);
497 break;
498
500 for (size_t i = 0; i < new_siblings.size(); ++i) {
501 overworld_maps_[new_siblings[i]].SetAsLargeMap(parent_index, i);
502 }
503 break;
504
506 for (int sibling : new_siblings) {
507 overworld_maps_[sibling].SetParent(parent_index);
508 overworld_maps_[sibling].SetAreaSize(AreaSizeEnum::WideArea);
509 }
510 break;
511
513 for (int sibling : new_siblings) {
514 overworld_maps_[sibling].SetParent(parent_index);
515 overworld_maps_[sibling].SetAreaSize(AreaSizeEnum::TallArea);
516 }
517 break;
518 }
519
520 // Update ROM data for ALL affected siblings (old + new)
521 std::set<int> all_affected;
522 for (int sibling : old_siblings) {
523 all_affected.insert(sibling);
524 }
525 for (int sibling : new_siblings) {
526 all_affected.insert(sibling);
527 }
528
530 // v3+: Update expanded tables
531 for (int sibling : all_affected) {
532 if (sibling < 0 || sibling >= kNumOverworldMaps)
533 continue;
534
536 rom()->WriteByte(GetOverworldMapParentIdExpanded() + sibling,
537 overworld_maps_[sibling].parent()));
538 RETURN_IF_ERROR(rom()->WriteByte(
539 kOverworldScreenSize + sibling,
540 static_cast<uint8_t>(overworld_maps_[sibling].area_size())));
541 }
542 } else {
543 // Vanilla/v1/v2: the parent and size tables are 64-entry legacy tables
544 // shared by Light World and Dark World. Store local indices only; writing
545 // full map ids would run into adjacent ROM data and cannot persist Special
546 // World layout changes.
547 for (int sibling : all_affected) {
548 if (!CanPersistLegacyMultiAreaMap(sibling)) {
549 continue;
550 }
551
552 const int parent_table_index = LegacyParentTableIndexForMap(sibling);
553 RETURN_IF_ERROR(rom()->WriteByte(
554 kOverworldMapParentId + parent_table_index,
556 if (CanPersistLegacyScreenSize(sibling)) {
557 const int screen_size_index = LegacyScreenSizeTableIndexForMap(sibling);
558 RETURN_IF_ERROR(rom()->WriteByte(
559 kOverworldScreenSize + screen_size_index,
560 (overworld_maps_[sibling].area_size() == AreaSizeEnum::LargeArea)
561 ? 0x00
562 : 0x01));
563 }
564 }
565 }
566
567 LOG_DEBUG("Overworld",
568 "Configured %s area: parent=%d, old_siblings=%zu, new_siblings=%zu",
569 (size == AreaSizeEnum::LargeArea) ? "Large"
570 : (size == AreaSizeEnum::WideArea) ? "Wide"
571 : (size == AreaSizeEnum::TallArea) ? "Tall"
572 : "Small",
573 parent_index, old_siblings.size(), new_siblings.size());
574
575 return absl::OkStatus();
576}
577
578absl::StatusOr<uint16_t> Overworld::GetTile16ForTile32(
579 int index, int quadrant, int dimension, const uint32_t* map32address) {
581 auto arg1, rom()->ReadByte(map32address[dimension] + quadrant + (index)));
582 ASSIGN_OR_RETURN(auto arg2,
583 rom()->ReadWord(map32address[dimension] + (index) +
584 (quadrant <= 1 ? 4 : 5)));
585 return (uint16_t)(arg1 +
586 (((arg2 >> (quadrant % 2 == 0 ? 4 : 0)) & 0x0F) * 256));
587}
588
590 constexpr int kMap32TilesLength = 0x33F0;
591 int num_tile32 = kMap32TilesLength;
592 uint32_t map32address[4] = {
595
596 // Check if expanded tile32 data is actually present in ROM
597 // The flag position should contain 0x04 for vanilla, something else for
598 // expanded
599 const auto profile = DetectOverworldRomProfile(*rom());
600 uint8_t expanded_flag = ReadRomByteOr(*rom(), kMap32ExpandedFlagPos, 0x04);
601 util::logf("Expanded tile32 flag: %d", expanded_flag);
602 if (profile.has_expanded_tile32) {
603 // ROM has expanded tile32 data - use expanded addresses
604 map32address[0] = version_constants().kMap32TileTL;
605 map32address[1] = GetMap32TileTRExpanded();
606 map32address[2] = GetMap32TileBLExpanded();
607 map32address[3] = GetMap32TileBRExpanded();
608 num_tile32 = kMap32TileCountExpanded;
609 expanded_tile32_ = true;
610 }
611 // Otherwise use vanilla addresses (already set above)
612
613 // Loop through each 32x32 pixel tile in the rom
614 for (int i = 0; i < num_tile32; i += 6) {
615 // Loop through each quadrant of the 32x32 pixel tile.
616 for (int k = 0; k < 4; k++) {
617 // Generate the 16-bit tile for the current quadrant of the current
618 // 32x32 pixel tile.
620 uint16_t tl,
621 GetTile16ForTile32(i, k, (int)Dimension::map32TilesTL, map32address));
623 uint16_t tr,
624 GetTile16ForTile32(i, k, (int)Dimension::map32TilesTR, map32address));
626 uint16_t bl,
627 GetTile16ForTile32(i, k, (int)Dimension::map32TilesBL, map32address));
629 uint16_t br,
630 GetTile16ForTile32(i, k, (int)Dimension::map32TilesBR, map32address));
631
632 // Add the generated 16-bit tiles to the tiles32 vector.
633 tiles32_unique_.emplace_back(gfx::Tile32(tl, tr, bl, br));
634 }
635 }
636
637 map_tiles_.light_world.resize(0x200);
638 map_tiles_.dark_world.resize(0x200);
639 map_tiles_.special_world.resize(0x200);
640 for (int i = 0; i < 0x200; i++) {
641 map_tiles_.light_world[i].resize(0x200);
642 map_tiles_.dark_world[i].resize(0x200);
643 map_tiles_.special_world[i].resize(0x200);
644 }
645
646 return absl::OkStatus();
647}
648
650 int tpos = kMap16Tiles;
651 int num_tile16 = kNumTile16Individual;
652
653 // Check if expanded tile16 data is actually present in ROM
654 // The flag position should contain 0x0F for vanilla, something else for
655 // expanded
656 const auto profile = DetectOverworldRomProfile(*rom());
657 uint8_t expanded_flag = ReadRomByteOr(*rom(), kMap16ExpandedFlagPos, 0x0F);
658 util::logf("Expanded tile16 flag: %d", expanded_flag);
659 if (profile.has_expanded_tile16) {
660 // ROM has expanded tile16 data - use expanded addresses
661 tpos = GetMap16TilesExpanded();
662 num_tile16 = NumberOfMap16Ex;
663 expanded_tile16_ = true;
664 }
665 // Otherwise use vanilla addresses (already set above)
666
667 for (int i = 0; i < num_tile16; i += 1) {
668 ASSIGN_OR_RETURN(auto t0_data, rom()->ReadWord(tpos));
669 gfx::TileInfo t0 = gfx::GetTilesInfo(t0_data);
670 tpos += 2;
671 ASSIGN_OR_RETURN(auto t1_data, rom()->ReadWord(tpos));
672 gfx::TileInfo t1 = gfx::GetTilesInfo(t1_data);
673 tpos += 2;
674 ASSIGN_OR_RETURN(auto t2_data, rom()->ReadWord(tpos));
675 gfx::TileInfo t2 = gfx::GetTilesInfo(t2_data);
676 tpos += 2;
677 ASSIGN_OR_RETURN(auto t3_data, rom()->ReadWord(tpos));
678 gfx::TileInfo t3 = gfx::GetTilesInfo(t3_data);
679 tpos += 2;
680 tiles16_.emplace_back(t0, t1, t2, t3);
681 }
682 return absl::OkStatus();
683}
684
685void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
686 OverworldBlockset& world) {
687 int position_x1 = (x * 2) + (sx * 32);
688 int position_y1 = (y * 2) + (sy * 32);
689 int position_x2 = (x * 2) + 1 + (sx * 32);
690 int position_y2 = (y * 2) + 1 + (sy * 32);
691 world[position_x1][position_y1] = tiles32_unique_[tpos].tile0_;
692 world[position_x2][position_y1] = tiles32_unique_[tpos].tile1_;
693 world[position_x1][position_y2] = tiles32_unique_[tpos].tile2_;
694 world[position_x2][position_y2] = tiles32_unique_[tpos].tile3_;
695}
696
698 switch (world_type) {
699 case 0:
700 return map_tiles_.light_world;
701 case 1:
702 return map_tiles_.dark_world;
703 default:
705 }
706}
707
708void Overworld::FillBlankMapTiles(int map_index) {
709 int world_type = 0;
710 if (map_index >= kDarkWorldMapIdStart &&
711 map_index < kSpecialWorldMapIdStart) {
712 world_type = 1;
713 } else if (map_index >= kSpecialWorldMapIdStart) {
714 world_type = 2;
715 }
716
717 int local_index = map_index % 64;
718 int sx = local_index % 8;
719 int sy = local_index / 8;
720
721 auto& world = SelectWorldBlockset(world_type);
722 // Fill the 32x32 tile16 region for this map with tile 0
723 for (int y = 0; y < 32; ++y) {
724 for (int x = 0; x < 32; ++x) {
725 world[(sx * 32) + x][(sy * 32) + y] = 0;
726 }
727 }
728}
729
730void Overworld::OrganizeMapTiles(std::vector<uint8_t>& bytes,
731 std::vector<uint8_t>& bytes2, int i, int sx,
732 int sy, int& ttpos) {
733 for (int y = 0; y < 16; y++) {
734 for (int x = 0; x < 16; x++) {
735 auto tidD = (uint16_t)((bytes2[ttpos] << 8) + bytes[ttpos]);
736 if (int tpos = tidD; tpos < tiles32_unique_.size()) {
737 if (i < kDarkWorldMapIdStart) {
738 AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.light_world);
739 } else if (i < kSpecialWorldMapIdStart && i >= kDarkWorldMapIdStart) {
740 AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.dark_world);
741 } else {
742 AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.special_world);
743 }
744 }
745 ttpos += 1;
746 }
747 }
748}
749
751 const auto get_ow_map_gfx_ptr = [this](int index, uint32_t map_ptr) {
752 int p = (rom()->data()[map_ptr + 2 + (3 * index)] << 16) +
753 (rom()->data()[map_ptr + 1 + (3 * index)] << 8) +
754 (rom()->data()[map_ptr + (3 * index)]);
755 return SnesToPc(p);
756 };
757
758 constexpr uint32_t kBaseLowest = 0x0FFFFF;
759 constexpr uint32_t kBaseHighest = 0x0F8000;
760
761 uint32_t lowest = kBaseLowest;
762 uint32_t highest = kBaseHighest;
763 int sx = 0;
764 int sy = 0;
765 int c = 0;
766 // Tail maps (0xA0-0xBF) require BOTH:
767 // 1. Feature flag enabled in settings
768 // 2. TailMapExpansion.asm patch applied to ROM (marker at 0x1423FF)
769 const bool allow_special_tail =
772
773 for (int i = 0; i < kNumOverworldMaps; i++) {
774 // Guard: skip building tail special maps unless expansion is available
775 if (!allow_special_tail &&
776 i >= kSpecialWorldMapIdStart + 0x20) { // 0xA0-0xBF
778 sx++;
779 if (sx >= 8) {
780 sy++;
781 sx = 0;
782 }
783 c++;
784 if (c >= 64) {
785 sx = 0;
786 sy = 0;
787 c = 0;
788 }
789 continue;
790 }
791
792 auto p1 = get_ow_map_gfx_ptr(
793 i, version_constants().kCompressedAllMap32PointersHigh);
794 auto p2 = get_ow_map_gfx_ptr(
795 i, version_constants().kCompressedAllMap32PointersLow);
796
797 int ttpos = 0;
798
799 bool pointers_valid =
800 (p1 > 0 && p2 > 0 && p1 < rom()->size() && p2 < rom()->size());
801 if (!pointers_valid) {
802 // Missing/invalid pointers -> use blank map tiles to avoid crashes
804 sx++;
805 if (sx >= 8) {
806 sy++;
807 sx = 0;
808 }
809 c++;
810 if (c >= 64) {
811 sx = 0;
812 sy = 0;
813 c = 0;
814 }
815 continue;
816 }
817
818 if (p1 >= highest)
819 highest = p1;
820 if (p2 >= highest)
821 highest = p2;
822
823 if (p1 <= lowest && p1 > kBaseHighest)
824 lowest = p1;
825 if (p2 <= lowest && p2 > kBaseHighest)
826 lowest = p2;
827
828 int size1, size2;
829 size_t max_size_p2 = rom()->size() - p2;
830 auto bytes =
831 gfx::HyruleMagicDecompress(rom()->data() + p2, &size1, 1, max_size_p2);
832 size_t max_size_p1 = rom()->size() - p1;
833 auto bytes2 =
834 gfx::HyruleMagicDecompress(rom()->data() + p1, &size2, 1, max_size_p1);
835
836 // If decompression fails, use blank tiles to keep map index usable
837 if (bytes.empty() || bytes2.empty()) {
839 } else {
840 OrganizeMapTiles(bytes, bytes2, i, sx, sy, ttpos);
841 }
842
843 sx++;
844 if (sx >= 8) {
845 sy++;
846 sx = 0;
847 }
848
849 c++;
850 if (c >= 64) {
851 sx = 0;
852 sy = 0;
853 c = 0;
854 }
855 }
856
857 return absl::OkStatus();
858}
859
861 auto size = tiles16_.size();
862
863 // Performance optimization: Only build essential maps initially
864 // Essential maps are the first few maps of each world that are commonly
865 // accessed
866 constexpr int kLightWorldEssential = yaze::zelda3::kEssentialMapsPerWorld;
867 constexpr int kDarkWorldEssential =
869 constexpr int kSpecialWorldEssential =
871 std::vector<int> essential_map_ids;
872 essential_map_ids.reserve(yaze::zelda3::kEssentialMapsPerWorld * 3);
873
875 "Building essential maps only (first %d maps per world) for faster "
876 "loading",
878
879#ifdef __EMSCRIPTEN__
880 // WASM: Use sequential loading to avoid spawning excessive Web Workers
881 // and blocking the main thread. std::async creates new pthreads which
882 // become Web Workers, and future.wait() blocks the main thread which
883 // is dangerous in browsers.
884 for (int i = 0; i < kNumOverworldMaps; ++i) {
885 bool is_essential = false;
886
887 if (i < kLightWorldEssential) {
888 is_essential = true;
889 } else if (i >= kDarkWorldMapIdStart && i < kDarkWorldEssential) {
890 is_essential = true;
891 } else if (i >= kSpecialWorldMapIdStart && i < kSpecialWorldEssential) {
892 is_essential = true;
893 }
894
895 if (is_essential) {
896 essential_map_ids.push_back(i);
897 int world_type = 0;
899 world_type = 1;
900 } else if (i >= kSpecialWorldMapIdStart) {
901 world_type = 2;
902 }
903
904 // CRITICAL: Set game_state_ BEFORE LoadAreaGraphics() because
905 // LoadSpritesBlocksets() uses game_state_ to determine static_graphics_[12-15]
906 overworld_maps_[i].set_game_state(game_state_);
907
908 // Apply large map child handling BEFORE computing hash
909 // Must match LoadAreaInfo() logic exactly for SW maps
910 auto* map = &overworld_maps_[i];
911 if (map->is_large_map() &&
913 if (map->parent() != i && !map->is_initialized()) {
914 if (i >= kSpecialWorldMapIdStart && i <= 0x8A && i != 0x88) {
915 // Zora's Domain children - also set sprite_graphics
916 map->set_sprite_graphics(0, 0x0E);
917 map->set_sprite_graphics(1, 0x0E);
918 map->set_sprite_graphics(2, 0x0E);
919 map->set_area_graphics(
921 (map->parent() - kSpecialWorldMapIdStart)]);
922 map->set_area_palette((*rom_)[kOverworldSpecialPalGroup + 1]);
923 } else if (i == 0x88) {
924 map->set_area_graphics(0x51);
925 map->set_area_palette(0x00);
926 } else if (i < kSpecialWorldMapIdStart) {
927 // LW/DW large map child - use parent's graphics
928 map->set_area_graphics((*rom_)[kAreaGfxIdPtr + map->parent()]);
929 map->set_area_palette(
930 (*rom_)[kOverworldMapPaletteIds + map->parent()]);
931 }
932 // Note: Other SW maps (>0x8A) keep their LoadAreaInfo values
933 }
934 }
935
936 // Reuse cached tilesets to reduce load time on WASM
937 overworld_maps_[i].LoadAreaGraphics();
938 uint64_t config_hash = ComputeGraphicsConfigHash(i);
939 const std::vector<uint8_t>* cached_tileset =
940 GetCachedTileset(config_hash);
941 RETURN_IF_ERROR(overworld_maps_[i].BuildMapWithCache(
942 size, game_state_, world_type, tiles16_, GetMapTiles(world_type),
943 cached_tileset));
944 if (!cached_tileset) {
946 }
947 built_map_lru_.push_front(i);
948 } else {
949 overworld_maps_[i].SetNotBuilt();
950 }
951 }
952#else
953 // Native: Use parallel loading with std::async for faster performance
954 std::vector<std::future<absl::Status>> futures;
955
956 // Build essential maps only
957 for (int i = 0; i < kNumOverworldMaps; ++i) {
958 bool is_essential = false;
959
960 // Check if this is an essential map
961 if (i < kLightWorldEssential) {
962 is_essential = true;
963 } else if (i >= kDarkWorldMapIdStart && i < kDarkWorldEssential) {
964 is_essential = true;
965 } else if (i >= kSpecialWorldMapIdStart && i < kSpecialWorldEssential) {
966 is_essential = true;
967 }
968
969 if (is_essential) {
970 int world_type = 0;
972 world_type = 1;
973 } else if (i >= kSpecialWorldMapIdStart) {
974 world_type = 2;
975 }
976
977 auto task_function = [this, i, size, world_type]() {
978 return overworld_maps_[i].BuildMap(size, game_state_, world_type,
979 tiles16_, GetMapTiles(world_type));
980 };
981 futures.emplace_back(std::async(std::launch::async, task_function));
982 } else {
983 // Mark non-essential maps as not built yet
984 overworld_maps_[i].SetNotBuilt();
985 }
986 }
987
988 // Wait for essential maps to complete
989 for (auto& future : futures) {
990 future.wait();
991 RETURN_IF_ERROR(future.get());
992 }
993
994 built_map_lru_.clear();
995 for (int map_index : essential_map_ids) {
996 built_map_lru_.push_front(map_index);
997 }
998#endif
999
1000 util::logf("Essential maps built. Remaining maps will be built on-demand.");
1001 return absl::OkStatus();
1002}
1003
1004absl::Status Overworld::EnsureMapBuilt(int map_index) {
1005 if (map_index < 0 || map_index >= kNumOverworldMaps) {
1006 return absl::InvalidArgumentError("Invalid map index");
1007 }
1008
1009 // Tail maps (0xA0-0xBF) require BOTH:
1010 // 1. Feature flag enabled in settings
1011 // 2. TailMapExpansion.asm patch applied to ROM (marker at 0x1423FF)
1012 const bool allow_special_tail =
1015 if (!allow_special_tail &&
1016 map_index >= kSpecialWorldMapIdStart + 0x20) { // 0xA0-0xBF
1017 // Do not attempt to build disabled special-tail maps; keep them blank-safe.
1018 // This prevents pointer table corruption from attempting to access
1019 // non-existent entries beyond vanilla's 160-entry limit.
1020 FillBlankMapTiles(map_index);
1021 return absl::OkStatus();
1022 }
1023
1024 // Check if map is already built
1025 if (overworld_maps_[map_index].is_built()) {
1026 // Move to front of LRU (most recently used)
1027 auto it =
1028 std::find(built_map_lru_.begin(), built_map_lru_.end(), map_index);
1029 if (it != built_map_lru_.end()) {
1030 built_map_lru_.erase(it);
1031 }
1032 built_map_lru_.push_front(map_index);
1033 return absl::OkStatus();
1034 }
1035
1036 // Evict oldest maps if cache is full (LRU eviction)
1037 while (static_cast<int>(built_map_lru_.size()) >= kMaxBuiltMaps) {
1038 int oldest_map = built_map_lru_.back();
1039 built_map_lru_.pop_back();
1040 // Invalidate graphics cache for evicted map to prevent stale tileset refs
1041 InvalidateMapCache(oldest_map);
1042 // Destroy the oldest map to free memory
1043 overworld_maps_[oldest_map].Destroy();
1044 }
1045
1046 // Build the map on-demand
1047 auto size = tiles16_.size();
1048 int world_type = 0;
1049 if (map_index >= kDarkWorldMapIdStart &&
1050 map_index < kSpecialWorldMapIdStart) {
1051 world_type = 1;
1052 } else if (map_index >= kSpecialWorldMapIdStart) {
1053 world_type = 2;
1054 }
1055
1056 // CRITICAL: Set game_state_ BEFORE LoadAreaGraphics() because
1057 // LoadSpritesBlocksets() uses game_state_ to determine static_graphics_[12-15]
1058 overworld_maps_[map_index].set_game_state(game_state_);
1059
1060 // Apply large map child handling BEFORE computing hash
1061 // This mirrors the logic in BuildMapWithCache that modifies area_graphics_
1062 // for large map children in vanilla ROMs - must happen before hash
1063 auto* map = &overworld_maps_[map_index];
1064 if (map->is_large_map() && cached_version_ == OverworldVersion::kVanilla) {
1065 if (map->parent() != map_index && !map->is_initialized()) {
1066 // Large map child in vanilla ROM - apply special graphics handling
1067 // Must match LoadAreaInfo() logic exactly for SW maps
1068 if (map_index >= kSpecialWorldMapIdStart && map_index <= 0x8A &&
1069 map_index != 0x88) {
1070 // Zora's Domain children - also set sprite_graphics
1071 map->set_sprite_graphics(0, 0x0E);
1072 map->set_sprite_graphics(1, 0x0E);
1073 map->set_sprite_graphics(2, 0x0E);
1074 map->set_area_graphics(
1076 (map->parent() - kSpecialWorldMapIdStart)]);
1077 map->set_area_palette((*rom_)[kOverworldSpecialPalGroup + 1]);
1078 } else if (map_index == 0x88) {
1079 map->set_area_graphics(0x51);
1080 map->set_area_palette(0x00);
1081 } else if (map_index < kSpecialWorldMapIdStart) {
1082 // LW/DW large map child - use parent's graphics
1083 map->set_area_graphics((*rom_)[kAreaGfxIdPtr + map->parent()]);
1084 map->set_area_palette((*rom_)[kOverworldMapPaletteIds + map->parent()]);
1085 }
1086 // Note: Other SW maps (>0x8A) keep their LoadAreaInfo values
1087 }
1088 }
1089
1090 // Prepare graphics config to check cache (must call LoadAreaGraphics first)
1091 overworld_maps_[map_index].LoadAreaGraphics();
1092 uint64_t config_hash = ComputeGraphicsConfigHash(map_index);
1093
1094 // Try to use cached tileset for faster build
1095 const std::vector<uint8_t>* cached_tileset = GetCachedTileset(config_hash);
1096
1097 auto status = overworld_maps_[map_index].BuildMapWithCache(
1098 size, game_state_, world_type, tiles16_, GetMapTiles(world_type),
1099 cached_tileset);
1100
1101 if (status.ok()) {
1102 // Cache the tileset if we didn't use cached data
1103 if (!cached_tileset) {
1104 CacheTileset(config_hash, overworld_maps_[map_index].current_graphics());
1105 }
1106 // Add to front of LRU cache
1107 built_map_lru_.push_front(map_index);
1108 }
1109 return status;
1110}
1111
1113 for (int i = 0; i < kNumTileTypes; ++i) {
1114 all_tiles_types_[i] =
1115 rom()->data()[version_constants().kOverworldTilesType + i];
1116 }
1117}
1118
1120 // Compute a comprehensive hash that distinguishes tileset configurations
1121 // across different worlds (LW/DW/SW) and map types
1122 const auto* map = &overworld_maps_[map_index];
1123 uint64_t hash = 0;
1124
1125 // CRITICAL: Include explicit world type to absolutely prevent cross-world sharing
1126 // LW=0, DW=1, SW=2 - this is the strongest disambiguation
1127 int world_type = 0;
1128 if (map_index >= kDarkWorldMapIdStart &&
1129 map_index < kSpecialWorldMapIdStart) {
1130 world_type = 1;
1131 } else if (map_index >= kSpecialWorldMapIdStart) {
1132 world_type = 2;
1133 }
1134 hash ^= static_cast<uint64_t>(world_type) << 62;
1135 hash *= 0x517cc1b727220a95ULL;
1136
1137 // Hash the first 12 static graphics IDs (main blocksets)
1138 // Note: static_graphics_[12-15] are sprite sheets loaded using game_state_
1139 // which may be stale at hash time, so we handle them separately below
1140 for (int i = 0; i < 12; ++i) {
1141 hash ^= static_cast<uint64_t>(map->static_graphics(i)) << ((i % 8) * 8);
1142 hash *= 0x517cc1b727220a95ULL; // FNV-like mixing
1143 }
1144
1145 // Include game_state_ to distinguish sprite sheet configurations
1146 // static_graphics_[12-15] are loaded using sprite_graphics_[game_state_]
1147 // which varies by game state (Beginning, Zelda Rescued, Master Sword, Agahnim)
1148 hash ^= static_cast<uint64_t>(game_state_) << 60;
1149 hash *= 0x517cc1b727220a95ULL;
1150
1151 // Include ALL sprite_graphics values since SW maps (especially Zora's Domain)
1152 // have different sprite graphics (0x0E) than LW/DW maps
1153 for (int i = 0; i < 3; ++i) {
1154 hash ^= static_cast<uint64_t>(map->sprite_graphics(i)) << (52 + i * 4);
1155 hash *= 0x517cc1b727220a95ULL;
1156 }
1157
1158 // Include area_graphics for complete config
1159 hash ^= static_cast<uint64_t>(map->area_graphics()) << 48;
1160 hash *= 0x517cc1b727220a95ULL;
1161
1162 // Include main_gfx_id to distinguish between worlds
1163 // LW=0x20, DW=0x21, SW=0x20/0x24 - prevents cache collisions between LW/SW
1164 hash ^= static_cast<uint64_t>(map->main_gfx_id()) << 56;
1165 hash *= 0x517cc1b727220a95ULL;
1166
1167 // Include parent ID to prevent cache collisions between sibling maps
1168 hash ^= static_cast<uint64_t>(map->parent()) << 40;
1169 hash *= 0x517cc1b727220a95ULL;
1170
1171 // CRITICAL: Include map index for Special World disambiguation
1172 // SW maps have many unique hardcoded configurations based on index:
1173 // 0x80 (Master Sword), 0x88/0x93 (Triforce), 0x94, 0x95, 0x96, 0x9C
1174 // These must not share cached tilesets even if other properties match
1175 hash ^= static_cast<uint64_t>(map_index) << 8;
1176 hash *= 0x517cc1b727220a95ULL;
1177
1178 // Include main_palette to distinguish world palettes (LW=0, DW=1, DM=2/3, etc.)
1179 hash ^= static_cast<uint64_t>(map->main_palette()) << 24;
1180 hash *= 0x517cc1b727220a95ULL;
1181
1182 // Include animated_gfx to distinguish between Death Mountain (0x59) and normal (0x5B)
1183 hash ^= static_cast<uint64_t>(map->animated_gfx()) << 16;
1184 hash *= 0x517cc1b727220a95ULL;
1185
1186 // Include area_palette for final disambiguation
1187 hash ^= static_cast<uint64_t>(map->area_palette()) << 32;
1188 hash *= 0x517cc1b727220a95ULL;
1189
1190 // Include subscreen overlay for visual consistency (fog, curtains, sky, lava)
1191 // Different overlays can affect which tiles are visible/rendered
1192 hash ^= static_cast<uint64_t>(map->subscreen_overlay());
1193 hash *= 0x517cc1b727220a95ULL;
1194
1195 return hash;
1196}
1197
1198const std::vector<uint8_t>* Overworld::GetCachedTileset(uint64_t config_hash) {
1199 auto it = gfx_config_cache_.find(config_hash);
1200 if (it != gfx_config_cache_.end()) {
1201 it->second.reference_count++;
1202 return &it->second.current_gfx;
1203 }
1204 return nullptr;
1205}
1206
1207void Overworld::CacheTileset(uint64_t config_hash,
1208 const std::vector<uint8_t>& tileset) {
1209 // Limit cache size by evicting least-used entries
1210 while (gfx_config_cache_.size() >= kMaxCachedConfigs) {
1211 // Find entry with lowest reference count
1212 auto min_it = gfx_config_cache_.begin();
1213 for (auto it = gfx_config_cache_.begin(); it != gfx_config_cache_.end();
1214 ++it) {
1215 if (it->second.reference_count < min_it->second.reference_count) {
1216 min_it = it;
1217 }
1218 }
1219 gfx_config_cache_.erase(min_it);
1220 }
1221
1222 // Cache the tileset
1223 gfx_config_cache_[config_hash] = {tileset, 1};
1224}
1225
1227 if (map_index < 0 || map_index >= kNumOverworldMaps) {
1228 return;
1229 }
1230
1231 // Compute the hash for this map's graphics configuration and remove it
1232 uint64_t config_hash = ComputeGraphicsConfigHash(map_index);
1233 gfx_config_cache_.erase(config_hash);
1234
1235 // Also mark the map as needing rebuild
1236 if (static_cast<size_t>(map_index) < overworld_maps_.size()) {
1237 overworld_maps_[map_index].SetNotBuilt();
1238 }
1239}
1240
1242 if (map_index < 0 || map_index >= kNumOverworldMaps) {
1243 return;
1244 }
1245
1246 auto* map = mutable_overworld_map(map_index);
1247 if (!map)
1248 return;
1249
1250 // Get parent and determine all sibling maps
1251 int parent_id = map->parent();
1252 std::vector<int> siblings;
1253
1255
1256 if (use_v3_sizes) {
1257 // v3: Use area_size enum
1258 switch (map->area_size()) {
1260 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
1261 break;
1263 siblings = {parent_id, parent_id + 1};
1264 break;
1266 siblings = {parent_id, parent_id + 8};
1267 break;
1268 default:
1269 siblings = {map_index}; // Small area - just this map
1270 break;
1271 }
1272 } else {
1273 // Vanilla/v1/v2: Use large_map flag
1274 if (map->is_large_map()) {
1275 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
1276 } else {
1277 siblings = {map_index}; // Small area - just this map
1278 }
1279 }
1280
1281 // Invalidate cache for all siblings
1282 for (int sibling : siblings) {
1283 if (sibling >= 0 && sibling < kNumOverworldMaps) {
1284 InvalidateMapCache(sibling);
1285 }
1286 }
1287}
1288
1290 // Determine sprite table locations based on actual ASM version in ROM
1291
1292#ifdef __EMSCRIPTEN__
1293 // WASM: Sequential loading to avoid Web Worker explosion
1300 } else {
1304 }
1305#else
1306 // Native: Parallel loading for performance
1307 std::vector<std::future<absl::Status>> futures;
1308
1310 // v3: Use expanded sprite tables
1311 futures.emplace_back(std::async(std::launch::async, [this]() {
1313 }));
1314 futures.emplace_back(std::async(std::launch::async, [this]() {
1316 }));
1317 futures.emplace_back(std::async(std::launch::async, [this]() {
1319 }));
1320 } else {
1321 // Vanilla/v2: Use original sprite tables
1322 futures.emplace_back(std::async(std::launch::async, [this]() {
1324 }));
1325 futures.emplace_back(std::async(std::launch::async, [this]() {
1327 }));
1328 futures.emplace_back(std::async(std::launch::async, [this]() {
1330 }));
1331 }
1332
1333 for (auto& future : futures) {
1334 future.wait();
1335 RETURN_IF_ERROR(future.get());
1336 }
1337#endif
1338 return absl::OkStatus();
1339}
1340
1341absl::Status Overworld::LoadSpritesFromMap(int sprites_per_gamestate_ptr,
1342 int num_maps_per_gamestate,
1343 int game_state) {
1344 for (int i = 0; i < num_maps_per_gamestate; i++) {
1345 if (map_parent_[i] != i)
1346 continue;
1347
1348 int current_spr_ptr = sprites_per_gamestate_ptr + (i * 2);
1349 ASSIGN_OR_RETURN(auto word_addr, rom()->ReadWord(current_spr_ptr));
1350 int sprite_address = SnesToPc((0x09 << 0x10) | word_addr);
1351 while (true) {
1352 ASSIGN_OR_RETURN(uint8_t b1, rom()->ReadByte(sprite_address));
1353 ASSIGN_OR_RETURN(uint8_t b2, rom()->ReadByte(sprite_address + 1));
1354 ASSIGN_OR_RETURN(uint8_t b3, rom()->ReadByte(sprite_address + 2));
1355 if (b1 == 0xFF)
1356 break;
1357
1358 int editor_map_index = i;
1359 if (game_state != 0) {
1360 if (editor_map_index >= 128)
1361 editor_map_index -= 128;
1362 else if (editor_map_index >= 64)
1363 editor_map_index -= 64;
1364 }
1365 int mapY = (editor_map_index / 8);
1366 int mapX = (editor_map_index % 8);
1367
1368 int realX = ((b2 & 0x3F) * 16) + mapX * 512;
1369 int realY = ((b1 & 0x3F) * 16) + mapY * 512;
1370 all_sprites_[game_state].emplace_back(
1371 *overworld_maps_[i].mutable_current_graphics(), (uint8_t)i, b3,
1372 (uint8_t)(b2 & 0x3F), (uint8_t)(b1 & 0x3F), realX, realY);
1373 all_sprites_[game_state].back().Draw();
1374
1375 sprite_address += 3;
1376 }
1377 }
1378
1379 return absl::OkStatus();
1380}
1381
1406
1408 util::logf("Saving Overworld Maps");
1409
1410 if (tiles32_list_.size() < NumberOfMap32) {
1412 }
1413
1414 // Initialize map pointers
1415 std::fill(map_pointers1_id.begin(), map_pointers1_id.end(), -1);
1416 std::fill(map_pointers2_id.begin(), map_pointers2_id.end(), -1);
1417
1418 // Compress and save each map
1420 for (int i = 0; i < kNumOverworldMaps; i++) {
1421 std::vector<uint8_t> single_map_1(512);
1422 std::vector<uint8_t> single_map_2(512);
1423
1424 // Copy tiles32 data to single_map_1 and single_map_2
1425 int npos = 0;
1426 for (int y = 0; y < 16; y++) {
1427 for (int x = 0; x < 16; x++) {
1428 auto packed = tiles32_list_[npos + (i * 256)];
1429 single_map_1[npos] = packed & 0xFF; // Lower 8 bits
1430 single_map_2[npos] = (packed >> 8) & 0xFF; // Next 8 bits
1431 npos++;
1432 }
1433 }
1434
1435 int size_a, size_b;
1436 // Compress single_map_1 and single_map_2
1437 auto a = gfx::HyruleMagicCompress(single_map_1.data(), 256, &size_a, 1);
1438 auto b = gfx::HyruleMagicCompress(single_map_2.data(), 256, &size_b, 1);
1439 if (a.empty() || b.empty()) {
1440 return absl::AbortedError("Error compressing map gfx.");
1441 }
1442
1443 // Save compressed data and pointers
1444 map_data_p1[i] = std::vector<uint8_t>(size_a);
1445 map_data_p2[i] = std::vector<uint8_t>(size_b);
1446
1447 if ((pos + size_a) >= 0x5FE70 && (pos + size_a) <= 0x60000) {
1448 pos = 0x60000;
1449 }
1450
1451 if ((pos + size_a) >= 0x6411F && (pos + size_a) <= 0x70000) {
1452 util::logf("Pos set to overflow region for map %s at %s",
1453 std::to_string(i), util::HexLong(pos));
1454 pos = kOverworldMapDataOverflow; // 0x0F8780;
1455 }
1456
1457 const auto compare_array = [](const std::vector<uint8_t>& array1,
1458 const std::vector<uint8_t>& array2) -> bool {
1459 if (array1.size() != array2.size()) {
1460 return false;
1461 }
1462
1463 for (size_t i = 0; i < array1.size(); i++) {
1464 if (array1[i] != array2[i]) {
1465 return false;
1466 }
1467 }
1468
1469 return true;
1470 };
1471
1472 for (int j = 0; j < i; j++) {
1473 if (compare_array(a, map_data_p1[j])) {
1474 // Reuse pointer id j for P1 (a)
1475 map_pointers1_id[i] = j;
1476 }
1477
1478 if (compare_array(b, map_data_p2[j])) {
1479 map_pointers2_id[i] = j;
1480 // Reuse pointer id j for P2 (b)
1481 }
1482 }
1483
1484 if (map_pointers1_id[i] == -1) {
1485 // Save compressed data and pointer for map1
1486 std::copy(a.begin(), a.end(), map_data_p1[i].begin());
1487 int snes_pos = PcToSnes(pos);
1488 map_pointers1[i] = snes_pos;
1489 util::logf("Saving map pointers1 and compressed data for map %s at %s",
1490 util::HexByte(i), util::HexLong(snes_pos));
1491 RETURN_IF_ERROR(rom()->WriteLong(
1492 version_constants().kCompressedAllMap32PointersLow + (3 * i),
1493 snes_pos));
1494 RETURN_IF_ERROR(rom()->WriteVector(pos, a));
1495 pos += size_a;
1496 } else {
1497 // Save pointer for map1
1498 int snes_pos = map_pointers1[map_pointers1_id[i]];
1499 util::logf("Saving map pointers1 for map %s at %s", util::HexByte(i),
1500 util::HexLong(snes_pos));
1501 RETURN_IF_ERROR(rom()->WriteLong(
1502 version_constants().kCompressedAllMap32PointersLow + (3 * i),
1503 snes_pos));
1504 }
1505
1506 if ((pos + b.size()) >= 0x5FE70 && (pos + b.size()) <= 0x60000) {
1507 pos = 0x60000;
1508 }
1509
1510 if ((pos + b.size()) >= 0x6411F && (pos + b.size()) <= 0x70000) {
1511 util::logf("Pos set to overflow region for map %s at %s",
1512 util::HexByte(i), util::HexLong(pos));
1514 }
1515
1516 if (map_pointers2_id[i] == -1) {
1517 // Save compressed data and pointer for map2
1518 std::copy(b.begin(), b.end(), map_data_p2[i].begin());
1519 int snes_pos = PcToSnes(pos);
1520 map_pointers2[i] = snes_pos;
1521 util::logf("Saving map pointers2 and compressed data for map %s at %s",
1522 util::HexByte(i), util::HexLong(snes_pos));
1523 RETURN_IF_ERROR(rom()->WriteLong(
1524 version_constants().kCompressedAllMap32PointersHigh + (3 * i),
1525 snes_pos));
1526 RETURN_IF_ERROR(rom()->WriteVector(pos, b));
1527 pos += size_b;
1528 } else {
1529 // Save pointer for map2
1530 int snes_pos = map_pointers2[map_pointers2_id[i]];
1531 util::logf("Saving map pointers2 for map %s at %s", util::HexByte(i),
1532 util::HexLong(snes_pos));
1533 RETURN_IF_ERROR(rom()->WriteLong(
1534 version_constants().kCompressedAllMap32PointersHigh + (3 * i),
1535 snes_pos));
1536 }
1537 }
1538
1539 // Check if too many maps data
1541 util::logf("Too many maps data %s", util::HexLong(pos));
1542 return absl::AbortedError("Too many maps data " + std::to_string(pos));
1543 }
1544
1546 return absl::OkStatus();
1547}
1548
1549std::vector<std::pair<uint32_t, uint32_t>> Overworld::GetProjectedWriteRanges()
1550 const {
1551 std::vector<std::pair<uint32_t, uint32_t>> ranges;
1552
1553 // 1) Map32 Tiles (4 quadrants)
1554 uint32_t map32address[4] = {
1557 int map32_len = 0x33F0; // Vanilla length
1558
1559 // Mirror AssembleMap32Tiles() logic: if expanded tile32 is present, the three
1560 // non-TL quadrants are relocated and the logical length changes.
1561 if (expanded_tile32_) {
1562 map32address[1] = GetMap32TileTRExpanded();
1563 map32address[2] = GetMap32TileBLExpanded();
1564 map32address[3] = GetMap32TileBRExpanded();
1565 map32_len = kMap32TileCountExpanded;
1566 }
1567
1568 for (int i = 0; i < 4; ++i) {
1569 if (map32address[i] > 0) { // Valid address
1570 ranges.emplace_back(map32address[i], map32address[i] + map32_len);
1571 }
1572 }
1573
1574 // 2) Map16 Tiles
1575 int map16_addr = kMap16Tiles;
1576 int map16_len = kNumTile16Individual * 8; // Vanilla: 4096 * 8 = 32KB
1577 if (expanded_tile16_) {
1578 map16_addr = GetMap16TilesExpanded();
1579 map16_len = NumberOfMap16Ex * 8;
1580 }
1581 ranges.emplace_back(map16_addr, map16_addr + map16_len);
1582
1583 // 3) Overworld map compressed pointer tables + data regions.
1584 // SaveOverworldMaps() writes two 3-byte pointer tables for all maps, plus the
1585 // compressed data itself. This is a conservative superset of the possible
1586 // write footprint.
1587 constexpr uint32_t kCompressedBank0bEnd = 0x5FE70;
1588 constexpr uint32_t kCompressedBank0cStart = 0x60000;
1589 constexpr uint32_t kCompressedBank0cEnd = 0x6411F;
1590
1591 const uint32_t ptr_low = version_constants().kCompressedAllMap32PointersLow;
1592 const uint32_t ptr_high = version_constants().kCompressedAllMap32PointersHigh;
1593 ranges.emplace_back(ptr_low, ptr_low + (3 * kNumOverworldMaps));
1594 ranges.emplace_back(ptr_high, ptr_high + (3 * kNumOverworldMaps));
1595
1596 ranges.emplace_back(kOverworldCompressedMapPos,
1597 kCompressedBank0bEnd); // Bank 0B
1598 ranges.emplace_back(kCompressedBank0cStart, kCompressedBank0cEnd); // Bank 0C
1599 ranges.emplace_back(kOverworldMapDataOverflow,
1600 kOverworldCompressedOverflowPos); // Overflow Area
1601
1602 return ranges;
1603}
1604
1606 util::logf("Saving Large Maps");
1607
1608 // Check if this is a v3+ ROM to use expanded transition system
1609 bool use_expanded_transitions =
1611
1612 if (use_expanded_transitions) {
1613 // Use new v3+ complex transition system with neighbor awareness
1614 return SaveLargeMapsExpanded();
1615 }
1616
1617 // Original vanilla/v2 logic preserved
1618 std::vector<uint8_t> checked_map;
1619
1620 for (int i = 0; i < kNumMapsPerWorld; ++i) {
1621 int y_pos = i / 8;
1622 int x_pos = i % 8;
1623 int parent_y_pos = overworld_maps_[i].parent() / 8;
1624 int parent_x_pos = overworld_maps_[i].parent() % 8;
1625
1626 // Always write the map parent since it should not matter
1627 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + i,
1628 overworld_maps_[i].parent()))
1629
1630 if (std::find(checked_map.begin(), checked_map.end(), i) !=
1631 checked_map.end()) {
1632 continue;
1633 }
1634
1635 // If it's large then save parent pos *
1636 // 0x200 otherwise pos * 0x200
1637 if (overworld_maps_[i].is_large_map()) {
1638 const uint8_t large_map_offsets[] = {0, 1, 8, 9};
1639 for (const auto& offset : large_map_offsets) {
1640 // Check 1
1641 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i + offset, 0x20));
1642 // Check 2
1644 rom()->WriteByte(kOverworldMapSizeHighByte + i + offset, 0x03));
1645 // Check 3
1647 rom()->WriteByte(kOverworldScreenSize + i + offset, 0x00));
1649 rom()->WriteByte(kOverworldScreenSize + i + offset + 64, 0x00));
1650 // Check 4
1651 RETURN_IF_ERROR(rom()->WriteByte(
1652 kOverworldScreenSizeForLoading + i + offset, 0x04));
1653 RETURN_IF_ERROR(rom()->WriteByte(
1655 0x04));
1657 offset + kSpecialWorldMapIdStart,
1658 0x04));
1659 }
1660
1661 // Check 5 and 6 - transition targets
1663 rom()->WriteShort(kTransitionTargetNorth + (i * 2),
1664 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
1666 rom()->WriteShort(kTransitionTargetWest + (i * 2),
1667 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
1668
1670 rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 2,
1671 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
1673 rom()->WriteShort(kTransitionTargetWest + (i * 2) + 2,
1674 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
1675
1677 rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 16,
1678 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
1680 rom()->WriteShort(kTransitionTargetWest + (i * 2) + 16,
1681 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
1682
1684 rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 18,
1685 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
1687 rom()->WriteShort(kTransitionTargetWest + (i * 2) + 18,
1688 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
1689
1690 // Check 7 and 8 - transition positions
1691 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionX + (i * 2),
1692 (parent_x_pos * 0x200)));
1693 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionY + (i * 2),
1694 (parent_y_pos * 0x200)));
1695
1697 rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 02,
1698 (parent_x_pos * 0x200)));
1700 rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 02,
1701 (parent_y_pos * 0x200)));
1702
1704 rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 16,
1705 (parent_x_pos * 0x200)));
1707 rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 16,
1708 (parent_y_pos * 0x200)));
1709
1711 rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 18,
1712 (parent_x_pos * 0x200)));
1714 rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 18,
1715 (parent_y_pos * 0x200)));
1716
1717 // Check 9 - simple vanilla large area transitions
1718 RETURN_IF_ERROR(rom()->WriteShort(
1719 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 00, 0x0060));
1720 RETURN_IF_ERROR(rom()->WriteShort(
1721 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 02, 0x0060));
1722
1723 // If parentX == 0 then lower submaps == 0x0060 too
1724 if (parent_x_pos == 0) {
1725 RETURN_IF_ERROR(rom()->WriteShort(
1726 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 16, 0x0060));
1727 RETURN_IF_ERROR(rom()->WriteShort(
1728 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 18, 0x0060));
1729 } else {
1730 // Otherwise lower submaps == 0x1060
1731 RETURN_IF_ERROR(rom()->WriteShort(
1732 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 16, 0x1060));
1733 RETURN_IF_ERROR(rom()->WriteShort(
1734 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 18, 0x1060));
1735
1736 // If the area to the left is a large map, we don't need to add an
1737 // offset to it. otherwise leave it the same. Just to make sure where
1738 // don't try to read outside of the array.
1739 if ((i - 1) >= 0) {
1740 // If the area to the left is a large area.
1741 if (overworld_maps_[i - 1].is_large_map()) {
1742 // If the area to the left is the bottom right of a large area.
1743 if (overworld_maps_[i - 1].large_index() == 1) {
1744 RETURN_IF_ERROR(rom()->WriteShort(
1746 0x0060));
1747 }
1748 }
1749 }
1750 }
1751
1752 // Always 0x0080
1753 RETURN_IF_ERROR(rom()->WriteShort(
1754 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 00, 0x0080));
1755 RETURN_IF_ERROR(rom()->WriteShort(
1756 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 2, 0x0080));
1757 // Lower always 0x1080
1758 RETURN_IF_ERROR(rom()->WriteShort(
1759 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 16, 0x1080));
1760 RETURN_IF_ERROR(rom()->WriteShort(
1761 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 18, 0x1080));
1762
1763 // If the area to the right is a large map, we don't need to add an offset
1764 // to it. otherwise leave it the same. Just to make sure where don't try
1765 // to read outside of the array.
1766 if ((i + 2) < 64) {
1767 // If the area to the right is a large area.
1768 if (overworld_maps_[i + 2].is_large_map()) {
1769 // If the area to the right is the top left of a large area.
1770 if (overworld_maps_[i + 2].large_index() == 0) {
1771 RETURN_IF_ERROR(rom()->WriteShort(
1772 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 18, 0x0080));
1773 }
1774 }
1775 }
1776
1777 // Always 0x1800
1778 RETURN_IF_ERROR(rom()->WriteShort(
1779 kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x1800));
1780 RETURN_IF_ERROR(rom()->WriteShort(
1781 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 16, 0x1800));
1782 // Right side is always 0x1840
1783 RETURN_IF_ERROR(rom()->WriteShort(
1784 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 2, 0x1840));
1785 RETURN_IF_ERROR(rom()->WriteShort(
1786 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 18, 0x1840));
1787
1788 // If the area above is a large map, we don't need to add an offset to it.
1789 // otherwise leave it the same.
1790 // Just to make sure where don't try to read outside of the array.
1791 if (i - 8 >= 0) {
1792 // If the area just above us is a large area.
1793 if (overworld_maps_[i - 8].is_large_map()) {
1794 // If the area just above us is the bottom left of a large area.
1795 if (overworld_maps_[i - 8].large_index() == 2) {
1796 RETURN_IF_ERROR(rom()->WriteShort(
1797 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 02, 0x1800));
1798 }
1799 }
1800 }
1801
1802 // Always 0x2000
1803 RETURN_IF_ERROR(rom()->WriteShort(
1804 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 00, 0x2000));
1805 RETURN_IF_ERROR(rom()->WriteShort(
1806 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 16, 0x2000));
1807 // Right side always 0x2040
1808 RETURN_IF_ERROR(rom()->WriteShort(
1809 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 2, 0x2040));
1810 RETURN_IF_ERROR(rom()->WriteShort(
1811 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 18, 0x2040));
1812
1813 // If the area below is a large map, we don't need to add an offset to it.
1814 // otherwise leave it the same.
1815 // Just to make sure where don't try to read outside of the array.
1816 if (i + 16 < 64) {
1817 // If the area just below us is a large area.
1818 if (overworld_maps_[i + 16].is_large_map()) {
1819 // If the area just below us is the top left of a large area.
1820 if (overworld_maps_[i + 16].large_index() == 0) {
1821 RETURN_IF_ERROR(rom()->WriteShort(
1822 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 18, 0x2000));
1823 }
1824 }
1825 }
1826
1827 checked_map.emplace_back(i);
1828 checked_map.emplace_back((i + 1));
1829 checked_map.emplace_back((i + 8));
1830 checked_map.emplace_back((i + 9));
1831
1832 } else {
1833 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i, 0x00));
1834 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSizeHighByte + i, 0x01));
1835
1836 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, 0x01));
1837 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i + 64, 0x01));
1838
1840 rom()->WriteByte(kOverworldScreenSizeForLoading + i, 0x02));
1841 RETURN_IF_ERROR(rom()->WriteByte(
1843 RETURN_IF_ERROR(rom()->WriteByte(
1845
1846 RETURN_IF_ERROR(rom()->WriteShort(
1847 kOverworldScreenTileMapChangeByScreen1 + (i * 2), 0x0060));
1848
1849 // If the area to the left is a large map, we don't need to add an offset
1850 // to it. otherwise leave it the same.
1851 // Just to make sure where don't try to read outside of the array.
1852 if (i - 1 >= 0 && parent_x_pos != 0) {
1853 if (overworld_maps_[i - 1].is_large_map()) {
1854 if (overworld_maps_[i - 1].large_index() == 3) {
1855 RETURN_IF_ERROR(rom()->WriteShort(
1856 kOverworldScreenTileMapChangeByScreen1 + (i * 2), 0xF060));
1857 }
1858 }
1859 }
1860
1861 RETURN_IF_ERROR(rom()->WriteShort(
1862 kOverworldScreenTileMapChangeByScreen2 + (i * 2), 0x0040));
1863
1864 if (i + 1 < 64 && parent_x_pos != 7) {
1865 if (overworld_maps_[i + 1].is_large_map()) {
1866 if (overworld_maps_[i + 1].large_index() == 2) {
1867 RETURN_IF_ERROR(rom()->WriteShort(
1868 kOverworldScreenTileMapChangeByScreen2 + (i * 2), 0xF040));
1869 }
1870 }
1871 }
1872
1873 RETURN_IF_ERROR(rom()->WriteShort(
1874 kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x1800));
1875
1876 // If the area above is a large map, we don't need to add an offset to it.
1877 // otherwise leave it the same.
1878 // Just to make sure where don't try to read outside of the array.
1879 if (i - 8 >= 0) {
1880 // If the area just above us is a large area.
1881 if (overworld_maps_[i - 8].is_large_map()) {
1882 // If we are under the bottom right of the large area.
1883 if (overworld_maps_[i - 8].large_index() == 3) {
1884 RETURN_IF_ERROR(rom()->WriteShort(
1885 kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x17C0));
1886 }
1887 }
1888 }
1889
1890 RETURN_IF_ERROR(rom()->WriteShort(
1891 kOverworldScreenTileMapChangeByScreen4 + (i * 2), 0x1000));
1892
1893 // If the area below is a large map, we don't need to add an offset to it.
1894 // otherwise leave it the same.
1895 // Just to make sure where don't try to read outside of the array.
1896 if (i + 8 < 64) {
1897 // If the area just below us is a large area.
1898 if (overworld_maps_[i + 8].is_large_map()) {
1899 // If we are on top of the top right of the large area.
1900 if (overworld_maps_[i + 8].large_index() == 1) {
1901 RETURN_IF_ERROR(rom()->WriteShort(
1902 kOverworldScreenTileMapChangeByScreen4 + (i * 2), 0x0FC0));
1903 }
1904 }
1905 }
1906
1907 RETURN_IF_ERROR(rom()->WriteShort(kTransitionTargetNorth + (i * 2),
1908 (uint16_t)((y_pos * 0x200) - 0xE0)));
1909 RETURN_IF_ERROR(rom()->WriteShort(kTransitionTargetWest + (i * 2),
1910 (uint16_t)((x_pos * 0x200) - 0x100)));
1911
1912 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionX + (i * 2),
1913 (x_pos * 0x200)));
1914 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionY + (i * 2),
1915 (y_pos * 0x200)));
1916
1917 checked_map.emplace_back(i);
1918 }
1919 }
1920
1921 // The legacy parent and transition tables are 64-entry local tables, but the
1922 // screen-size bytes are stored per Light/Dark world. The original loop above
1923 // mirrors Light World sizes into Dark World while calculating transitions;
1924 // correct those world-specific bytes from the actual map objects before
1925 // returning so a Light/Special edit cannot silently clobber Dark World sizes.
1926 const auto legacy_size_byte = [this](int map_index) -> uint8_t {
1927 return overworld_maps_[map_index].is_large_map() ? 0x00 : 0x01;
1928 };
1929 const auto legacy_loading_size_byte = [this](int map_index) -> uint8_t {
1930 return overworld_maps_[map_index].is_large_map() ? 0x04 : 0x02;
1931 };
1932
1933 for (int map_index = 0; map_index < kSpecialWorldMapIdStart; ++map_index) {
1934 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + map_index,
1935 legacy_size_byte(map_index)));
1936 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSizeForLoading + map_index,
1937 legacy_loading_size_byte(map_index)));
1938 }
1939
1940 for (int map_index = kSpecialWorldMapIdStart; map_index < kNumOverworldMaps;
1941 ++map_index) {
1942 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSizeForLoading + map_index,
1943 legacy_loading_size_byte(map_index)));
1944 }
1945
1946 constexpr int OverworldScreenTileMapChangeMask = 0x1262C;
1947
1949 rom()->WriteShort(OverworldScreenTileMapChangeMask + 0, 0x1F80));
1951 rom()->WriteShort(OverworldScreenTileMapChangeMask + 2, 0x1F80));
1953 rom()->WriteShort(OverworldScreenTileMapChangeMask + 4, 0x007F));
1955 rom()->WriteShort(OverworldScreenTileMapChangeMask + 6, 0x007F));
1956
1957 return absl::OkStatus();
1958}
1959
1961 int i, int parent_x_pos, int parent_y_pos, int transition_target_north,
1962 int transition_target_west, int transition_pos_x, int transition_pos_y,
1963 int screen_change_1, int screen_change_2, int screen_change_3,
1964 int screen_change_4) {
1965 // Set basic transition targets
1967 rom()->WriteShort(transition_target_north + (i * 2),
1968 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
1970 rom()->WriteShort(transition_target_west + (i * 2),
1971 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
1972
1974 rom()->WriteShort(transition_pos_x + (i * 2), parent_x_pos * 0x0200));
1976 rom()->WriteShort(transition_pos_y + (i * 2), parent_y_pos * 0x0200));
1977
1978 // byScreen1 = Transitioning right
1979 uint16_t by_screen1_small = 0x0060;
1980
1981 // Check west neighbor for transition adjustments
1982 if ((i % 0x40) - 1 >= 0) {
1983 auto& west_neighbor = overworld_maps_[i - 1];
1984
1985 // Transition from bottom right quadrant of large area to small area
1986 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1987 west_neighbor.large_index() == 3) {
1988 by_screen1_small = 0xF060;
1989 }
1990 // Transition from bottom quadrant of tall area to small area
1991 else if (west_neighbor.area_size() == AreaSizeEnum::TallArea &&
1992 west_neighbor.large_index() == 2) {
1993 by_screen1_small = 0xF060;
1994 }
1995 }
1996
1998 rom()->WriteShort(screen_change_1 + (i * 2), by_screen1_small));
1999
2000 // byScreen2 = Transitioning left
2001 uint16_t by_screen2_small = 0x0040;
2002
2003 // Check east neighbor for transition adjustments
2004 if ((i % 0x40) + 1 < 0x40 && i + 1 < kNumOverworldMaps) {
2005 auto& east_neighbor = overworld_maps_[i + 1];
2006
2007 // Transition from bottom left quadrant of large area to small area
2008 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2009 east_neighbor.large_index() == 2) {
2010 by_screen2_small = 0xF040;
2011 }
2012 // Transition from bottom quadrant of tall area to small area
2013 else if (east_neighbor.area_size() == AreaSizeEnum::TallArea &&
2014 east_neighbor.large_index() == 2) {
2015 by_screen2_small = 0xF040;
2016 }
2017 }
2018
2020 rom()->WriteShort(screen_change_2 + (i * 2), by_screen2_small));
2021
2022 // byScreen3 = Transitioning down
2023 uint16_t by_screen3_small = 0x1800;
2024
2025 // Check north neighbor for transition adjustments
2026 if ((i % 0x40) - 8 >= 0) {
2027 auto& north_neighbor = overworld_maps_[i - 8];
2028
2029 // Transition from bottom right quadrant of large area to small area
2030 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2031 north_neighbor.large_index() == 3) {
2032 by_screen3_small = 0x17C0;
2033 }
2034 // Transition from right quadrant of wide area to small area
2035 else if (north_neighbor.area_size() == AreaSizeEnum::WideArea &&
2036 north_neighbor.large_index() == 1) {
2037 by_screen3_small = 0x17C0;
2038 }
2039 }
2040
2042 rom()->WriteShort(screen_change_3 + (i * 2), by_screen3_small));
2043
2044 // byScreen4 = Transitioning up
2045 uint16_t by_screen4_small = 0x1000;
2046
2047 // Check south neighbor for transition adjustments
2048 if ((i % 0x40) + 8 < 0x40 && i + 8 < kNumOverworldMaps) {
2049 auto& south_neighbor = overworld_maps_[i + 8];
2050
2051 // Transition from top right quadrant of large area to small area
2052 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2053 south_neighbor.large_index() == 1) {
2054 by_screen4_small = 0x0FC0;
2055 }
2056 // Transition from right quadrant of wide area to small area
2057 else if (south_neighbor.area_size() == AreaSizeEnum::WideArea &&
2058 south_neighbor.large_index() == 1) {
2059 by_screen4_small = 0x0FC0;
2060 }
2061 }
2062
2064 rom()->WriteShort(screen_change_4 + (i * 2), by_screen4_small));
2065
2066 return absl::OkStatus();
2067}
2068
2070 int i, int parent_x_pos, int parent_y_pos, int transition_target_north,
2071 int transition_target_west, int transition_pos_x, int transition_pos_y,
2072 int screen_change_1, int screen_change_2, int screen_change_3,
2073 int screen_change_4) {
2074 // Set transition targets for all 4 quadrants
2075 const uint16_t offsets[] = {0, 2, 16, 18};
2076 for (auto offset : offsets) {
2078 rom()->WriteShort(transition_target_north + (i * 2) + offset,
2079 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
2081 rom()->WriteShort(transition_target_west + (i * 2) + offset,
2082 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
2083 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset,
2084 parent_x_pos * 0x0200));
2085 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset,
2086 parent_y_pos * 0x0200));
2087 }
2088
2089 // Complex neighbor-aware transition calculations for large areas
2090 // byScreen1 = Transitioning right
2091 std::array<uint16_t, 4> by_screen1_large = {0x0060, 0x0060, 0x1060, 0x1060};
2092
2093 // Check west neighbor
2094 if ((i % 0x40) - 1 >= 0) {
2095 auto& west_neighbor = overworld_maps_[i - 1];
2096
2097 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2098 switch (west_neighbor.large_index()) {
2099 case 1: // From bottom right to bottom left of large area
2100 by_screen1_large[2] = 0x0060;
2101 break;
2102 case 3: // From bottom right to top left of large area
2103 by_screen1_large[0] = 0xF060;
2104 break;
2105 }
2106 } else if (west_neighbor.area_size() == AreaSizeEnum::TallArea) {
2107 switch (west_neighbor.large_index()) {
2108 case 0: // From bottom of tall to bottom left of large
2109 by_screen1_large[2] = 0x0060;
2110 break;
2111 case 2: // From bottom of tall to top left of large
2112 by_screen1_large[0] = 0xF060;
2113 break;
2114 }
2115 }
2116 }
2117
2118 for (int j = 0; j < 4; j++) {
2119 RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j],
2120 by_screen1_large[j]));
2121 }
2122
2123 // byScreen2 = Transitioning left
2124 std::array<uint16_t, 4> by_screen2_large = {0x0080, 0x0080, 0x1080, 0x1080};
2125
2126 // Check east neighbor
2127 if ((i % 0x40) + 2 < 0x40 && i + 2 < kNumOverworldMaps) {
2128 auto& east_neighbor = overworld_maps_[i + 2];
2129
2130 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2131 switch (east_neighbor.large_index()) {
2132 case 0: // From bottom left to bottom right of large area
2133 by_screen2_large[3] = 0x0080;
2134 break;
2135 case 2: // From bottom left to top right of large area
2136 by_screen2_large[1] = 0xF080;
2137 break;
2138 }
2139 } else if (east_neighbor.area_size() == AreaSizeEnum::TallArea) {
2140 switch (east_neighbor.large_index()) {
2141 case 0: // From bottom of tall to bottom right of large
2142 by_screen2_large[3] = 0x0080;
2143 break;
2144 case 2: // From bottom of tall to top right of large
2145 by_screen2_large[1] = 0xF080;
2146 break;
2147 }
2148 }
2149 }
2150
2151 for (int j = 0; j < 4; j++) {
2152 RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j],
2153 by_screen2_large[j]));
2154 }
2155
2156 // byScreen3 = Transitioning down
2157 std::array<uint16_t, 4> by_screen3_large = {0x1800, 0x1840, 0x1800, 0x1840};
2158
2159 // Check north neighbor
2160 if ((i % 0x40) - 8 >= 0) {
2161 auto& north_neighbor = overworld_maps_[i - 8];
2162
2163 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2164 switch (north_neighbor.large_index()) {
2165 case 2: // From bottom right to top right of large area
2166 by_screen3_large[1] = 0x1800;
2167 break;
2168 case 3: // From bottom right to top left of large area
2169 by_screen3_large[0] = 0x17C0;
2170 break;
2171 }
2172 } else if (north_neighbor.area_size() == AreaSizeEnum::WideArea) {
2173 switch (north_neighbor.large_index()) {
2174 case 0: // From right of wide to top right of large
2175 by_screen3_large[1] = 0x1800;
2176 break;
2177 case 1: // From right of wide to top left of large
2178 by_screen3_large[0] = 0x17C0;
2179 break;
2180 }
2181 }
2182 }
2183
2184 for (int j = 0; j < 4; j++) {
2185 RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j],
2186 by_screen3_large[j]));
2187 }
2188
2189 // byScreen4 = Transitioning up
2190 std::array<uint16_t, 4> by_screen4_large = {0x2000, 0x2040, 0x2000, 0x2040};
2191
2192 // Check south neighbor
2193 if ((i % 0x40) + 16 < 0x40 && i + 16 < kNumOverworldMaps) {
2194 auto& south_neighbor = overworld_maps_[i + 16];
2195
2196 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2197 switch (south_neighbor.large_index()) {
2198 case 0: // From top right to bottom right of large area
2199 by_screen4_large[3] = 0x2000;
2200 break;
2201 case 1: // From top right to bottom left of large area
2202 by_screen4_large[2] = 0x1FC0;
2203 break;
2204 }
2205 } else if (south_neighbor.area_size() == AreaSizeEnum::WideArea) {
2206 switch (south_neighbor.large_index()) {
2207 case 0: // From right of wide to bottom right of large
2208 by_screen4_large[3] = 0x2000;
2209 break;
2210 case 1: // From right of wide to bottom left of large
2211 by_screen4_large[2] = 0x1FC0;
2212 break;
2213 }
2214 }
2215 }
2216
2217 for (int j = 0; j < 4; j++) {
2218 RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j],
2219 by_screen4_large[j]));
2220 }
2221
2222 return absl::OkStatus();
2223}
2224
2226 int i, int parent_x_pos, int parent_y_pos, int transition_target_north,
2227 int transition_target_west, int transition_pos_x, int transition_pos_y,
2228 int screen_change_1, int screen_change_2, int screen_change_3,
2229 int screen_change_4) {
2230 // Set transition targets for both quadrants
2231 const uint16_t offsets[] = {0, 2};
2232 for (auto offset : offsets) {
2234 rom()->WriteShort(transition_target_north + (i * 2) + offset,
2235 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
2237 rom()->WriteShort(transition_target_west + (i * 2) + offset,
2238 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
2239 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset,
2240 parent_x_pos * 0x0200));
2241 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset,
2242 parent_y_pos * 0x0200));
2243 }
2244
2245 // byScreen1 = Transitioning right
2246 std::array<uint16_t, 2> by_screen1_wide = {0x0060, 0x0060};
2247
2248 // Check west neighbor
2249 if ((i % 0x40) - 1 >= 0) {
2250 auto& west_neighbor = overworld_maps_[i - 1];
2251
2252 // From bottom right of large to left of wide
2253 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2254 west_neighbor.large_index() == 3) {
2255 by_screen1_wide[0] = 0xF060;
2256 }
2257 // From bottom of tall to left of wide
2258 else if (west_neighbor.area_size() == AreaSizeEnum::TallArea &&
2259 west_neighbor.large_index() == 2) {
2260 by_screen1_wide[0] = 0xF060;
2261 }
2262 }
2263
2264 for (int j = 0; j < 2; j++) {
2265 RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j],
2266 by_screen1_wide[j]));
2267 }
2268
2269 // byScreen2 = Transitioning left
2270 std::array<uint16_t, 2> by_screen2_wide = {0x0080, 0x0080};
2271
2272 // Check east neighbor
2273 if ((i % 0x40) + 2 < 0x40 && i + 2 < kNumOverworldMaps) {
2274 auto& east_neighbor = overworld_maps_[i + 2];
2275
2276 // From bottom left of large to right of wide
2277 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2278 east_neighbor.large_index() == 2) {
2279 by_screen2_wide[1] = 0xF080;
2280 }
2281 // From bottom of tall to right of wide
2282 else if (east_neighbor.area_size() == AreaSizeEnum::TallArea &&
2283 east_neighbor.large_index() == 2) {
2284 by_screen2_wide[1] = 0xF080;
2285 }
2286 }
2287
2288 for (int j = 0; j < 2; j++) {
2289 RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j],
2290 by_screen2_wide[j]));
2291 }
2292
2293 // byScreen3 = Transitioning down
2294 std::array<uint16_t, 2> by_screen3_wide = {0x1800, 0x1840};
2295
2296 // Check north neighbor
2297 if ((i % 0x40) - 8 >= 0) {
2298 auto& north_neighbor = overworld_maps_[i - 8];
2299
2300 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2301 switch (north_neighbor.large_index()) {
2302 case 2: // From bottom right of large to right of wide
2303 by_screen3_wide[1] = 0x1800;
2304 break;
2305 case 3: // From bottom right of large to left of wide
2306 by_screen3_wide[0] = 0x17C0;
2307 break;
2308 }
2309 } else if (north_neighbor.area_size() == AreaSizeEnum::WideArea) {
2310 switch (north_neighbor.large_index()) {
2311 case 0: // From right of wide to right of wide
2312 by_screen3_wide[1] = 0x1800;
2313 break;
2314 case 1: // From right of wide to left of wide
2315 by_screen3_wide[0] = 0x07C0;
2316 break;
2317 }
2318 }
2319 }
2320
2321 for (int j = 0; j < 2; j++) {
2322 RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j],
2323 by_screen3_wide[j]));
2324 }
2325
2326 // byScreen4 = Transitioning up
2327 std::array<uint16_t, 2> by_screen4_wide = {0x1000, 0x1040};
2328
2329 // Check south neighbor
2330 if ((i % 0x40) + 8 < 0x40 && i + 8 < kNumOverworldMaps) {
2331 auto& south_neighbor = overworld_maps_[i + 8];
2332
2333 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2334 switch (south_neighbor.large_index()) {
2335 case 0: // From top right of large to right of wide
2336 by_screen4_wide[1] = 0x1000;
2337 break;
2338 case 1: // From top right of large to left of wide
2339 by_screen4_wide[0] = 0x0FC0;
2340 break;
2341 }
2342 } else if (south_neighbor.area_size() == AreaSizeEnum::WideArea) {
2343 if (south_neighbor.large_index() == 1) {
2344 by_screen4_wide[0] = 0x0FC0;
2345 }
2346 switch (south_neighbor.large_index()) {
2347 case 0: // From right of wide to right of wide
2348 by_screen4_wide[1] = 0x1000;
2349 break;
2350 case 1: // From right of wide to left of wide
2351 by_screen4_wide[0] = 0x0FC0;
2352 break;
2353 }
2354 }
2355 }
2356
2357 for (int j = 0; j < 2; j++) {
2358 RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j],
2359 by_screen4_wide[j]));
2360 }
2361
2362 return absl::OkStatus();
2363}
2364
2366 int i, int parent_x_pos, int parent_y_pos, int transition_target_north,
2367 int transition_target_west, int transition_pos_x, int transition_pos_y,
2368 int screen_change_1, int screen_change_2, int screen_change_3,
2369 int screen_change_4) {
2370 // Set transition targets for both quadrants
2371 const uint16_t offsets[] = {0, 16};
2372 for (auto offset : offsets) {
2374 rom()->WriteShort(transition_target_north + (i * 2) + offset,
2375 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
2377 rom()->WriteShort(transition_target_west + (i * 2) + offset,
2378 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
2379 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset,
2380 parent_x_pos * 0x0200));
2381 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset,
2382 parent_y_pos * 0x0200));
2383 }
2384
2385 // byScreen1 = Transitioning right
2386 std::array<uint16_t, 2> by_screen1_tall = {0x0060, 0x1060};
2387
2388 // Check west neighbor
2389 if ((i % 0x40) - 1 >= 0) {
2390 auto& west_neighbor = overworld_maps_[i - 1];
2391
2392 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2393 switch (west_neighbor.large_index()) {
2394 case 1: // From bottom right of large to bottom of tall
2395 by_screen1_tall[1] = 0x0060;
2396 break;
2397 case 3: // From bottom right of large to top of tall
2398 by_screen1_tall[0] = 0xF060;
2399 break;
2400 }
2401 } else if (west_neighbor.area_size() == AreaSizeEnum::TallArea) {
2402 switch (west_neighbor.large_index()) {
2403 case 0: // From bottom of tall to bottom of tall
2404 by_screen1_tall[1] = 0x0060;
2405 break;
2406 case 2: // From bottom of tall to top of tall
2407 by_screen1_tall[0] = 0xF060;
2408 break;
2409 }
2410 }
2411 }
2412
2413 for (int j = 0; j < 2; j++) {
2414 RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j],
2415 by_screen1_tall[j]));
2416 }
2417
2418 // byScreen2 = Transitioning left
2419 std::array<uint16_t, 2> by_screen2_tall = {0x0040, 0x1040};
2420
2421 // Check east neighbor
2422 if ((i % 0x40) + 1 < 0x40 && i + 1 < kNumOverworldMaps) {
2423 auto& east_neighbor = overworld_maps_[i + 1];
2424
2425 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2426 switch (east_neighbor.large_index()) {
2427 case 0: // From bottom left of large to bottom of tall
2428 by_screen2_tall[1] = 0x0040;
2429 break;
2430 case 2: // From bottom left of large to top of tall
2431 by_screen2_tall[0] = 0xF040;
2432 break;
2433 }
2434 } else if (east_neighbor.area_size() == AreaSizeEnum::TallArea) {
2435 switch (east_neighbor.large_index()) {
2436 case 0: // From bottom of tall to bottom of tall
2437 by_screen2_tall[1] = 0x0040;
2438 break;
2439 case 2: // From bottom of tall to top of tall
2440 by_screen2_tall[0] = 0xF040;
2441 break;
2442 }
2443 }
2444 }
2445
2446 for (int j = 0; j < 2; j++) {
2447 RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j],
2448 by_screen2_tall[j]));
2449 }
2450
2451 // byScreen3 = Transitioning down
2452 std::array<uint16_t, 2> by_screen3_tall = {0x1800, 0x1800};
2453
2454 // Check north neighbor
2455 if ((i % 0x40) - 8 >= 0) {
2456 auto& north_neighbor = overworld_maps_[i - 8];
2457
2458 // From bottom right of large to top of tall
2459 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2460 north_neighbor.large_index() == 3) {
2461 by_screen3_tall[0] = 0x17C0;
2462 }
2463 // From right of wide to top of tall
2464 else if (north_neighbor.area_size() == AreaSizeEnum::WideArea &&
2465 north_neighbor.large_index() == 1) {
2466 by_screen3_tall[0] = 0x17C0;
2467 }
2468 }
2469
2470 for (int j = 0; j < 2; j++) {
2471 RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j],
2472 by_screen3_tall[j]));
2473 }
2474
2475 // byScreen4 = Transitioning up
2476 std::array<uint16_t, 2> by_screen4_tall = {0x2000, 0x2000};
2477
2478 // Check south neighbor
2479 if ((i % 0x40) + 16 < 0x40 && i + 16 < kNumOverworldMaps) {
2480 auto& south_neighbor = overworld_maps_[i + 16];
2481
2482 // From top right of large to bottom of tall
2483 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2484 south_neighbor.large_index() == 1) {
2485 by_screen4_tall[1] = 0x1FC0;
2486 }
2487 // From right of wide to bottom of tall
2488 else if (south_neighbor.area_size() == AreaSizeEnum::WideArea &&
2489 south_neighbor.large_index() == 1) {
2490 by_screen4_tall[1] = 0x1FC0;
2491 }
2492 }
2493
2494 for (int j = 0; j < 2; j++) {
2495 RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j],
2496 by_screen4_tall[j]));
2497 }
2498
2499 return absl::OkStatus();
2500}
2501
2503 util::logf("Saving Large Maps (v3+ Expanded)");
2504
2505 // Use expanded memory locations for v3+
2506 int transition_target_north = zelda3::transition_target_northExpanded;
2507 int transition_target_west = zelda3::transition_target_westExpanded;
2508 int transition_pos_x = zelda3::GetOverworldTransitionPositionXExpanded();
2509 int transition_pos_y = zelda3::GetOverworldTransitionPositionYExpanded();
2510 int screen_change_1 = zelda3::GetOverworldScreenChange1Expanded();
2511 int screen_change_2 = zelda3::GetOverworldScreenChange2Expanded();
2512 int screen_change_3 = zelda3::GetOverworldScreenChange3Expanded();
2513 int screen_change_4 = zelda3::GetOverworldScreenChange4Expanded();
2514
2515 std::vector<uint8_t> checked_map;
2516
2517 // Process all overworld maps (0xA0 for v3)
2518 for (int i = 0; i < kNumOverworldMaps; ++i) {
2519 // Skip if this map was already processed as part of a multi-area structure
2520 if (std::find(checked_map.begin(), checked_map.end(), i) !=
2521 checked_map.end()) {
2522 continue;
2523 }
2524
2525 int parent_y_pos = (overworld_maps_[i].parent() % 0x40) / 8;
2526 int parent_x_pos = (overworld_maps_[i].parent() % 0x40) % 8;
2527
2528 // Write the map parent ID to expanded parent table
2531 overworld_maps_[i].parent()));
2532
2533 // Handle transitions based on area size
2534 switch (overworld_maps_[i].area_size()) {
2537 i, parent_x_pos, parent_y_pos, transition_target_north,
2538 transition_target_west, transition_pos_x, transition_pos_y,
2539 screen_change_1, screen_change_2, screen_change_3,
2540 screen_change_4));
2541 checked_map.emplace_back(i);
2542 break;
2543
2546 i, parent_x_pos, parent_y_pos, transition_target_north,
2547 transition_target_west, transition_pos_x, transition_pos_y,
2548 screen_change_1, screen_change_2, screen_change_3,
2549 screen_change_4));
2550 // Mark all 4 quadrants as processed
2551 checked_map.emplace_back(i);
2552 checked_map.emplace_back(i + 1);
2553 checked_map.emplace_back(i + 8);
2554 checked_map.emplace_back(i + 9);
2555 break;
2556
2559 i, parent_x_pos, parent_y_pos, transition_target_north,
2560 transition_target_west, transition_pos_x, transition_pos_y,
2561 screen_change_1, screen_change_2, screen_change_3,
2562 screen_change_4));
2563 // Mark both horizontal quadrants as processed
2564 checked_map.emplace_back(i);
2565 checked_map.emplace_back(i + 1);
2566 break;
2567
2570 i, parent_x_pos, parent_y_pos, transition_target_north,
2571 transition_target_west, transition_pos_x, transition_pos_y,
2572 screen_change_1, screen_change_2, screen_change_3,
2573 screen_change_4));
2574 // Mark both vertical quadrants as processed
2575 checked_map.emplace_back(i);
2576 checked_map.emplace_back(i + 8);
2577 break;
2578 }
2579 }
2580
2581 return absl::OkStatus();
2582}
2583
2584namespace {
2585std::vector<uint64_t> GetAllTile16(OverworldMapTiles& map_tiles_) {
2586 std::vector<uint64_t> all_tile_16; // Ensure it's 64 bits
2587
2588 int sx = 0;
2589 int sy = 0;
2590 int c = 0;
2591 OverworldBlockset tiles_used;
2592 for (int i = 0; i < kNumOverworldMaps; i++) {
2593 if (i < kDarkWorldMapIdStart) {
2594 tiles_used = map_tiles_.light_world;
2595 } else if (i < kSpecialWorldMapIdStart && i >= kDarkWorldMapIdStart) {
2596 tiles_used = map_tiles_.dark_world;
2597 } else {
2598 tiles_used = map_tiles_.special_world;
2599 }
2600
2601 for (int y = 0; y < 32; y += 2) {
2602 for (int x = 0; x < 32; x += 2) {
2603 gfx::Tile32 current_tile(
2604 tiles_used[x + (sx * 32)][y + (sy * 32)],
2605 tiles_used[x + 1 + (sx * 32)][y + (sy * 32)],
2606 tiles_used[x + (sx * 32)][y + 1 + (sy * 32)],
2607 tiles_used[x + 1 + (sx * 32)][y + 1 + (sy * 32)]);
2608
2609 all_tile_16.emplace_back(current_tile.GetPackedValue());
2610 }
2611 }
2612
2613 sx++;
2614 if (sx >= 8) {
2615 sy++;
2616 sx = 0;
2617 }
2618
2619 c++;
2620 if (c >= 64) {
2621 sx = 0;
2622 sy = 0;
2623 c = 0;
2624 }
2625 }
2626
2627 return all_tile_16;
2628}
2629} // namespace
2630
2632 tiles32_unique_.clear();
2633 tiles32_list_.clear();
2634
2635 ASSIGN_OR_RETURN(const auto storage_layout,
2636 ResolveMap32StorageLayout(*rom(), version_constants()));
2637
2638 // Get all tiles16 and packs them into tiles32
2639 std::vector<uint64_t> all_tile_16 = GetAllTile16(map_tiles_);
2640
2641 // Convert to set then back to vector
2642 std::set<uint64_t> unique_tiles_set(all_tile_16.begin(), all_tile_16.end());
2643
2644 std::vector<uint64_t> unique_tiles(all_tile_16);
2645 unique_tiles.assign(unique_tiles_set.begin(), unique_tiles_set.end());
2646
2647 // Create the indexed tiles list
2648 std::unordered_map<uint64_t, uint16_t> all_tiles_indexed;
2649 for (size_t tile32_id = 0; tile32_id < unique_tiles.size(); tile32_id++) {
2650 all_tiles_indexed.insert(
2651 {unique_tiles[tile32_id], static_cast<uint16_t>(tile32_id)});
2652 }
2653
2654 // Add all tiles32 from all maps.
2655 // Convert all tiles32 non-unique IDs into unique array of IDs.
2656 for (int j = 0; j < NumberOfMap32; j++) {
2657 tiles32_list_.emplace_back(all_tiles_indexed[all_tile_16[j]]);
2658 }
2659
2660 // Create the unique tiles list
2661 for (size_t i = 0; i < unique_tiles.size(); ++i) {
2662 tiles32_unique_.emplace_back(gfx::Tile32(unique_tiles[i]));
2663 }
2664
2665 while (tiles32_unique_.size() % 4 != 0) {
2666 gfx::Tile32 padding_tile(0, 0, 0, 0);
2667 tiles32_unique_.emplace_back(padding_tile.GetPackedValue());
2668 }
2669
2671 ValidateMap32DefinitionCount(tiles32_unique_.size(), storage_layout));
2672
2673 if (core::FeatureFlags::get().kLogToConsole) {
2674 std::cout << "Number of unique Tiles32: " << tiles32_unique_.size()
2675 << " Saved:" << tiles32_unique_.size()
2676 << " Out of: " << storage_layout.definition_capacity << std::endl;
2677 }
2678
2679 return absl::OkStatus();
2680}
2681
2683 ASSIGN_OR_RETURN(const auto storage_layout,
2684 ResolveMap32StorageLayout(*rom(), version_constants()));
2685 if (!storage_layout.expanded) {
2686 return absl::FailedPreconditionError(
2687 "Expanded Tile32 save requested for a vanilla Tile32 layout");
2688 }
2689 // Capacity and alignment must be checked before updating relocation pointers
2690 // or writing any quadrant bytes.
2692 ValidateMap32DefinitionCount(tiles32_unique_.size(), storage_layout));
2693
2694 const int bottomLeft = GetMap32TileBLExpanded();
2695 const int bottomRight = GetMap32TileBRExpanded();
2696 const int topRight = GetMap32TileTRExpanded();
2697
2698 // Updates the pointers too for the tile32
2699 // Top Right
2700 RETURN_IF_ERROR(rom()->WriteLong(0x0176EC, PcToSnes(topRight)));
2701 RETURN_IF_ERROR(rom()->WriteLong(0x0176F3, PcToSnes(topRight + 1)));
2702 RETURN_IF_ERROR(rom()->WriteLong(0x0176FA, PcToSnes(topRight + 2)));
2703 RETURN_IF_ERROR(rom()->WriteLong(0x017701, PcToSnes(topRight + 3)));
2704 RETURN_IF_ERROR(rom()->WriteLong(0x017708, PcToSnes(topRight + 4)));
2705 RETURN_IF_ERROR(rom()->WriteLong(0x01771A, PcToSnes(topRight + 5)));
2706
2707 // BottomLeft
2708 RETURN_IF_ERROR(rom()->WriteLong(0x01772C, PcToSnes(bottomLeft)));
2709 RETURN_IF_ERROR(rom()->WriteLong(0x017733, PcToSnes(bottomLeft + 1)));
2710 RETURN_IF_ERROR(rom()->WriteLong(0x01773A, PcToSnes(bottomLeft + 2)));
2711 RETURN_IF_ERROR(rom()->WriteLong(0x017741, PcToSnes(bottomLeft + 3)));
2712 RETURN_IF_ERROR(rom()->WriteLong(0x017748, PcToSnes(bottomLeft + 4)));
2713 RETURN_IF_ERROR(rom()->WriteLong(0x01775A, PcToSnes(bottomLeft + 5)));
2714
2715 // BottomRight
2716 RETURN_IF_ERROR(rom()->WriteLong(0x01776C, PcToSnes(bottomRight)));
2717 RETURN_IF_ERROR(rom()->WriteLong(0x017773, PcToSnes(bottomRight + 1)));
2718 RETURN_IF_ERROR(rom()->WriteLong(0x01777A, PcToSnes(bottomRight + 2)));
2719 RETURN_IF_ERROR(rom()->WriteLong(0x017781, PcToSnes(bottomRight + 3)));
2720 RETURN_IF_ERROR(rom()->WriteLong(0x017788, PcToSnes(bottomRight + 4)));
2721 RETURN_IF_ERROR(rom()->WriteLong(0x01779A, PcToSnes(bottomRight + 5)));
2722
2723 int unique_tile_index = 0;
2724 const int encoded_bytes = static_cast<int>(tiles32_unique_.size() /
2727
2728 for (int i = 0; i < encoded_bytes; i += kMap32BytesPerPackedGroup) {
2729 // Top Left.
2730 auto top_left = version_constants().kMap32TileTL;
2731 RETURN_IF_ERROR(rom()->WriteByte(
2732 top_left + i,
2733 (uint8_t)(tiles32_unique_[unique_tile_index].tile0_ & 0xFF)));
2734 RETURN_IF_ERROR(rom()->WriteByte(
2735 top_left + (i + 1),
2736 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile0_ & 0xFF)));
2737 RETURN_IF_ERROR(rom()->WriteByte(
2738 top_left + (i + 2),
2739 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile0_ & 0xFF)));
2740 RETURN_IF_ERROR(rom()->WriteByte(
2741 top_left + (i + 3),
2742 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile0_ & 0xFF)));
2743
2744 RETURN_IF_ERROR(rom()->WriteByte(
2745 top_left + (i + 4),
2746 (uint8_t)(((tiles32_unique_[unique_tile_index].tile0_ >> 4) & 0xF0) +
2747 ((tiles32_unique_[unique_tile_index + 1].tile0_ >> 8) &
2748 0x0F))));
2749 RETURN_IF_ERROR(rom()->WriteByte(
2750 top_left + (i + 5),
2751 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile0_ >> 4) &
2752 0xF0) +
2753 ((tiles32_unique_[unique_tile_index + 3].tile0_ >> 8) &
2754 0x0F))));
2755
2756 // Top Right.
2757 auto top_right = topRight;
2758 RETURN_IF_ERROR(rom()->WriteByte(
2759 top_right + i,
2760 (uint8_t)(tiles32_unique_[unique_tile_index].tile1_ & 0xFF)));
2761 RETURN_IF_ERROR(rom()->WriteByte(
2762 top_right + (i + 1),
2763 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile1_ & 0xFF)));
2764 RETURN_IF_ERROR(rom()->WriteByte(
2765 top_right + (i + 2),
2766 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile1_ & 0xFF)));
2767 RETURN_IF_ERROR(rom()->WriteByte(
2768 top_right + (i + 3),
2769 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile1_ & 0xFF)));
2770
2771 RETURN_IF_ERROR(rom()->WriteByte(
2772 top_right + (i + 4),
2773 (uint8_t)(((tiles32_unique_[unique_tile_index].tile1_ >> 4) & 0xF0) |
2774 ((tiles32_unique_[unique_tile_index + 1].tile1_ >> 8) &
2775 0x0F))));
2776 RETURN_IF_ERROR(rom()->WriteByte(
2777 top_right + (i + 5),
2778 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile1_ >> 4) &
2779 0xF0) |
2780 ((tiles32_unique_[unique_tile_index + 3].tile1_ >> 8) &
2781 0x0F))));
2782
2783 // Bottom Left.
2784 auto bottom_left = bottomLeft;
2785 RETURN_IF_ERROR(rom()->WriteByte(
2786 bottom_left + i,
2787 (uint8_t)(tiles32_unique_[unique_tile_index].tile2_ & 0xFF)));
2788 RETURN_IF_ERROR(rom()->WriteByte(
2789 bottom_left + (i + 1),
2790 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile2_ & 0xFF)));
2791 RETURN_IF_ERROR(rom()->WriteByte(
2792 bottom_left + (i + 2),
2793 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile2_ & 0xFF)));
2794 RETURN_IF_ERROR(rom()->WriteByte(
2795 bottom_left + (i + 3),
2796 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile2_ & 0xFF)));
2797
2798 RETURN_IF_ERROR(rom()->WriteByte(
2799 bottom_left + (i + 4),
2800 (uint8_t)(((tiles32_unique_[unique_tile_index].tile2_ >> 4) & 0xF0) |
2801 ((tiles32_unique_[unique_tile_index + 1].tile2_ >> 8) &
2802 0x0F))));
2803 RETURN_IF_ERROR(rom()->WriteByte(
2804 bottom_left + (i + 5),
2805 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile2_ >> 4) &
2806 0xF0) |
2807 ((tiles32_unique_[unique_tile_index + 3].tile2_ >> 8) &
2808 0x0F))));
2809
2810 // Bottom Right.
2811 auto bottom_right = bottomRight;
2812 RETURN_IF_ERROR(rom()->WriteByte(
2813 bottom_right + i,
2814 (uint8_t)(tiles32_unique_[unique_tile_index].tile3_ & 0xFF)));
2815 RETURN_IF_ERROR(rom()->WriteByte(
2816 bottom_right + (i + 1),
2817 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile3_ & 0xFF)));
2818 RETURN_IF_ERROR(rom()->WriteByte(
2819 bottom_right + (i + 2),
2820 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile3_ & 0xFF)));
2821 RETURN_IF_ERROR(rom()->WriteByte(
2822 bottom_right + (i + 3),
2823 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile3_ & 0xFF)));
2824
2825 RETURN_IF_ERROR(rom()->WriteByte(
2826 bottom_right + (i + 4),
2827 (uint8_t)(((tiles32_unique_[unique_tile_index].tile3_ >> 4) & 0xF0) |
2828 ((tiles32_unique_[unique_tile_index + 1].tile3_ >> 8) &
2829 0x0F))));
2830 RETURN_IF_ERROR(rom()->WriteByte(
2831 bottom_right + (i + 5),
2832 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile3_ >> 4) &
2833 0xF0) |
2834 ((tiles32_unique_[unique_tile_index + 3].tile3_ >> 8) &
2835 0x0F))));
2836
2837 unique_tile_index += 4;
2838 }
2839
2840 return absl::OkStatus();
2841}
2842
2844 util::logf("Saving Map32 Tiles");
2845 ASSIGN_OR_RETURN(const auto storage_layout,
2846 ResolveMap32StorageLayout(*rom(), version_constants()));
2847 if (storage_layout.expanded) {
2848 return absl::FailedPreconditionError(
2849 "Vanilla Tile32 save requested for an expanded Tile32 layout");
2850 }
2852 ValidateMap32DefinitionCount(tiles32_unique_.size(), storage_layout));
2853
2854 int unique_tile_index = 0;
2855 const int encoded_bytes = static_cast<int>(tiles32_unique_.size() /
2858
2859 for (int i = 0; i < encoded_bytes; i += kMap32BytesPerPackedGroup) {
2860 // Top Left.
2861 auto top_left = version_constants().kMap32TileTL;
2862
2863 RETURN_IF_ERROR(rom()->WriteByte(
2864 top_left + i,
2865 (uint8_t)(tiles32_unique_[unique_tile_index].tile0_ & 0xFF)));
2866 RETURN_IF_ERROR(rom()->WriteByte(
2867 top_left + (i + 1),
2868 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile0_ & 0xFF)));
2869 RETURN_IF_ERROR(rom()->WriteByte(
2870 top_left + (i + 2),
2871 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile0_ & 0xFF)));
2872 RETURN_IF_ERROR(rom()->WriteByte(
2873 top_left + (i + 3),
2874 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile0_ & 0xFF)));
2875
2876 RETURN_IF_ERROR(rom()->WriteByte(
2877 top_left + (i + 4),
2878 (uint8_t)(((tiles32_unique_[unique_tile_index].tile0_ >> 4) & 0xF0) +
2879 ((tiles32_unique_[unique_tile_index + 1].tile0_ >> 8) &
2880 0x0F))));
2881 RETURN_IF_ERROR(rom()->WriteByte(
2882 top_left + (i + 5),
2883 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile0_ >> 4) &
2884 0xF0) +
2885 ((tiles32_unique_[unique_tile_index + 3].tile0_ >> 8) &
2886 0x0F))));
2887
2888 // Top Right.
2889 auto top_right = version_constants().kMap32TileTR;
2890 RETURN_IF_ERROR(rom()->WriteByte(
2891 top_right + i,
2892 (uint8_t)(tiles32_unique_[unique_tile_index].tile1_ & 0xFF)));
2893 RETURN_IF_ERROR(rom()->WriteByte(
2894 top_right + (i + 1),
2895 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile1_ & 0xFF)));
2896 RETURN_IF_ERROR(rom()->WriteByte(
2897 top_right + (i + 2),
2898 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile1_ & 0xFF)));
2899 RETURN_IF_ERROR(rom()->WriteByte(
2900 top_right + (i + 3),
2901 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile1_ & 0xFF)));
2902
2903 RETURN_IF_ERROR(rom()->WriteByte(
2904 top_right + (i + 4),
2905 (uint8_t)(((tiles32_unique_[unique_tile_index].tile1_ >> 4) & 0xF0) |
2906 ((tiles32_unique_[unique_tile_index + 1].tile1_ >> 8) &
2907 0x0F))));
2908 RETURN_IF_ERROR(rom()->WriteByte(
2909 top_right + (i + 5),
2910 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile1_ >> 4) &
2911 0xF0) |
2912 ((tiles32_unique_[unique_tile_index + 3].tile1_ >> 8) &
2913 0x0F))));
2914
2915 // Bottom Left.
2917 RETURN_IF_ERROR(rom()->WriteByte(
2918 map32TilesBL + i,
2919 (uint8_t)(tiles32_unique_[unique_tile_index].tile2_ & 0xFF)));
2920 RETURN_IF_ERROR(rom()->WriteByte(
2921 map32TilesBL + (i + 1),
2922 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile2_ & 0xFF)));
2923 RETURN_IF_ERROR(rom()->WriteByte(
2924 map32TilesBL + (i + 2),
2925 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile2_ & 0xFF)));
2926 RETURN_IF_ERROR(rom()->WriteByte(
2927 map32TilesBL + (i + 3),
2928 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile2_ & 0xFF)));
2929
2930 RETURN_IF_ERROR(rom()->WriteByte(
2931 map32TilesBL + (i + 4),
2932 (uint8_t)(((tiles32_unique_[unique_tile_index].tile2_ >> 4) & 0xF0) |
2933 ((tiles32_unique_[unique_tile_index + 1].tile2_ >> 8) &
2934 0x0F))));
2935 RETURN_IF_ERROR(rom()->WriteByte(
2936 map32TilesBL + (i + 5),
2937 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile2_ >> 4) &
2938 0xF0) |
2939 ((tiles32_unique_[unique_tile_index + 3].tile2_ >> 8) &
2940 0x0F))));
2941
2942 // Bottom Right.
2944 RETURN_IF_ERROR(rom()->WriteByte(
2945 map32TilesBR + i,
2946 (uint8_t)(tiles32_unique_[unique_tile_index].tile3_ & 0xFF)));
2947 RETURN_IF_ERROR(rom()->WriteByte(
2948 map32TilesBR + (i + 1),
2949 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile3_ & 0xFF)));
2950 RETURN_IF_ERROR(rom()->WriteByte(
2951 map32TilesBR + (i + 2),
2952 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile3_ & 0xFF)));
2953 RETURN_IF_ERROR(rom()->WriteByte(
2954 map32TilesBR + (i + 3),
2955 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile3_ & 0xFF)));
2956
2957 RETURN_IF_ERROR(rom()->WriteByte(
2958 map32TilesBR + (i + 4),
2959 (uint8_t)(((tiles32_unique_[unique_tile_index].tile3_ >> 4) & 0xF0) |
2960 ((tiles32_unique_[unique_tile_index + 1].tile3_ >> 8) &
2961 0x0F))));
2962 RETURN_IF_ERROR(rom()->WriteByte(
2963 map32TilesBR + (i + 5),
2964 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile3_ >> 4) &
2965 0xF0) |
2966 ((tiles32_unique_[unique_tile_index + 3].tile3_ >> 8) &
2967 0x0F))));
2968
2969 unique_tile_index += 4;
2970 }
2971
2972 return absl::OkStatus();
2973}
2974
2976 const int map16_expanded = GetMap16TilesExpanded();
2978 rom()->WriteLong(SnesToPc(0x008865), PcToSnes(map16_expanded)));
2980 rom()->WriteLong(SnesToPc(0x0EDE4F), PcToSnes(map16_expanded)));
2982 rom()->WriteLong(SnesToPc(0x0EDEE9), PcToSnes(map16_expanded)));
2983
2985 rom()->WriteLong(SnesToPc(0x1BBC2D), PcToSnes(map16_expanded + 2)));
2987 rom()->WriteLong(SnesToPc(0x1BBC4C), PcToSnes(map16_expanded)));
2989 rom()->WriteLong(SnesToPc(0x1BBCC2), PcToSnes(map16_expanded + 4)));
2991 rom()->WriteLong(SnesToPc(0x1BBCCB), PcToSnes(map16_expanded + 6)));
2992
2994 rom()->WriteLong(SnesToPc(0x1BBEF6), PcToSnes(map16_expanded)));
2996 rom()->WriteLong(SnesToPc(0x1BBF23), PcToSnes(map16_expanded)));
2998 rom()->WriteLong(SnesToPc(0x1BC041), PcToSnes(map16_expanded)));
3000 rom()->WriteLong(SnesToPc(0x1BC9B3), PcToSnes(map16_expanded)));
3001
3003 rom()->WriteLong(SnesToPc(0x1BC9BA), PcToSnes(map16_expanded + 2)));
3005 rom()->WriteLong(SnesToPc(0x1BC9C1), PcToSnes(map16_expanded + 4)));
3007 rom()->WriteLong(SnesToPc(0x1BC9C8), PcToSnes(map16_expanded + 6)));
3008
3010 rom()->WriteLong(SnesToPc(0x1BCA40), PcToSnes(map16_expanded)));
3012 rom()->WriteLong(SnesToPc(0x1BCA47), PcToSnes(map16_expanded + 2)));
3014 rom()->WriteLong(SnesToPc(0x1BCA4E), PcToSnes(map16_expanded + 4)));
3016 rom()->WriteLong(SnesToPc(0x1BCA55), PcToSnes(map16_expanded + 6)));
3017
3019 rom()->WriteLong(SnesToPc(0x02F457), PcToSnes(map16_expanded)));
3021 rom()->WriteLong(SnesToPc(0x02F45E), PcToSnes(map16_expanded + 2)));
3023 rom()->WriteLong(SnesToPc(0x02F467), PcToSnes(map16_expanded + 4)));
3025 rom()->WriteLong(SnesToPc(0x02F46E), PcToSnes(map16_expanded + 6)));
3027 rom()->WriteLong(SnesToPc(0x02F51F), PcToSnes(map16_expanded)));
3029 rom()->WriteLong(SnesToPc(0x02F526), PcToSnes(map16_expanded + 4)));
3031 rom()->WriteLong(SnesToPc(0x02F52F), PcToSnes(map16_expanded + 2)));
3033 rom()->WriteLong(SnesToPc(0x02F536), PcToSnes(map16_expanded + 6)));
3034
3036 rom()->WriteShort(SnesToPc(0x02FE1C), PcToSnes(map16_expanded)));
3038 rom()->WriteShort(SnesToPc(0x02FE23), PcToSnes(map16_expanded + 4)));
3040 rom()->WriteShort(SnesToPc(0x02FE2C), PcToSnes(map16_expanded + 2)));
3042 rom()->WriteShort(SnesToPc(0x02FE33), PcToSnes(map16_expanded + 6)));
3043
3045 rom()->WriteByte(SnesToPc(0x02FD28),
3046 static_cast<uint8_t>(PcToSnes(map16_expanded) >> 16)));
3048 rom()->WriteByte(SnesToPc(0x02FD39),
3049 static_cast<uint8_t>(PcToSnes(map16_expanded) >> 16)));
3050
3051 int tpos = map16_expanded;
3052 for (int i = 0; i < NumberOfMap16Ex; i += 1) // 4096
3053 {
3055 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_)));
3056 tpos += 2;
3058 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_)));
3059 tpos += 2;
3061 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_)));
3062 tpos += 2;
3064 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_)));
3065 tpos += 2;
3066 }
3067
3068 return absl::OkStatus();
3069}
3070
3072 util::logf("Saving Map16 Tiles");
3073 int tpos = kMap16Tiles;
3074 // 3760
3075 for (int i = 0; i < NumberOfMap16; i += 1) {
3077 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_)))
3078 tpos += 2;
3080 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_)))
3081 tpos += 2;
3083 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_)))
3084 tpos += 2;
3086 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_)))
3087 tpos += 2;
3088 }
3089 return absl::OkStatus();
3090}
3091
3098
3099absl::Status Overworld::SaveExits() {
3101 return absl::OkStatus();
3102}
3103
3104absl::Status Overworld::SaveItems() {
3106
3107 // Compact the in-memory vector: physically remove items flagged as deleted
3108 // so they don't accumulate across save cycles or leak through the API.
3109 all_items_.erase(
3110 std::remove_if(all_items_.begin(), all_items_.end(),
3111 [](const OverworldItem& item) { return item.deleted; }),
3112 all_items_.end());
3113
3114 return absl::OkStatus();
3115}
3116
3118 util::logf("Saving Map Overlays");
3119
3120 // Generate the new overlay code that handles interactive overlays
3121 std::vector<uint8_t> new_overlay_code = {
3122 0xC2, 0x30, // REP #$30
3123 0xA5, 0x8A, // LDA $8A
3124 0x0A, 0x18, // ASL : CLC
3125 0x65, 0x8A, // ADC $8A
3126 0xAA, // TAX
3127 0xBF, 0x00, 0x00, 0x00, // LDA, X
3128 0x85, 0x00, // STA $00
3129 0xBF, 0x00, 0x00, 0x00, // LDA, X +2
3130 0x85, 0x02, // STA $02
3131 0x4B, // PHK
3132 0xF4, 0x00, 0x00, // This position +3 ?
3133 0xDC, 0x00, 0x00, // JML [$00 00]
3134 0xE2, 0x30, // SEP #$30
3135 0xAB, // PLB
3136 0x6B, // RTL
3137 };
3138
3139 // Write overlay code to ROM
3140 constexpr int kOverlayCodeStart = 0x077657;
3141 RETURN_IF_ERROR(rom()->WriteVector(kOverlayCodeStart, new_overlay_code));
3142
3143 // Set up overlay pointers
3144 int ptr_start = kOverlayCodeStart + 0x20;
3145 int snes_ptr_start = PcToSnes(ptr_start);
3146
3147 // Write overlay pointer addresses in the code
3148 RETURN_IF_ERROR(rom()->WriteLong(kOverlayCodeStart + 10, snes_ptr_start));
3149 RETURN_IF_ERROR(rom()->WriteLong(kOverlayCodeStart + 16, snes_ptr_start + 2));
3150
3151 int pea_addr = PcToSnes(kOverlayCodeStart + 27);
3152 RETURN_IF_ERROR(rom()->WriteShort(kOverlayCodeStart + 23, pea_addr));
3153
3154 // Write overlay data to expanded space
3155 constexpr int kExpandedOverlaySpace = 0x120000;
3156 int pos = kExpandedOverlaySpace;
3157 int ptr_pos = kOverlayCodeStart + 32;
3158
3159 for (int i = 0; i < kNumOverworldMaps; i++) {
3160 int snes_addr = PcToSnes(pos);
3161 RETURN_IF_ERROR(rom()->WriteLong(ptr_pos, snes_addr & 0xFFFFFF));
3162 ptr_pos += 3;
3163
3164 // Write overlay data for each map that has overlays
3165 if (overworld_maps_[i].has_overlay()) {
3166 const auto& overlay_data = overworld_maps_[i].overlay_data();
3167 for (size_t t = 0; t < overlay_data.size(); t += 3) {
3168 if (t + 2 < overlay_data.size()) {
3169 // Generate LDA/STA sequence for each overlay tile
3170 RETURN_IF_ERROR(rom()->WriteByte(pos, 0xA9)); // LDA #$
3171 RETURN_IF_ERROR(rom()->WriteShort(
3172 pos + 1, overlay_data[t] | (overlay_data[t + 1] << 8)));
3173 pos += 3;
3174
3175 RETURN_IF_ERROR(rom()->WriteByte(pos, 0x8D)); // STA $xxxx
3176 RETURN_IF_ERROR(rom()->WriteShort(pos + 1, overlay_data[t + 2]));
3177 pos += 3;
3178 }
3179 }
3180 }
3181
3182 RETURN_IF_ERROR(rom()->WriteByte(pos, 0x6B)); // RTL
3183 pos++;
3184 }
3185
3186 return absl::OkStatus();
3187}
3188
3190 util::logf("Saving Overworld Tiles Types");
3191
3192 for (int i = 0; i < kNumTileTypes; i++) {
3194 rom()->WriteByte(overworldTilesType + i, all_tiles_types_[i]));
3195 }
3196
3197 return absl::OkStatus();
3198}
3199
3201 util::logf("Loading Diggable Tiles");
3202
3203 // Check if custom diggable tiles are enabled
3204 ASSIGN_OR_RETURN(uint8_t enable_flag,
3206
3207 if (enable_flag != 0x00 && enable_flag != 0xFF) {
3208 // Custom table is enabled, load from ROM
3209 std::array<uint8_t, kDiggableTilesBitfieldSize> bitfield;
3210 for (int i = 0; i < kDiggableTilesBitfieldSize; ++i) {
3211 ASSIGN_OR_RETURN(bitfield[i],
3212 rom()->ReadByte(kOverworldCustomDiggableTilesArray + i));
3213 }
3214 diggable_tiles_.FromBytes(bitfield.data());
3215 } else {
3216 // Use vanilla defaults
3218 }
3219
3220 return absl::OkStatus();
3221}
3222
3224 // Diggable tiles require v3+ (custom table at 0x140980+)
3225 const auto version = OverworldVersionHelper::GetVersion(*rom_);
3226 cached_version_ = version;
3228 return absl::OkStatus(); // Skip for vanilla/v1/v2
3229 }
3230
3231 util::logf("Saving Diggable Tiles");
3232
3233 // Write enable flag
3235
3236 // Write the 64-byte bitfield
3237 const auto& bitfield = diggable_tiles_.GetRawData();
3238 for (int i = 0; i < kDiggableTilesBitfieldSize; ++i) {
3240 rom()->WriteByte(kOverworldCustomDiggableTilesArray + i, bitfield[i]));
3241 }
3242
3243 return absl::OkStatus();
3244}
3245
3247 util::logf("Auto-detecting Diggable Tiles");
3248
3250
3251 // Iterate through all Map16 tiles and check if they're diggable
3252 for (uint16_t tile_id = 0; tile_id < static_cast<uint16_t>(tiles16_.size()) &&
3253 tile_id < kMaxDiggableTileId;
3254 ++tile_id) {
3256 diggable_tiles_.SetDiggable(tile_id, true);
3257 }
3258 }
3259
3260 util::logf("Auto-detected %d diggable tiles",
3262 return absl::OkStatus();
3263}
3264
3265absl::Status Overworld::SaveCustomOverworldASM(bool enable_bg_color,
3266 bool enable_main_palette,
3267 bool enable_mosaic,
3268 bool enable_gfx_groups,
3269 bool enable_subscreen_overlay,
3270 bool enable_animated) {
3271 // Check ROM version - this function requires at least v2 for basic features
3274 return absl::OkStatus(); // Cannot apply custom ASM settings to vanilla/v1
3275 }
3276
3277 util::logf("Applying Custom Overworld ASM");
3278
3279 const auto write_enable_flag = [this](int address,
3280 bool enabled) -> absl::Status {
3281 ASSIGN_OR_RETURN(const uint8_t current, rom()->ReadByte(address));
3282 if ((current != 0x00) == enabled) {
3283 return absl::OkStatus();
3284 }
3285 return rom()->WriteByte(address, enabled ? 0xFF : 0x00);
3286 };
3287
3288 // v2+ features: BG color, main palette enable flags
3291 enable_bg_color));
3293 enable_main_palette));
3294
3295 // Write the main palette table
3296 if (enable_main_palette) {
3297 for (int i = 0; i < kNumOverworldMaps; i++) {
3299 overworld_maps_[i].main_palette()));
3300 }
3301 }
3302 }
3303
3304 // v3+ features: mosaic, gfx groups, animated, overlays
3307 write_enable_flag(OverworldCustomMosaicEnabled, enable_mosaic));
3309 enable_gfx_groups));
3311 write_enable_flag(OverworldCustomAnimatedGFXEnabled, enable_animated));
3313 enable_subscreen_overlay));
3314
3315 // Write the mosaic table
3316 if (enable_mosaic) {
3317 for (int i = 0; i < kNumOverworldMaps; i++) {
3318 const auto& mosaic = overworld_maps_[i].mosaic_expanded();
3319 // .... udlr bit format
3320 uint8_t mosaic_byte = (mosaic[0] ? 0x08 : 0x00) | // up
3321 (mosaic[1] ? 0x04 : 0x00) | // down
3322 (mosaic[2] ? 0x02 : 0x00) | // left
3323 (mosaic[3] ? 0x01 : 0x00); // right
3324
3326 rom()->WriteByte(OverworldCustomMosaicArray + i, mosaic_byte));
3327 }
3328 }
3329
3330 if (enable_gfx_groups) {
3331 for (int i = 0; i < kNumOverworldMaps; i++) {
3332 for (int j = 0; j < 8; j++) {
3334 rom()->WriteByte(OverworldCustomTileGFXGroupArray + (i * 8) + j,
3335 overworld_maps_[i].custom_tileset(j)));
3336 }
3337 }
3338 }
3339 if (enable_animated) {
3340 for (int i = 0; i < kNumOverworldMaps; i++) {
3342 overworld_maps_[i].animated_gfx()));
3343 }
3344 }
3345
3346 // Write the subscreen overlay table
3347 if (enable_subscreen_overlay) {
3348 for (int i = 0; i < kNumOverworldMaps; i++) {
3350 rom()->WriteShort(OverworldCustomSubscreenOverlayArray + (i * 2),
3351 overworld_maps_[i].subscreen_overlay()));
3352 }
3353 }
3354 }
3355
3356 return absl::OkStatus();
3357}
3358
3360 // Only write to custom address space for v2+ ROMs
3362 return absl::OkStatus(); // Vanilla/v1 ROM - skip custom address writes
3363 }
3364
3365 util::logf("Saving Area Specific Background Colors");
3366
3367 // Write area-specific background colors if enabled
3368 for (int i = 0; i < kNumOverworldMaps; i++) {
3369 uint16_t bg_color = overworld_maps_[i].area_specific_bg_color();
3370 RETURN_IF_ERROR(rom()->WriteShort(
3371 OverworldCustomAreaSpecificBGPalette + (i * 2), bg_color));
3372 }
3373
3374 return absl::OkStatus();
3375}
3376
3378 if (!rom_ || !rom_->is_loaded()) {
3379 return absl::FailedPreconditionError("ROM not loaded");
3380 }
3381
3385 return absl::OkStatus();
3386 }
3387
3388 const auto enabled = [this](int address) {
3389 return ReadRomByteOr(*rom_, address, 0x00) != 0x00;
3390 };
3391 const bool bg_color_enabled = enabled(OverworldCustomAreaSpecificBGEnabled);
3392 const bool main_palette_enabled = enabled(OverworldCustomMainPaletteEnabled);
3393 const bool mosaic_enabled = enabled(OverworldCustomMosaicEnabled);
3394 const bool gfx_groups_enabled = enabled(OverworldCustomTileGFXGroupEnabled);
3395 const bool subscreen_overlay_enabled =
3397 const bool animated_enabled = enabled(OverworldCustomAnimatedGFXEnabled);
3399 bg_color_enabled, main_palette_enabled, mosaic_enabled,
3400 gfx_groups_enabled, subscreen_overlay_enabled, animated_enabled));
3401 if (bg_color_enabled) {
3403 }
3405 return absl::OkStatus();
3406}
3407
3409 util::logf("Saving Map Properties");
3410 const auto version = OverworldVersionHelper::GetVersion(*rom_);
3411 const bool use_zscustom_palette_table =
3413 const bool use_zscustom_special_tables =
3415 const int palette_table = use_zscustom_palette_table
3418
3419 for (int i = 0; i < kDarkWorldMapIdStart; i++) {
3420 RETURN_IF_ERROR(rom()->WriteByte(kAreaGfxIdPtr + i,
3421 overworld_maps_[i].area_graphics()));
3423 rom()->WriteByte(palette_table + i, overworld_maps_[i].area_palette()));
3424 RETURN_IF_ERROR(rom()->WriteByte(kOverworldSpriteset + i,
3425 overworld_maps_[i].sprite_graphics(0)));
3427 rom()->WriteByte(kOverworldSpriteset + kDarkWorldMapIdStart + i,
3428 overworld_maps_[i].sprite_graphics(1)));
3431 overworld_maps_[i].sprite_graphics(2)));
3433 overworld_maps_[i].sprite_palette(0)));
3436 overworld_maps_[i].sprite_palette(1)));
3437 RETURN_IF_ERROR(rom()->WriteByte(
3439 overworld_maps_[i].sprite_palette(2)));
3440 }
3441
3442 for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) {
3443 RETURN_IF_ERROR(rom()->WriteByte(kAreaGfxIdPtr + i,
3444 overworld_maps_[i].area_graphics()));
3447 overworld_maps_[i].sprite_graphics(0)));
3449 rom()->WriteByte(palette_table + i, overworld_maps_[i].area_palette()));
3450 RETURN_IF_ERROR(rom()->WriteByte(
3452 overworld_maps_[i].sprite_palette(0)));
3453 }
3454
3455 for (int i = kSpecialWorldMapIdStart; i < kSpecialWorldMapIdStart + 0x20;
3456 i++) {
3457 if (use_zscustom_special_tables) {
3458 RETURN_IF_ERROR(rom()->WriteByte(kAreaGfxIdPtr + i,
3459 overworld_maps_[i].area_graphics()));
3460 }
3461 if (use_zscustom_palette_table) {
3462 RETURN_IF_ERROR(rom()->WriteByte(palette_table + i,
3463 overworld_maps_[i].area_palette()));
3464 }
3465
3466 const int local_index = i - kSpecialWorldMapIdStart;
3467 const int sprite_gfx_table =
3468 use_zscustom_special_tables
3471 const int sprite_palette_table =
3472 use_zscustom_special_tables ? kOverworldSpecialSpritePaletteExpandedTemp
3474 RETURN_IF_ERROR(rom()->WriteByte(sprite_gfx_table + local_index,
3475 overworld_maps_[i].sprite_graphics(0)));
3476 RETURN_IF_ERROR(rom()->WriteByte(sprite_palette_table + local_index,
3477 overworld_maps_[i].sprite_palette(0)));
3478 }
3479
3480 return absl::OkStatus();
3481}
3482
3483absl::Status Overworld::SaveMusic() {
3484 util::logf("Saving Music Data");
3485
3486 // Save music data for Light World maps
3487 for (int i = 0; i < kDarkWorldMapIdStart; i++) {
3489 overworld_maps_[i].area_music(0)));
3490 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicZelda + i,
3491 overworld_maps_[i].area_music(1)));
3493 overworld_maps_[i].area_music(2)));
3495 overworld_maps_[i].area_music(3)));
3496 }
3497
3498 // Save music data for Dark World maps
3499 for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) {
3502 overworld_maps_[i].area_music(0)));
3503 }
3504
3505 return absl::OkStatus();
3506}
3507
3509 util::logf("Saving V3 Area Sizes");
3510
3511 // Check if this is a v3 ROM
3512 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
3513 if (asm_version < 3 || asm_version == 0xFF) {
3514 return absl::OkStatus(); // Not a v3 ROM, nothing to do
3515 }
3516
3517 // Save area sizes to the expanded table
3518 for (int i = 0; i < kNumOverworldMaps; i++) {
3519 uint8_t area_size_byte =
3520 static_cast<uint8_t>(overworld_maps_[i].area_size());
3521 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, area_size_byte));
3522 }
3523
3524 // Save message IDs to expanded table
3525 for (int i = 0; i < kNumOverworldMaps; i++) {
3526 uint16_t message_id = overworld_maps_[i].message_id();
3527 RETURN_IF_ERROR(rom()->WriteShort(GetOverworldMessagesExpanded() + (i * 2),
3528 message_id));
3529 }
3530
3531 return absl::OkStatus();
3532}
3533
3534} // 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:28
auto size() const
Definition rom.h:150
bool is_loaded() const
Definition rom.h:144
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 FromBytes(const uint8_t *data)
Load bitfield from raw bytes (64 bytes).
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.
const std::array< uint8_t, kDiggableTilesBitfieldSize > & GetRawData() const
Get raw bitfield data for direct ROM writing.
void Clear()
Clear all diggable bits.
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 DecompressAllMapTilesParallel()
Definition overworld.cc:750
std::vector< uint16_t > tiles32_list_
Definition overworld.h:845
absl::Status Load(Rom *rom)
Load all overworld data from ROM.
Definition overworld.cc:131
std::vector< OverworldItem > all_items_
Definition overworld.h:839
void OrganizeMapTiles(std::vector< uint8_t > &bytes, std::vector< uint8_t > &bytes2, int i, int sx, int sy, int &ttpos)
Definition overworld.cc:730
zelda3_version_pointers version_constants() const
Get version-specific ROM addresses.
Definition overworld.h:397
std::array< int, kNumOverworldMaps > map_pointers1
Definition overworld.h:856
std::vector< gfx::Tile32 > tiles32_unique_
Definition overworld.h:843
const std::vector< uint8_t > & current_graphics() const
Definition overworld.h:689
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::vector< OverworldEntrance > all_entrances_
Definition overworld.h:836
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:807
absl::Status SaveMap16Tiles()
Save tile16 definitions to ROM.
absl::Status SaveAreaSizes()
Save area size enum data (v3+ only)
void InvalidateSiblingMapCaches(int map_index)
Invalidate cached tilesets for a map and all its siblings.
absl::Status SaveLargeMaps()
Save large map parent/sibling relationships.
std::array< uint8_t, kNumOverworldMaps > map_parent_
Definition overworld.h:848
void AssignWorldTiles(int x, int y, int sx, int sy, int tpos, OverworldBlockset &world)
Definition overworld.cc:685
absl::Status SaveDiggableTiles()
void InvalidateMapCache(int map_index)
Invalidate cached tileset for a specific map.
std::array< uint8_t, kNumTileTypes > all_tiles_types_
Definition overworld.h:849
std::unordered_map< uint64_t, GraphicsConfigCache > gfx_config_cache_
Definition overworld.h:825
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:854
const std::vector< uint8_t > * GetCachedTileset(uint64_t config_hash)
Try to get cached tileset data for a graphics configuration.
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:697
bool HasExpandedPointerTables() const
Check if the ROM has expanded pointer tables for tail maps.
Definition overworld.h:611
static constexpr int kMaxCachedConfigs
Definition overworld.h:832
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.
void FillBlankMapTiles(int map_index)
Definition overworld.cc:708
auto mutable_overworld_map(int i)
Definition overworld.h:668
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.
uint64_t ComputeGraphicsConfigHash(int map_index)
Compute hash of graphics configuration for cache lookup.
std::vector< OverworldMap > overworld_maps_
Definition overworld.h:835
void CacheTileset(uint64_t config_hash, const std::vector< uint8_t > &tileset)
Cache tileset data for future reuse.
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 SaveOverworldMaps()
Save compressed map tile data to ROM.
std::array< std::vector< Sprite >, 3 > all_sprites_
Definition overworld.h:850
OverworldVersion cached_version_
Definition overworld.h:805
std::array< int, kNumOverworldMaps > map_pointers2_id
Definition overworld.h:855
absl::Status LoadOverworldMaps()
Load overworld map tile data.
Definition overworld.cc:860
std::vector< gfx::Tile16 > tiles16_
Definition overworld.h:841
absl::Status AutoDetectDiggableTiles()
absl::Status AssembleMap16Tiles()
Definition overworld.cc:649
absl::Status LoadSpritesFromMap(int sprite_start, int sprite_count, int sprite_index)
Load sprites from a specific map range.
std::array< std::vector< uint8_t >, kNumOverworldMaps > map_data_p1
Definition overworld.h:852
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:318
absl::Status SaveMap16Expanded()
Save expanded tile16 definitions (v1+ ROMs)
std::vector< OverworldExit > all_exits_
Definition overworld.h:838
std::array< int, kNumOverworldMaps > map_pointers2
Definition overworld.h:857
absl::StatusOr< uint16_t > GetTile16ForTile32(int index, int quadrant, int dimension, const uint32_t *map32address)
Definition overworld.cc:578
std::array< std::vector< uint8_t >, kNumOverworldMaps > map_data_p2
Definition overworld.h:853
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:816
absl::Status SaveMapOverlays()
Save interactive overlay data to ROM.
absl::Status AssembleMap32Tiles()
Definition overworld.cc:589
DiggableTiles diggable_tiles_
Definition overworld.h:851
static constexpr int kMaxBuiltMaps
Definition overworld.h:815
OverworldBlockset & GetMapTiles(int world_type)
Definition overworld.h:646
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:406
std::vector< OverworldEntrance > all_holes_
Definition overworld.h:837
struct destination destination
Room transition destination.
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#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:411
std::vector< uint8_t > HyruleMagicCompress(uint8_t const *const src, int const oldsize, int *const size, int const flag)
std::string HexByte(uint8_t byte, HexStringParams params)
Definition hex.cc:30
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
Definition log.h:115
std::string HexLong(uint32_t dword, HexStringParams params)
Definition hex.cc:52
std::vector< uint64_t > GetAllTile16(OverworldMapTiles &map_tiles_)
absl::StatusOr< Map32StorageLayout > ResolveMap32StorageLayout(const Rom &rom, const zelda3_version_pointers &version_constants)
Definition overworld.cc:46
absl::Status ValidateMap32DefinitionCount(size_t definition_count, const Map32StorageLayout &layout)
Definition overworld.cc:101
Zelda 3 specific classes and functions.
constexpr int kDiggableTilesBitfieldSize
constexpr int kAreaGfxIdPtr
Definition overworld.h:122
int GetOverworldMapParentIdExpanded()
absl::Status SaveEntrances(Rom *rom, const std::vector< OverworldEntrance > &entrances, bool expanded_entrances)
constexpr int OverworldCustomTileGFXGroupEnabled
constexpr int OverworldCustomAreaSpecificBGEnabled
constexpr int kOverworldTransitionPositionY
Definition overworld.h:146
constexpr int kNumMapsPerWorld
Definition overworld.h:268
constexpr int kOverworldSpriteset
Definition overworld.h:115
int GetMap32TileBRExpanded()
Definition overworld.h:209
int GetOverworldScreenChange3Expanded()
constexpr int kMap16ExpandedFlagPos
Definition overworld.h:170
constexpr int NumberOfMap16Ex
Definition overworld.h:244
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:151
constexpr int kOverworldMapDataOverflow
Definition overworld.h:156
int LegacyParentTableIndexForMap(int map_index)
Definition overworld.h:364
constexpr int kMap32BytesPerPackedGroup
Definition overworld.h:249
constexpr int kOverworldMapSizeHighByte
Definition overworld.h:137
absl::StatusOr< std::vector< OverworldItem > > LoadItems(Rom *rom, std::vector< OverworldMap > &overworld_maps)
constexpr int overworldSpritesBeginingExpanded
Definition overworld.h:172
constexpr int kNumTileTypes
Definition overworld.h:238
uint8_t ReadRomByteOr(const Rom &rom, int address, uint8_t fallback)
Definition overworld.h:299
constexpr int NumberOfMap32
Definition overworld.h:267
constexpr int kOverworldScreenSize
Definition overworld.h:148
constexpr int kOverworldScreenTileMapChangeByScreen4
Definition overworld.h:154
constexpr int kNumTile16Individual
Definition overworld.h:241
int GetMap32TileTRExpanded()
Definition overworld.h:199
constexpr int kSpecialWorldMapIdStart
constexpr int OverworldCustomMosaicArray
constexpr int kOverworldCustomDiggableTilesEnabled
constexpr int kMap16Tiles
Definition overworld.h:239
constexpr int overworldSpritesAgahnimExpanded
Definition overworld.h:174
int GetMap32TileBLExpanded()
Definition overworld.h:204
constexpr int OverworldCustomAnimatedGFXEnabled
constexpr int Map32DefinitionCapacityForStorageBytes(int storage_bytes)
Definition overworld.h:253
constexpr int OverworldCustomMainPaletteEnabled
constexpr int kNumOverworldMaps
Definition common.h:85
constexpr int OverworldCustomMainPaletteArray
constexpr int kOverworldSpecialSpritePaletteExpandedTemp
constexpr int kOverworldSpecialSpriteGfxGroupExpandedTemp
constexpr int kOverworldMusicBeginning
Definition overworld.h:125
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:147
constexpr int kOverworldMusicDarkWorld
Definition overworld.h:129
constexpr int kOverworldSpecialPalGroup
Definition overworld.h:117
constexpr int kOverworldScreenSizeForLoading
Definition overworld.h:149
constexpr int kOverworldSpritePaletteIds
Definition overworld.h:113
constexpr int overworldTilesType
Definition overworld.h:190
constexpr int transition_target_westExpanded
int GetOverworldScreenChange1Expanded()
absl::StatusOr< std::vector< OverworldExit > > LoadExits(Rom *rom)
constexpr int kMap32TileCountExpanded
Definition overworld.h:168
constexpr int kTransitionTargetWest
Definition overworld.h:159
constexpr int OverworldCustomASMHasBeenApplied
Definition common.h:89
constexpr int kOverworldMusicAgahnim
Definition overworld.h:128
constexpr int kOverworldSpritesZelda
Definition overworld.h:120
constexpr int kOverworldMapParentId
Definition overworld.h:145
constexpr int kOverworldCustomDiggableTilesArray
constexpr int kMap32ExpandedFlagPos
Definition overworld.h:169
constexpr int kMap32DefinitionsPerPackedGroup
Definition overworld.h:248
int GetOverworldTransitionPositionXExpanded()
@ kZSCustomV1
Basic features, expanded pointers.
@ kVanilla
0xFF in ROM, no ZScream ASM applied
bool IsSameOverworldWorld(int a, int b)
Definition overworld.h:352
constexpr int kOverworldPalettesScreenToSetNew
constexpr int kOverworldMusicMasterSword
Definition overworld.h:127
constexpr int kOverworldMusicZelda
Definition overworld.h:126
constexpr int transition_target_northExpanded
constexpr int NumberOfMap16
Definition overworld.h:243
constexpr int kOverworldMapSize
Definition overworld.h:134
constexpr int kOverworldScreenTileMapChangeByScreen2
Definition overworld.h:152
constexpr int kOverworldSpecialSpritePalette
constexpr int OverworldCustomAnimatedGFXArray
constexpr int kDarkWorldMapIdStart
OverworldRomProfile DetectOverworldRomProfile(const Rom &rom)
Definition overworld.h:311
absl::Status SaveHoles(Rom *rom, const std::vector< OverworldEntrance > &holes)
bool CanPersistLegacyScreenSize(int map_index)
Definition overworld.h:360
absl::StatusOr< std::vector< OverworldEntrance > > LoadHoles(Rom *rom)
constexpr int OverworldCustomMosaicEnabled
constexpr int kEssentialMapsPerWorld
Definition overworld.h:100
constexpr int kOverworldCompressedMapPos
Definition overworld.h:235
constexpr int kMaxDiggableTileId
constexpr int kOverworldSpritesBeginning
Definition overworld.h:118
constexpr int kOverworldSpecialSpriteGFXGroup
constexpr int kOverworldScreenTileMapChangeByScreen3
Definition overworld.h:153
constexpr int OverworldCustomTileGFXGroupArray
bool CanPersistLegacyMultiAreaMap(int map_index)
Definition overworld.h:356
uint8_t LegacyParentTableValueForMap(int parent_index)
Definition overworld.h:368
int Map32StorageBytesForProfile(const OverworldRomProfile &profile)
Definition overworld.h:284
constexpr int OverworldCustomSubscreenOverlayEnabled
constexpr int OverworldCustomAreaSpecificBGPalette
int GetOverworldMessagesExpanded()
constexpr int kOverworldSpritesAgahnim
Definition overworld.h:119
int LegacyScreenSizeTableIndexForMap(int map_index)
Definition overworld.h:372
constexpr int kTransitionTargetNorth
Definition overworld.h:158
constexpr int overworldSpritesZeldaExpanded
Definition overworld.h:173
constexpr int kOverworldCompressedOverflowPos
Definition overworld.h:236
int GetOverworldScreenChange2Expanded()
constexpr int kOverlayCodeStart
absl::Status SaveExits(Rom *rom, const std::vector< OverworldExit > &exits)
int GetMap16TilesExpanded()
Definition overworld.h:194
constexpr int kOverworldMapPaletteIds
Definition overworld.h:112
int GetOverworldTransitionPositionYExpanded()
constexpr int kOverworldSpecialGfxGroup
Definition overworld.h:116
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.
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