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