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 overworld_maps_[sibling].parent()));
422 RETURN_IF_ERROR(rom()->WriteByte(
423 kOverworldScreenSize + sibling,
424 static_cast<uint8_t>(overworld_maps_[sibling].area_size())));
425 }
427 // v1/v2: Update basic parent table
428 for (int sibling : all_affected) {
429 if (sibling < 0 || sibling >= kNumOverworldMaps)
430 continue;
431
432 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + sibling,
433 overworld_maps_[sibling].parent()));
434 RETURN_IF_ERROR(rom()->WriteByte(
435 kOverworldScreenSize + (sibling & 0x3F),
436 static_cast<uint8_t>(overworld_maps_[sibling].area_size())));
437 }
438 } else {
439 // Vanilla: Update parent and screen size tables
440 for (int sibling : all_affected) {
441 if (sibling < 0 || sibling >= kNumOverworldMaps)
442 continue;
443
444 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + sibling,
445 overworld_maps_[sibling].parent()));
446 RETURN_IF_ERROR(rom()->WriteByte(
447 kOverworldScreenSize + (sibling & 0x3F),
448 (overworld_maps_[sibling].area_size() == AreaSizeEnum::LargeArea)
449 ? 0x00
450 : 0x01));
451 }
452 }
453
454 LOG_DEBUG("Overworld",
455 "Configured %s area: parent=%d, old_siblings=%zu, new_siblings=%zu",
456 (size == AreaSizeEnum::LargeArea) ? "Large"
457 : (size == AreaSizeEnum::WideArea) ? "Wide"
458 : (size == AreaSizeEnum::TallArea) ? "Tall"
459 : "Small",
460 parent_index, old_siblings.size(), new_siblings.size());
461
462 return absl::OkStatus();
463}
464
465absl::StatusOr<uint16_t> Overworld::GetTile16ForTile32(
466 int index, int quadrant, int dimension, const uint32_t* map32address) {
468 auto arg1, rom()->ReadByte(map32address[dimension] + quadrant + (index)));
469 ASSIGN_OR_RETURN(auto arg2,
470 rom()->ReadWord(map32address[dimension] + (index) +
471 (quadrant <= 1 ? 4 : 5)));
472 return (uint16_t)(arg1 +
473 (((arg2 >> (quadrant % 2 == 0 ? 4 : 0)) & 0x0F) * 256));
474}
475
477 constexpr int kMap32TilesLength = 0x33F0;
478 int num_tile32 = kMap32TilesLength;
479 uint32_t map32address[4] = {
482
483 // Check if expanded tile32 data is actually present in ROM
484 // The flag position should contain 0x04 for vanilla, something else for
485 // expanded
486 uint8_t expanded_flag = rom()->data()[kMap32ExpandedFlagPos];
487 util::logf("Expanded tile32 flag: %d", expanded_flag);
488 if (expanded_flag != 0x04 ||
490 // ROM has expanded tile32 data - use expanded addresses
491 map32address[0] = version_constants().kMap32TileTL;
492 map32address[1] = kMap32TileTRExpanded;
493 map32address[2] = kMap32TileBLExpanded;
494 map32address[3] = kMap32TileBRExpanded;
495 num_tile32 = kMap32TileCountExpanded;
496 expanded_tile32_ = true;
497 }
498 // Otherwise use vanilla addresses (already set above)
499
500 // Loop through each 32x32 pixel tile in the rom
501 for (int i = 0; i < num_tile32; i += 6) {
502 // Loop through each quadrant of the 32x32 pixel tile.
503 for (int k = 0; k < 4; k++) {
504 // Generate the 16-bit tile for the current quadrant of the current
505 // 32x32 pixel tile.
507 uint16_t tl,
508 GetTile16ForTile32(i, k, (int)Dimension::map32TilesTL, map32address));
510 uint16_t tr,
511 GetTile16ForTile32(i, k, (int)Dimension::map32TilesTR, map32address));
513 uint16_t bl,
514 GetTile16ForTile32(i, k, (int)Dimension::map32TilesBL, map32address));
516 uint16_t br,
517 GetTile16ForTile32(i, k, (int)Dimension::map32TilesBR, map32address));
518
519 // Add the generated 16-bit tiles to the tiles32 vector.
520 tiles32_unique_.emplace_back(gfx::Tile32(tl, tr, bl, br));
521 }
522 }
523
524 map_tiles_.light_world.resize(0x200);
525 map_tiles_.dark_world.resize(0x200);
526 map_tiles_.special_world.resize(0x200);
527 for (int i = 0; i < 0x200; i++) {
528 map_tiles_.light_world[i].resize(0x200);
529 map_tiles_.dark_world[i].resize(0x200);
530 map_tiles_.special_world[i].resize(0x200);
531 }
532
533 return absl::OkStatus();
534}
535
537 int tpos = kMap16Tiles;
538 int num_tile16 = kNumTile16Individual;
539
540 // Check if expanded tile16 data is actually present in ROM
541 // The flag position should contain 0x0F for vanilla, something else for
542 // expanded
543 uint8_t expanded_flag = rom()->data()[kMap16ExpandedFlagPos];
544 util::logf("Expanded tile16 flag: %d", expanded_flag);
545 if (rom()->data()[kMap16ExpandedFlagPos] == 0x0F ||
547 // ROM has expanded tile16 data - use expanded addresses
548 tpos = kMap16TilesExpanded;
549 num_tile16 = NumberOfMap16Ex;
550 expanded_tile16_ = true;
551 }
552 // Otherwise use vanilla addresses (already set above)
553
554 for (int i = 0; i < num_tile16; i += 1) {
555 ASSIGN_OR_RETURN(auto t0_data, rom()->ReadWord(tpos));
556 gfx::TileInfo t0 = gfx::GetTilesInfo(t0_data);
557 tpos += 2;
558 ASSIGN_OR_RETURN(auto t1_data, rom()->ReadWord(tpos));
559 gfx::TileInfo t1 = gfx::GetTilesInfo(t1_data);
560 tpos += 2;
561 ASSIGN_OR_RETURN(auto t2_data, rom()->ReadWord(tpos));
562 gfx::TileInfo t2 = gfx::GetTilesInfo(t2_data);
563 tpos += 2;
564 ASSIGN_OR_RETURN(auto t3_data, rom()->ReadWord(tpos));
565 gfx::TileInfo t3 = gfx::GetTilesInfo(t3_data);
566 tpos += 2;
567 tiles16_.emplace_back(t0, t1, t2, t3);
568 }
569 return absl::OkStatus();
570}
571
572void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
573 OverworldBlockset& world) {
574 int position_x1 = (x * 2) + (sx * 32);
575 int position_y1 = (y * 2) + (sy * 32);
576 int position_x2 = (x * 2) + 1 + (sx * 32);
577 int position_y2 = (y * 2) + 1 + (sy * 32);
578 world[position_x1][position_y1] = tiles32_unique_[tpos].tile0_;
579 world[position_x2][position_y1] = tiles32_unique_[tpos].tile1_;
580 world[position_x1][position_y2] = tiles32_unique_[tpos].tile2_;
581 world[position_x2][position_y2] = tiles32_unique_[tpos].tile3_;
582}
583
585 switch (world_type) {
586 case 0:
587 return map_tiles_.light_world;
588 case 1:
589 return map_tiles_.dark_world;
590 default:
592 }
593}
594
595void Overworld::FillBlankMapTiles(int map_index) {
596 int world_type = 0;
597 if (map_index >= kDarkWorldMapIdStart &&
598 map_index < kSpecialWorldMapIdStart) {
599 world_type = 1;
600 } else if (map_index >= kSpecialWorldMapIdStart) {
601 world_type = 2;
602 }
603
604 int local_index = map_index % 64;
605 int sx = local_index % 8;
606 int sy = local_index / 8;
607
608 auto& world = SelectWorldBlockset(world_type);
609 // Fill the 32x32 tile16 region for this map with tile 0
610 for (int y = 0; y < 32; ++y) {
611 for (int x = 0; x < 32; ++x) {
612 world[(sx * 32) + x][(sy * 32) + y] = 0;
613 }
614 }
615}
616
617void Overworld::OrganizeMapTiles(std::vector<uint8_t>& bytes,
618 std::vector<uint8_t>& bytes2, int i, int sx,
619 int sy, int& ttpos) {
620 for (int y = 0; y < 16; y++) {
621 for (int x = 0; x < 16; x++) {
622 auto tidD = (uint16_t)((bytes2[ttpos] << 8) + bytes[ttpos]);
623 if (int tpos = tidD; tpos < tiles32_unique_.size()) {
624 if (i < kDarkWorldMapIdStart) {
625 AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.light_world);
626 } else if (i < kSpecialWorldMapIdStart && i >= kDarkWorldMapIdStart) {
627 AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.dark_world);
628 } else {
629 AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.special_world);
630 }
631 }
632 ttpos += 1;
633 }
634 }
635}
636
638 const auto get_ow_map_gfx_ptr = [this](int index, uint32_t map_ptr) {
639 int p = (rom()->data()[map_ptr + 2 + (3 * index)] << 16) +
640 (rom()->data()[map_ptr + 1 + (3 * index)] << 8) +
641 (rom()->data()[map_ptr + (3 * index)]);
642 return SnesToPc(p);
643 };
644
645 constexpr uint32_t kBaseLowest = 0x0FFFFF;
646 constexpr uint32_t kBaseHighest = 0x0F8000;
647
648 uint32_t lowest = kBaseLowest;
649 uint32_t highest = kBaseHighest;
650 int sx = 0;
651 int sy = 0;
652 int c = 0;
653 // Tail maps (0xA0-0xBF) require BOTH:
654 // 1. Feature flag enabled in settings
655 // 2. TailMapExpansion.asm patch applied to ROM (marker at 0x1423FF)
656 const bool allow_special_tail =
659
660 for (int i = 0; i < kNumOverworldMaps; i++) {
661 // Guard: skip building tail special maps unless expansion is available
662 if (!allow_special_tail &&
663 i >= kSpecialWorldMapIdStart + 0x20) { // 0xA0-0xBF
665 sx++;
666 if (sx >= 8) {
667 sy++;
668 sx = 0;
669 }
670 c++;
671 if (c >= 64) {
672 sx = 0;
673 sy = 0;
674 c = 0;
675 }
676 continue;
677 }
678
679 auto p1 = get_ow_map_gfx_ptr(
680 i, version_constants().kCompressedAllMap32PointersHigh);
681 auto p2 = get_ow_map_gfx_ptr(
682 i, version_constants().kCompressedAllMap32PointersLow);
683
684 int ttpos = 0;
685
686 bool pointers_valid =
687 (p1 > 0 && p2 > 0 && p1 < rom()->size() && p2 < rom()->size());
688 if (!pointers_valid) {
689 // Missing/invalid pointers -> use blank map tiles to avoid crashes
691 sx++;
692 if (sx >= 8) {
693 sy++;
694 sx = 0;
695 }
696 c++;
697 if (c >= 64) {
698 sx = 0;
699 sy = 0;
700 c = 0;
701 }
702 continue;
703 }
704
705 if (p1 >= highest)
706 highest = p1;
707 if (p2 >= highest)
708 highest = p2;
709
710 if (p1 <= lowest && p1 > kBaseHighest)
711 lowest = p1;
712 if (p2 <= lowest && p2 > kBaseHighest)
713 lowest = p2;
714
715 int size1, size2;
716 size_t max_size_p2 = rom()->size() - p2;
717 auto bytes =
718 gfx::HyruleMagicDecompress(rom()->data() + p2, &size1, 1, max_size_p2);
719 size_t max_size_p1 = rom()->size() - p1;
720 auto bytes2 =
721 gfx::HyruleMagicDecompress(rom()->data() + p1, &size2, 1, max_size_p1);
722
723 // If decompression fails, use blank tiles to keep map index usable
724 if (bytes.empty() || bytes2.empty()) {
726 } else {
727 OrganizeMapTiles(bytes, bytes2, i, sx, sy, ttpos);
728 }
729
730 sx++;
731 if (sx >= 8) {
732 sy++;
733 sx = 0;
734 }
735
736 c++;
737 if (c >= 64) {
738 sx = 0;
739 sy = 0;
740 c = 0;
741 }
742 }
743
744 return absl::OkStatus();
745}
746
748 auto size = tiles16_.size();
749
750 // Performance optimization: Only build essential maps initially
751 // Essential maps are the first few maps of each world that are commonly
752 // accessed
753#ifdef __EMSCRIPTEN__
754 // WASM: Fewer maps for faster initial load (rest load on-demand)
755 constexpr int kEssentialMapsPerWorld = 4;
756#else
757 constexpr int kEssentialMapsPerWorld = 16;
758#endif
759 constexpr int kLightWorldEssential = kEssentialMapsPerWorld;
760 constexpr int kDarkWorldEssential =
761 kDarkWorldMapIdStart + kEssentialMapsPerWorld;
762 constexpr int kSpecialWorldEssential =
763 kSpecialWorldMapIdStart + kEssentialMapsPerWorld;
764
766 "Building essential maps only (first %d maps per world) for faster "
767 "loading",
768 kEssentialMapsPerWorld);
769
770#ifdef __EMSCRIPTEN__
771 // WASM: Use sequential loading to avoid spawning excessive Web Workers
772 // and blocking the main thread. std::async creates new pthreads which
773 // become Web Workers, and future.wait() blocks the main thread which
774 // is dangerous in browsers.
775 for (int i = 0; i < kNumOverworldMaps; ++i) {
776 bool is_essential = false;
777
778 if (i < kLightWorldEssential) {
779 is_essential = true;
780 } else if (i >= kDarkWorldMapIdStart && i < kDarkWorldEssential) {
781 is_essential = true;
782 } else if (i >= kSpecialWorldMapIdStart && i < kSpecialWorldEssential) {
783 is_essential = true;
784 }
785
786 if (is_essential) {
787 int world_type = 0;
789 world_type = 1;
790 } else if (i >= kSpecialWorldMapIdStart) {
791 world_type = 2;
792 }
793
794 // CRITICAL: Set game_state_ BEFORE LoadAreaGraphics() because
795 // LoadSpritesBlocksets() uses game_state_ to determine static_graphics_[12-15]
796 overworld_maps_[i].set_game_state(game_state_);
797
798 // Apply large map child handling BEFORE computing hash
799 // Must match LoadAreaInfo() logic exactly for SW maps
800 auto* map = &overworld_maps_[i];
801 if (map->is_large_map() &&
803 if (map->parent() != i && !map->is_initialized()) {
804 if (i >= kSpecialWorldMapIdStart && i <= 0x8A && i != 0x88) {
805 // Zora's Domain children - also set sprite_graphics
806 map->set_sprite_graphics(0, 0x0E);
807 map->set_sprite_graphics(1, 0x0E);
808 map->set_sprite_graphics(2, 0x0E);
809 map->set_area_graphics(
811 (map->parent() - kSpecialWorldMapIdStart)]);
812 map->set_area_palette((*rom_)[kOverworldSpecialPalGroup + 1]);
813 } else if (i == 0x88) {
814 map->set_area_graphics(0x51);
815 map->set_area_palette(0x00);
816 } else if (i < kSpecialWorldMapIdStart) {
817 // LW/DW large map child - use parent's graphics
818 map->set_area_graphics((*rom_)[kAreaGfxIdPtr + map->parent()]);
819 map->set_area_palette(
820 (*rom_)[kOverworldMapPaletteIds + map->parent()]);
821 }
822 // Note: Other SW maps (>0x8A) keep their LoadAreaInfo values
823 }
824 }
825
826 // Reuse cached tilesets to reduce load time on WASM
827 overworld_maps_[i].LoadAreaGraphics();
828 uint64_t config_hash = ComputeGraphicsConfigHash(i);
829 const std::vector<uint8_t>* cached_tileset =
830 GetCachedTileset(config_hash);
831 RETURN_IF_ERROR(overworld_maps_[i].BuildMapWithCache(
832 size, game_state_, world_type, tiles16_, GetMapTiles(world_type),
833 cached_tileset));
834 if (!cached_tileset) {
836 }
837 } else {
838 overworld_maps_[i].SetNotBuilt();
839 }
840 }
841#else
842 // Native: Use parallel loading with std::async for faster performance
843 std::vector<std::future<absl::Status>> futures;
844
845 // Build essential maps only
846 for (int i = 0; i < kNumOverworldMaps; ++i) {
847 bool is_essential = false;
848
849 // Check if this is an essential map
850 if (i < kLightWorldEssential) {
851 is_essential = true;
852 } else if (i >= kDarkWorldMapIdStart && i < kDarkWorldEssential) {
853 is_essential = true;
854 } else if (i >= kSpecialWorldMapIdStart && i < kSpecialWorldEssential) {
855 is_essential = true;
856 }
857
858 if (is_essential) {
859 int world_type = 0;
861 world_type = 1;
862 } else if (i >= kSpecialWorldMapIdStart) {
863 world_type = 2;
864 }
865
866 auto task_function = [this, i, size, world_type]() {
867 return overworld_maps_[i].BuildMap(size, game_state_, world_type,
868 tiles16_, GetMapTiles(world_type));
869 };
870 futures.emplace_back(std::async(std::launch::async, task_function));
871 } else {
872 // Mark non-essential maps as not built yet
873 overworld_maps_[i].SetNotBuilt();
874 }
875 }
876
877 // Wait for essential maps to complete
878 for (auto& future : futures) {
879 future.wait();
880 RETURN_IF_ERROR(future.get());
881 }
882#endif
883
884 util::logf("Essential maps built. Remaining maps will be built on-demand.");
885 return absl::OkStatus();
886}
887
888absl::Status Overworld::EnsureMapBuilt(int map_index) {
889 if (map_index < 0 || map_index >= kNumOverworldMaps) {
890 return absl::InvalidArgumentError("Invalid map index");
891 }
892
893 // Tail maps (0xA0-0xBF) require BOTH:
894 // 1. Feature flag enabled in settings
895 // 2. TailMapExpansion.asm patch applied to ROM (marker at 0x1423FF)
896 const bool allow_special_tail =
899 if (!allow_special_tail &&
900 map_index >= kSpecialWorldMapIdStart + 0x20) { // 0xA0-0xBF
901 // Do not attempt to build disabled special-tail maps; keep them blank-safe.
902 // This prevents pointer table corruption from attempting to access
903 // non-existent entries beyond vanilla's 160-entry limit.
904 FillBlankMapTiles(map_index);
905 return absl::OkStatus();
906 }
907
908 // Check if map is already built
909 if (overworld_maps_[map_index].is_built()) {
910 // Move to front of LRU (most recently used)
911 auto it =
912 std::find(built_map_lru_.begin(), built_map_lru_.end(), map_index);
913 if (it != built_map_lru_.end()) {
914 built_map_lru_.erase(it);
915 }
916 built_map_lru_.push_front(map_index);
917 return absl::OkStatus();
918 }
919
920 // Evict oldest maps if cache is full (LRU eviction)
921 while (static_cast<int>(built_map_lru_.size()) >= kMaxBuiltMaps) {
922 int oldest_map = built_map_lru_.back();
923 built_map_lru_.pop_back();
924 // Invalidate graphics cache for evicted map to prevent stale tileset refs
925 InvalidateMapCache(oldest_map);
926 // Destroy the oldest map to free memory
927 overworld_maps_[oldest_map].Destroy();
928 }
929
930 // Build the map on-demand
931 auto size = tiles16_.size();
932 int world_type = 0;
933 if (map_index >= kDarkWorldMapIdStart &&
934 map_index < kSpecialWorldMapIdStart) {
935 world_type = 1;
936 } else if (map_index >= kSpecialWorldMapIdStart) {
937 world_type = 2;
938 }
939
940 // CRITICAL: Set game_state_ BEFORE LoadAreaGraphics() because
941 // LoadSpritesBlocksets() uses game_state_ to determine static_graphics_[12-15]
942 overworld_maps_[map_index].set_game_state(game_state_);
943
944 // Apply large map child handling BEFORE computing hash
945 // This mirrors the logic in BuildMapWithCache that modifies area_graphics_
946 // for large map children in vanilla ROMs - must happen before hash
947 auto* map = &overworld_maps_[map_index];
948 if (map->is_large_map() && cached_version_ == OverworldVersion::kVanilla) {
949 if (map->parent() != map_index && !map->is_initialized()) {
950 // Large map child in vanilla ROM - apply special graphics handling
951 // Must match LoadAreaInfo() logic exactly for SW maps
952 if (map_index >= kSpecialWorldMapIdStart && map_index <= 0x8A &&
953 map_index != 0x88) {
954 // Zora's Domain children - also set sprite_graphics
955 map->set_sprite_graphics(0, 0x0E);
956 map->set_sprite_graphics(1, 0x0E);
957 map->set_sprite_graphics(2, 0x0E);
958 map->set_area_graphics(
960 (map->parent() - kSpecialWorldMapIdStart)]);
961 map->set_area_palette((*rom_)[kOverworldSpecialPalGroup + 1]);
962 } else if (map_index == 0x88) {
963 map->set_area_graphics(0x51);
964 map->set_area_palette(0x00);
965 } else if (map_index < kSpecialWorldMapIdStart) {
966 // LW/DW large map child - use parent's graphics
967 map->set_area_graphics((*rom_)[kAreaGfxIdPtr + map->parent()]);
968 map->set_area_palette((*rom_)[kOverworldMapPaletteIds + map->parent()]);
969 }
970 // Note: Other SW maps (>0x8A) keep their LoadAreaInfo values
971 }
972 }
973
974 // Prepare graphics config to check cache (must call LoadAreaGraphics first)
975 overworld_maps_[map_index].LoadAreaGraphics();
976 uint64_t config_hash = ComputeGraphicsConfigHash(map_index);
977
978 // Try to use cached tileset for faster build
979 const std::vector<uint8_t>* cached_tileset = GetCachedTileset(config_hash);
980
981 auto status = overworld_maps_[map_index].BuildMapWithCache(
982 size, game_state_, world_type, tiles16_, GetMapTiles(world_type),
983 cached_tileset);
984
985 if (status.ok()) {
986 // Cache the tileset if we didn't use cached data
987 if (!cached_tileset) {
988 CacheTileset(config_hash, overworld_maps_[map_index].current_graphics());
989 }
990 // Add to front of LRU cache
991 built_map_lru_.push_front(map_index);
992 }
993 return status;
994}
995
997 for (int i = 0; i < kNumTileTypes; ++i) {
1000 }
1001}
1002
1004 // Compute a comprehensive hash that distinguishes tileset configurations
1005 // across different worlds (LW/DW/SW) and map types
1006 const auto* map = &overworld_maps_[map_index];
1007 uint64_t hash = 0;
1008
1009 // CRITICAL: Include explicit world type to absolutely prevent cross-world sharing
1010 // LW=0, DW=1, SW=2 - this is the strongest disambiguation
1011 int world_type = 0;
1012 if (map_index >= kDarkWorldMapIdStart &&
1013 map_index < kSpecialWorldMapIdStart) {
1014 world_type = 1;
1015 } else if (map_index >= kSpecialWorldMapIdStart) {
1016 world_type = 2;
1017 }
1018 hash ^= static_cast<uint64_t>(world_type) << 62;
1019 hash *= 0x517cc1b727220a95ULL;
1020
1021 // Hash the first 12 static graphics IDs (main blocksets)
1022 // Note: static_graphics_[12-15] are sprite sheets loaded using game_state_
1023 // which may be stale at hash time, so we handle them separately below
1024 for (int i = 0; i < 12; ++i) {
1025 hash ^= static_cast<uint64_t>(map->static_graphics(i)) << ((i % 8) * 8);
1026 hash *= 0x517cc1b727220a95ULL; // FNV-like mixing
1027 }
1028
1029 // Include game_state_ to distinguish sprite sheet configurations
1030 // static_graphics_[12-15] are loaded using sprite_graphics_[game_state_]
1031 // which varies by game state (Beginning, Zelda Rescued, Master Sword, Agahnim)
1032 hash ^= static_cast<uint64_t>(game_state_) << 60;
1033 hash *= 0x517cc1b727220a95ULL;
1034
1035 // Include ALL sprite_graphics values since SW maps (especially Zora's Domain)
1036 // have different sprite graphics (0x0E) than LW/DW maps
1037 for (int i = 0; i < 3; ++i) {
1038 hash ^= static_cast<uint64_t>(map->sprite_graphics(i)) << (52 + i * 4);
1039 hash *= 0x517cc1b727220a95ULL;
1040 }
1041
1042 // Include area_graphics for complete config
1043 hash ^= static_cast<uint64_t>(map->area_graphics()) << 48;
1044 hash *= 0x517cc1b727220a95ULL;
1045
1046 // Include main_gfx_id to distinguish between worlds
1047 // LW=0x20, DW=0x21, SW=0x20/0x24 - prevents cache collisions between LW/SW
1048 hash ^= static_cast<uint64_t>(map->main_gfx_id()) << 56;
1049 hash *= 0x517cc1b727220a95ULL;
1050
1051 // Include parent ID to prevent cache collisions between sibling maps
1052 hash ^= static_cast<uint64_t>(map->parent()) << 40;
1053 hash *= 0x517cc1b727220a95ULL;
1054
1055 // CRITICAL: Include map index for Special World disambiguation
1056 // SW maps have many unique hardcoded configurations based on index:
1057 // 0x80 (Master Sword), 0x88/0x93 (Triforce), 0x94, 0x95, 0x96, 0x9C
1058 // These must not share cached tilesets even if other properties match
1059 hash ^= static_cast<uint64_t>(map_index) << 8;
1060 hash *= 0x517cc1b727220a95ULL;
1061
1062 // Include main_palette to distinguish world palettes (LW=0, DW=1, DM=2/3, etc.)
1063 hash ^= static_cast<uint64_t>(map->main_palette()) << 24;
1064 hash *= 0x517cc1b727220a95ULL;
1065
1066 // Include animated_gfx to distinguish between Death Mountain (0x59) and normal (0x5B)
1067 hash ^= static_cast<uint64_t>(map->animated_gfx()) << 16;
1068 hash *= 0x517cc1b727220a95ULL;
1069
1070 // Include area_palette for final disambiguation
1071 hash ^= static_cast<uint64_t>(map->area_palette()) << 32;
1072 hash *= 0x517cc1b727220a95ULL;
1073
1074 // Include subscreen overlay for visual consistency (fog, curtains, sky, lava)
1075 // Different overlays can affect which tiles are visible/rendered
1076 hash ^= static_cast<uint64_t>(map->subscreen_overlay());
1077 hash *= 0x517cc1b727220a95ULL;
1078
1079 return hash;
1080}
1081
1082const std::vector<uint8_t>* Overworld::GetCachedTileset(uint64_t config_hash) {
1083 auto it = gfx_config_cache_.find(config_hash);
1084 if (it != gfx_config_cache_.end()) {
1085 it->second.reference_count++;
1086 return &it->second.current_gfx;
1087 }
1088 return nullptr;
1089}
1090
1091void Overworld::CacheTileset(uint64_t config_hash,
1092 const std::vector<uint8_t>& tileset) {
1093 // Limit cache size by evicting least-used entries
1094 while (gfx_config_cache_.size() >= kMaxCachedConfigs) {
1095 // Find entry with lowest reference count
1096 auto min_it = gfx_config_cache_.begin();
1097 for (auto it = gfx_config_cache_.begin(); it != gfx_config_cache_.end();
1098 ++it) {
1099 if (it->second.reference_count < min_it->second.reference_count) {
1100 min_it = it;
1101 }
1102 }
1103 gfx_config_cache_.erase(min_it);
1104 }
1105
1106 // Cache the tileset
1107 gfx_config_cache_[config_hash] = {tileset, 1};
1108}
1109
1111 if (map_index < 0 || map_index >= kNumOverworldMaps) {
1112 return;
1113 }
1114
1115 // Compute the hash for this map's graphics configuration and remove it
1116 uint64_t config_hash = ComputeGraphicsConfigHash(map_index);
1117 gfx_config_cache_.erase(config_hash);
1118
1119 // Also mark the map as needing rebuild
1120 if (static_cast<size_t>(map_index) < overworld_maps_.size()) {
1121 overworld_maps_[map_index].SetNotBuilt();
1122 }
1123}
1124
1126 if (map_index < 0 || map_index >= kNumOverworldMaps) {
1127 return;
1128 }
1129
1130 auto* map = mutable_overworld_map(map_index);
1131 if (!map)
1132 return;
1133
1134 // Get parent and determine all sibling maps
1135 int parent_id = map->parent();
1136 std::vector<int> siblings;
1137
1139
1140 if (use_v3_sizes) {
1141 // v3: Use area_size enum
1142 switch (map->area_size()) {
1144 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
1145 break;
1147 siblings = {parent_id, parent_id + 1};
1148 break;
1150 siblings = {parent_id, parent_id + 8};
1151 break;
1152 default:
1153 siblings = {map_index}; // Small area - just this map
1154 break;
1155 }
1156 } else {
1157 // Vanilla/v1/v2: Use large_map flag
1158 if (map->is_large_map()) {
1159 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
1160 } else {
1161 siblings = {map_index}; // Small area - just this map
1162 }
1163 }
1164
1165 // Invalidate cache for all siblings
1166 for (int sibling : siblings) {
1167 if (sibling >= 0 && sibling < kNumOverworldMaps) {
1168 InvalidateMapCache(sibling);
1169 }
1170 }
1171}
1172
1174 // Determine sprite table locations based on actual ASM version in ROM
1175
1176#ifdef __EMSCRIPTEN__
1177 // WASM: Sequential loading to avoid Web Worker explosion
1184 } else {
1188 }
1189#else
1190 // Native: Parallel loading for performance
1191 std::vector<std::future<absl::Status>> futures;
1192
1194 // v3: Use expanded sprite tables
1195 futures.emplace_back(std::async(std::launch::async, [this]() {
1197 }));
1198 futures.emplace_back(std::async(std::launch::async, [this]() {
1200 }));
1201 futures.emplace_back(std::async(std::launch::async, [this]() {
1203 }));
1204 } else {
1205 // Vanilla/v2: Use original sprite tables
1206 futures.emplace_back(std::async(std::launch::async, [this]() {
1208 }));
1209 futures.emplace_back(std::async(std::launch::async, [this]() {
1211 }));
1212 futures.emplace_back(std::async(std::launch::async, [this]() {
1214 }));
1215 }
1216
1217 for (auto& future : futures) {
1218 future.wait();
1219 RETURN_IF_ERROR(future.get());
1220 }
1221#endif
1222 return absl::OkStatus();
1223}
1224
1225absl::Status Overworld::LoadSpritesFromMap(int sprites_per_gamestate_ptr,
1226 int num_maps_per_gamestate,
1227 int game_state) {
1228 for (int i = 0; i < num_maps_per_gamestate; i++) {
1229 if (map_parent_[i] != i)
1230 continue;
1231
1232 int current_spr_ptr = sprites_per_gamestate_ptr + (i * 2);
1233 ASSIGN_OR_RETURN(auto word_addr, rom()->ReadWord(current_spr_ptr));
1234 int sprite_address = SnesToPc((0x09 << 0x10) | word_addr);
1235 while (true) {
1236 ASSIGN_OR_RETURN(uint8_t b1, rom()->ReadByte(sprite_address));
1237 ASSIGN_OR_RETURN(uint8_t b2, rom()->ReadByte(sprite_address + 1));
1238 ASSIGN_OR_RETURN(uint8_t b3, rom()->ReadByte(sprite_address + 2));
1239 if (b1 == 0xFF)
1240 break;
1241
1242 int editor_map_index = i;
1243 if (game_state != 0) {
1244 if (editor_map_index >= 128)
1245 editor_map_index -= 128;
1246 else if (editor_map_index >= 64)
1247 editor_map_index -= 64;
1248 }
1249 int mapY = (editor_map_index / 8);
1250 int mapX = (editor_map_index % 8);
1251
1252 int realX = ((b2 & 0x3F) * 16) + mapX * 512;
1253 int realY = ((b1 & 0x3F) * 16) + mapY * 512;
1254 all_sprites_[game_state].emplace_back(
1255 *overworld_maps_[i].mutable_current_graphics(), (uint8_t)i, b3,
1256 (uint8_t)(b2 & 0x3F), (uint8_t)(b1 & 0x3F), realX, realY);
1257 all_sprites_[game_state].back().Draw();
1258
1259 sprite_address += 3;
1260 }
1261 }
1262
1263 return absl::OkStatus();
1264}
1265
1291
1293 util::logf("Saving Overworld Maps");
1294
1295 if (tiles32_list_.size() < NumberOfMap32) {
1297 }
1298
1299 // Initialize map pointers
1300 std::fill(map_pointers1_id.begin(), map_pointers1_id.end(), -1);
1301 std::fill(map_pointers2_id.begin(), map_pointers2_id.end(), -1);
1302
1303 // Compress and save each map
1305 for (int i = 0; i < kNumOverworldMaps; i++) {
1306 std::vector<uint8_t> single_map_1(512);
1307 std::vector<uint8_t> single_map_2(512);
1308
1309 // Copy tiles32 data to single_map_1 and single_map_2
1310 int npos = 0;
1311 for (int y = 0; y < 16; y++) {
1312 for (int x = 0; x < 16; x++) {
1313 auto packed = tiles32_list_[npos + (i * 256)];
1314 single_map_1[npos] = packed & 0xFF; // Lower 8 bits
1315 single_map_2[npos] = (packed >> 8) & 0xFF; // Next 8 bits
1316 npos++;
1317 }
1318 }
1319
1320 int size_a, size_b;
1321 // Compress single_map_1 and single_map_2
1322 auto a = gfx::HyruleMagicCompress(single_map_1.data(), 256, &size_a, 1);
1323 auto b = gfx::HyruleMagicCompress(single_map_2.data(), 256, &size_b, 1);
1324 if (a.empty() || b.empty()) {
1325 return absl::AbortedError("Error compressing map gfx.");
1326 }
1327
1328 // Save compressed data and pointers
1329 map_data_p1[i] = std::vector<uint8_t>(size_a);
1330 map_data_p2[i] = std::vector<uint8_t>(size_b);
1331
1332 if ((pos + size_a) >= 0x5FE70 && (pos + size_a) <= 0x60000) {
1333 pos = 0x60000;
1334 }
1335
1336 if ((pos + size_a) >= 0x6411F && (pos + size_a) <= 0x70000) {
1337 util::logf("Pos set to overflow region for map %s at %s",
1338 std::to_string(i), util::HexLong(pos));
1339 pos = kOverworldMapDataOverflow; // 0x0F8780;
1340 }
1341
1342 const auto compare_array = [](const std::vector<uint8_t>& array1,
1343 const std::vector<uint8_t>& array2) -> bool {
1344 if (array1.size() != array2.size()) {
1345 return false;
1346 }
1347
1348 for (size_t i = 0; i < array1.size(); i++) {
1349 if (array1[i] != array2[i]) {
1350 return false;
1351 }
1352 }
1353
1354 return true;
1355 };
1356
1357 for (int j = 0; j < i; j++) {
1358 if (compare_array(a, map_data_p1[j])) {
1359 // Reuse pointer id j for P1 (a)
1360 map_pointers1_id[i] = j;
1361 }
1362
1363 if (compare_array(b, map_data_p2[j])) {
1364 map_pointers2_id[i] = j;
1365 // Reuse pointer id j for P2 (b)
1366 }
1367 }
1368
1369 if (map_pointers1_id[i] == -1) {
1370 // Save compressed data and pointer for map1
1371 std::copy(a.begin(), a.end(), map_data_p1[i].begin());
1372 int snes_pos = PcToSnes(pos);
1373 map_pointers1[i] = snes_pos;
1374 util::logf("Saving map pointers1 and compressed data for map %s at %s",
1375 util::HexByte(i), util::HexLong(snes_pos));
1376 RETURN_IF_ERROR(rom()->WriteLong(
1377 version_constants().kCompressedAllMap32PointersLow + (3 * i),
1378 snes_pos));
1379 RETURN_IF_ERROR(rom()->WriteVector(pos, a));
1380 pos += size_a;
1381 } else {
1382 // Save pointer for map1
1383 int snes_pos = map_pointers1[map_pointers1_id[i]];
1384 util::logf("Saving map pointers1 for map %s at %s", util::HexByte(i),
1385 util::HexLong(snes_pos));
1386 RETURN_IF_ERROR(rom()->WriteLong(
1387 version_constants().kCompressedAllMap32PointersLow + (3 * i),
1388 snes_pos));
1389 }
1390
1391 if ((pos + b.size()) >= 0x5FE70 && (pos + b.size()) <= 0x60000) {
1392 pos = 0x60000;
1393 }
1394
1395 if ((pos + b.size()) >= 0x6411F && (pos + b.size()) <= 0x70000) {
1396 util::logf("Pos set to overflow region for map %s at %s",
1397 util::HexByte(i), util::HexLong(pos));
1399 }
1400
1401 if (map_pointers2_id[i] == -1) {
1402 // Save compressed data and pointer for map2
1403 std::copy(b.begin(), b.end(), map_data_p2[i].begin());
1404 int snes_pos = PcToSnes(pos);
1405 map_pointers2[i] = snes_pos;
1406 util::logf("Saving map pointers2 and compressed data for map %s at %s",
1407 util::HexByte(i), util::HexLong(snes_pos));
1408 RETURN_IF_ERROR(rom()->WriteLong(
1409 version_constants().kCompressedAllMap32PointersHigh + (3 * i),
1410 snes_pos));
1411 RETURN_IF_ERROR(rom()->WriteVector(pos, b));
1412 pos += size_b;
1413 } else {
1414 // Save pointer for map2
1415 int snes_pos = map_pointers2[map_pointers2_id[i]];
1416 util::logf("Saving map pointers2 for map %s at %s", util::HexByte(i),
1417 util::HexLong(snes_pos));
1418 RETURN_IF_ERROR(rom()->WriteLong(
1419 version_constants().kCompressedAllMap32PointersHigh + (3 * i),
1420 snes_pos));
1421 }
1422 }
1423
1424 // Check if too many maps data
1426 util::logf("Too many maps data %s", util::HexLong(pos));
1427 return absl::AbortedError("Too many maps data " + std::to_string(pos));
1428 }
1429
1431 return absl::OkStatus();
1432}
1433
1435 util::logf("Saving Large Maps");
1436
1437 // Check if this is a v3+ ROM to use expanded transition system
1438 bool use_expanded_transitions =
1440
1441 if (use_expanded_transitions) {
1442 // Use new v3+ complex transition system with neighbor awareness
1443 return SaveLargeMapsExpanded();
1444 }
1445
1446 // Original vanilla/v2 logic preserved
1447 std::vector<uint8_t> checked_map;
1448
1449 for (int i = 0; i < kNumMapsPerWorld; ++i) {
1450 int y_pos = i / 8;
1451 int x_pos = i % 8;
1452 int parent_y_pos = overworld_maps_[i].parent() / 8;
1453 int parent_x_pos = overworld_maps_[i].parent() % 8;
1454
1455 // Always write the map parent since it should not matter
1456 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + i,
1457 overworld_maps_[i].parent()))
1458
1459 if (std::find(checked_map.begin(), checked_map.end(), i) !=
1460 checked_map.end()) {
1461 continue;
1462 }
1463
1464 // If it's large then save parent pos *
1465 // 0x200 otherwise pos * 0x200
1466 if (overworld_maps_[i].is_large_map()) {
1467 const uint8_t large_map_offsets[] = {0, 1, 8, 9};
1468 for (const auto& offset : large_map_offsets) {
1469 // Check 1
1470 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i + offset, 0x20));
1471 // Check 2
1473 rom()->WriteByte(kOverworldMapSizeHighByte + i + offset, 0x03));
1474 // Check 3
1476 rom()->WriteByte(kOverworldScreenSize + i + offset, 0x00));
1478 rom()->WriteByte(kOverworldScreenSize + i + offset + 64, 0x00));
1479 // Check 4
1480 RETURN_IF_ERROR(rom()->WriteByte(
1481 kOverworldScreenSizeForLoading + i + offset, 0x04));
1482 RETURN_IF_ERROR(rom()->WriteByte(
1484 0x04));
1486 offset + kSpecialWorldMapIdStart,
1487 0x04));
1488 }
1489
1490 // Check 5 and 6 - transition targets
1492 rom()->WriteShort(kTransitionTargetNorth + (i * 2),
1493 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
1495 rom()->WriteShort(kTransitionTargetWest + (i * 2),
1496 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
1497
1499 rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 2,
1500 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
1502 rom()->WriteShort(kTransitionTargetWest + (i * 2) + 2,
1503 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
1504
1506 rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 16,
1507 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
1509 rom()->WriteShort(kTransitionTargetWest + (i * 2) + 16,
1510 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
1511
1513 rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 18,
1514 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
1516 rom()->WriteShort(kTransitionTargetWest + (i * 2) + 18,
1517 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
1518
1519 // Check 7 and 8 - transition positions
1520 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionX + (i * 2),
1521 (parent_x_pos * 0x200)));
1522 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionY + (i * 2),
1523 (parent_y_pos * 0x200)));
1524
1526 rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 02,
1527 (parent_x_pos * 0x200)));
1529 rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 02,
1530 (parent_y_pos * 0x200)));
1531
1533 rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 16,
1534 (parent_x_pos * 0x200)));
1536 rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 16,
1537 (parent_y_pos * 0x200)));
1538
1540 rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 18,
1541 (parent_x_pos * 0x200)));
1543 rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 18,
1544 (parent_y_pos * 0x200)));
1545
1546 // Check 9 - simple vanilla large area transitions
1547 RETURN_IF_ERROR(rom()->WriteShort(
1548 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 00, 0x0060));
1549 RETURN_IF_ERROR(rom()->WriteShort(
1550 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 02, 0x0060));
1551
1552 // If parentX == 0 then lower submaps == 0x0060 too
1553 if (parent_x_pos == 0) {
1554 RETURN_IF_ERROR(rom()->WriteShort(
1555 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 16, 0x0060));
1556 RETURN_IF_ERROR(rom()->WriteShort(
1557 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 18, 0x0060));
1558 } else {
1559 // Otherwise lower submaps == 0x1060
1560 RETURN_IF_ERROR(rom()->WriteShort(
1561 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 16, 0x1060));
1562 RETURN_IF_ERROR(rom()->WriteShort(
1563 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 18, 0x1060));
1564
1565 // If the area to the left is a large map, we don't need to add an
1566 // offset to it. otherwise leave it the same. Just to make sure where
1567 // don't try to read outside of the array.
1568 if ((i - 1) >= 0) {
1569 // If the area to the left is a large area.
1570 if (overworld_maps_[i - 1].is_large_map()) {
1571 // If the area to the left is the bottom right of a large area.
1572 if (overworld_maps_[i - 1].large_index() == 1) {
1573 RETURN_IF_ERROR(rom()->WriteShort(
1575 0x0060));
1576 }
1577 }
1578 }
1579 }
1580
1581 // Always 0x0080
1582 RETURN_IF_ERROR(rom()->WriteShort(
1583 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 00, 0x0080));
1584 RETURN_IF_ERROR(rom()->WriteShort(
1585 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 2, 0x0080));
1586 // Lower always 0x1080
1587 RETURN_IF_ERROR(rom()->WriteShort(
1588 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 16, 0x1080));
1589 RETURN_IF_ERROR(rom()->WriteShort(
1590 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 18, 0x1080));
1591
1592 // If the area to the right is a large map, we don't need to add an offset
1593 // to it. otherwise leave it the same. Just to make sure where don't try
1594 // to read outside of the array.
1595 if ((i + 2) < 64) {
1596 // If the area to the right is a large area.
1597 if (overworld_maps_[i + 2].is_large_map()) {
1598 // If the area to the right is the top left of a large area.
1599 if (overworld_maps_[i + 2].large_index() == 0) {
1600 RETURN_IF_ERROR(rom()->WriteShort(
1601 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 18, 0x0080));
1602 }
1603 }
1604 }
1605
1606 // Always 0x1800
1607 RETURN_IF_ERROR(rom()->WriteShort(
1608 kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x1800));
1609 RETURN_IF_ERROR(rom()->WriteShort(
1610 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 16, 0x1800));
1611 // Right side is always 0x1840
1612 RETURN_IF_ERROR(rom()->WriteShort(
1613 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 2, 0x1840));
1614 RETURN_IF_ERROR(rom()->WriteShort(
1615 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 18, 0x1840));
1616
1617 // If the area above is a large map, we don't need to add an offset to it.
1618 // otherwise leave it the same.
1619 // Just to make sure where don't try to read outside of the array.
1620 if (i - 8 >= 0) {
1621 // If the area just above us is a large area.
1622 if (overworld_maps_[i - 8].is_large_map()) {
1623 // If the area just above us is the bottom left of a large area.
1624 if (overworld_maps_[i - 8].large_index() == 2) {
1625 RETURN_IF_ERROR(rom()->WriteShort(
1626 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 02, 0x1800));
1627 }
1628 }
1629 }
1630
1631 // Always 0x2000
1632 RETURN_IF_ERROR(rom()->WriteShort(
1633 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 00, 0x2000));
1634 RETURN_IF_ERROR(rom()->WriteShort(
1635 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 16, 0x2000));
1636 // Right side always 0x2040
1637 RETURN_IF_ERROR(rom()->WriteShort(
1638 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 2, 0x2040));
1639 RETURN_IF_ERROR(rom()->WriteShort(
1640 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 18, 0x2040));
1641
1642 // If the area below is a large map, we don't need to add an offset to it.
1643 // otherwise leave it the same.
1644 // Just to make sure where don't try to read outside of the array.
1645 if (i + 16 < 64) {
1646 // If the area just below us is a large area.
1647 if (overworld_maps_[i + 16].is_large_map()) {
1648 // If the area just below us is the top left of a large area.
1649 if (overworld_maps_[i + 16].large_index() == 0) {
1650 RETURN_IF_ERROR(rom()->WriteShort(
1651 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 18, 0x2000));
1652 }
1653 }
1654 }
1655
1656 checked_map.emplace_back(i);
1657 checked_map.emplace_back((i + 1));
1658 checked_map.emplace_back((i + 8));
1659 checked_map.emplace_back((i + 9));
1660
1661 } else {
1662 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i, 0x00));
1663 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSizeHighByte + i, 0x01));
1664
1665 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, 0x01));
1666 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i + 64, 0x01));
1667
1669 rom()->WriteByte(kOverworldScreenSizeForLoading + i, 0x02));
1670 RETURN_IF_ERROR(rom()->WriteByte(
1672 RETURN_IF_ERROR(rom()->WriteByte(
1674
1675 RETURN_IF_ERROR(rom()->WriteShort(
1676 kOverworldScreenTileMapChangeByScreen1 + (i * 2), 0x0060));
1677
1678 // If the area to the left is a large map, we don't need to add an offset
1679 // to it. otherwise leave it the same.
1680 // Just to make sure where don't try to read outside of the array.
1681 if (i - 1 >= 0 && parent_x_pos != 0) {
1682 if (overworld_maps_[i - 1].is_large_map()) {
1683 if (overworld_maps_[i - 1].large_index() == 3) {
1684 RETURN_IF_ERROR(rom()->WriteShort(
1685 kOverworldScreenTileMapChangeByScreen1 + (i * 2), 0xF060));
1686 }
1687 }
1688 }
1689
1690 RETURN_IF_ERROR(rom()->WriteShort(
1691 kOverworldScreenTileMapChangeByScreen2 + (i * 2), 0x0040));
1692
1693 if (i + 1 < 64 && parent_x_pos != 7) {
1694 if (overworld_maps_[i + 1].is_large_map()) {
1695 if (overworld_maps_[i + 1].large_index() == 2) {
1696 RETURN_IF_ERROR(rom()->WriteShort(
1697 kOverworldScreenTileMapChangeByScreen2 + (i * 2), 0xF040));
1698 }
1699 }
1700 }
1701
1702 RETURN_IF_ERROR(rom()->WriteShort(
1703 kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x1800));
1704
1705 // If the area above is a large map, we don't need to add an offset to it.
1706 // otherwise leave it the same.
1707 // Just to make sure where don't try to read outside of the array.
1708 if (i - 8 >= 0) {
1709 // If the area just above us is a large area.
1710 if (overworld_maps_[i - 8].is_large_map()) {
1711 // If we are under the bottom right of the large area.
1712 if (overworld_maps_[i - 8].large_index() == 3) {
1713 RETURN_IF_ERROR(rom()->WriteShort(
1714 kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x17C0));
1715 }
1716 }
1717 }
1718
1719 RETURN_IF_ERROR(rom()->WriteShort(
1720 kOverworldScreenTileMapChangeByScreen4 + (i * 2), 0x1000));
1721
1722 // If the area below is a large map, we don't need to add an offset to it.
1723 // otherwise leave it the same.
1724 // Just to make sure where don't try to read outside of the array.
1725 if (i + 8 < 64) {
1726 // If the area just below us is a large area.
1727 if (overworld_maps_[i + 8].is_large_map()) {
1728 // If we are on top of the top right of the large area.
1729 if (overworld_maps_[i + 8].large_index() == 1) {
1730 RETURN_IF_ERROR(rom()->WriteShort(
1731 kOverworldScreenTileMapChangeByScreen4 + (i * 2), 0x0FC0));
1732 }
1733 }
1734 }
1735
1736 RETURN_IF_ERROR(rom()->WriteShort(kTransitionTargetNorth + (i * 2),
1737 (uint16_t)((y_pos * 0x200) - 0xE0)));
1738 RETURN_IF_ERROR(rom()->WriteShort(kTransitionTargetWest + (i * 2),
1739 (uint16_t)((x_pos * 0x200) - 0x100)));
1740
1741 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionX + (i * 2),
1742 (x_pos * 0x200)));
1743 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionY + (i * 2),
1744 (y_pos * 0x200)));
1745
1746 checked_map.emplace_back(i);
1747 }
1748 }
1749
1750 constexpr int OverworldScreenTileMapChangeMask = 0x1262C;
1751
1753 rom()->WriteShort(OverworldScreenTileMapChangeMask + 0, 0x1F80));
1755 rom()->WriteShort(OverworldScreenTileMapChangeMask + 2, 0x1F80));
1757 rom()->WriteShort(OverworldScreenTileMapChangeMask + 4, 0x007F));
1759 rom()->WriteShort(OverworldScreenTileMapChangeMask + 6, 0x007F));
1760
1761 return absl::OkStatus();
1762}
1763
1765 int i, int parent_x_pos, int parent_y_pos, int transition_target_north,
1766 int transition_target_west, int transition_pos_x, int transition_pos_y,
1767 int screen_change_1, int screen_change_2, int screen_change_3,
1768 int screen_change_4) {
1769 // Set basic transition targets
1771 rom()->WriteShort(transition_target_north + (i * 2),
1772 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
1774 rom()->WriteShort(transition_target_west + (i * 2),
1775 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
1776
1778 rom()->WriteShort(transition_pos_x + (i * 2), parent_x_pos * 0x0200));
1780 rom()->WriteShort(transition_pos_y + (i * 2), parent_y_pos * 0x0200));
1781
1782 // byScreen1 = Transitioning right
1783 uint16_t by_screen1_small = 0x0060;
1784
1785 // Check west neighbor for transition adjustments
1786 if ((i % 0x40) - 1 >= 0) {
1787 auto& west_neighbor = overworld_maps_[i - 1];
1788
1789 // Transition from bottom right quadrant of large area to small area
1790 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1791 west_neighbor.large_index() == 3) {
1792 by_screen1_small = 0xF060;
1793 }
1794 // Transition from bottom quadrant of tall area to small area
1795 else if (west_neighbor.area_size() == AreaSizeEnum::TallArea &&
1796 west_neighbor.large_index() == 2) {
1797 by_screen1_small = 0xF060;
1798 }
1799 }
1800
1802 rom()->WriteShort(screen_change_1 + (i * 2), by_screen1_small));
1803
1804 // byScreen2 = Transitioning left
1805 uint16_t by_screen2_small = 0x0040;
1806
1807 // Check east neighbor for transition adjustments
1808 if ((i % 0x40) + 1 < 0x40 && i + 1 < kNumOverworldMaps) {
1809 auto& east_neighbor = overworld_maps_[i + 1];
1810
1811 // Transition from bottom left quadrant of large area to small area
1812 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1813 east_neighbor.large_index() == 2) {
1814 by_screen2_small = 0xF040;
1815 }
1816 // Transition from bottom quadrant of tall area to small area
1817 else if (east_neighbor.area_size() == AreaSizeEnum::TallArea &&
1818 east_neighbor.large_index() == 2) {
1819 by_screen2_small = 0xF040;
1820 }
1821 }
1822
1824 rom()->WriteShort(screen_change_2 + (i * 2), by_screen2_small));
1825
1826 // byScreen3 = Transitioning down
1827 uint16_t by_screen3_small = 0x1800;
1828
1829 // Check north neighbor for transition adjustments
1830 if ((i % 0x40) - 8 >= 0) {
1831 auto& north_neighbor = overworld_maps_[i - 8];
1832
1833 // Transition from bottom right quadrant of large area to small area
1834 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1835 north_neighbor.large_index() == 3) {
1836 by_screen3_small = 0x17C0;
1837 }
1838 // Transition from right quadrant of wide area to small area
1839 else if (north_neighbor.area_size() == AreaSizeEnum::WideArea &&
1840 north_neighbor.large_index() == 1) {
1841 by_screen3_small = 0x17C0;
1842 }
1843 }
1844
1846 rom()->WriteShort(screen_change_3 + (i * 2), by_screen3_small));
1847
1848 // byScreen4 = Transitioning up
1849 uint16_t by_screen4_small = 0x1000;
1850
1851 // Check south neighbor for transition adjustments
1852 if ((i % 0x40) + 8 < 0x40 && i + 8 < kNumOverworldMaps) {
1853 auto& south_neighbor = overworld_maps_[i + 8];
1854
1855 // Transition from top right quadrant of large area to small area
1856 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1857 south_neighbor.large_index() == 1) {
1858 by_screen4_small = 0x0FC0;
1859 }
1860 // Transition from right quadrant of wide area to small area
1861 else if (south_neighbor.area_size() == AreaSizeEnum::WideArea &&
1862 south_neighbor.large_index() == 1) {
1863 by_screen4_small = 0x0FC0;
1864 }
1865 }
1866
1868 rom()->WriteShort(screen_change_4 + (i * 2), by_screen4_small));
1869
1870 return absl::OkStatus();
1871}
1872
1874 int i, int parent_x_pos, int parent_y_pos, int transition_target_north,
1875 int transition_target_west, int transition_pos_x, int transition_pos_y,
1876 int screen_change_1, int screen_change_2, int screen_change_3,
1877 int screen_change_4) {
1878 // Set transition targets for all 4 quadrants
1879 const uint16_t offsets[] = {0, 2, 16, 18};
1880 for (auto offset : offsets) {
1882 rom()->WriteShort(transition_target_north + (i * 2) + offset,
1883 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
1885 rom()->WriteShort(transition_target_west + (i * 2) + offset,
1886 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
1887 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset,
1888 parent_x_pos * 0x0200));
1889 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset,
1890 parent_y_pos * 0x0200));
1891 }
1892
1893 // Complex neighbor-aware transition calculations for large areas
1894 // byScreen1 = Transitioning right
1895 std::array<uint16_t, 4> by_screen1_large = {0x0060, 0x0060, 0x1060, 0x1060};
1896
1897 // Check west neighbor
1898 if ((i % 0x40) - 1 >= 0) {
1899 auto& west_neighbor = overworld_maps_[i - 1];
1900
1901 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea) {
1902 switch (west_neighbor.large_index()) {
1903 case 1: // From bottom right to bottom left of large area
1904 by_screen1_large[2] = 0x0060;
1905 break;
1906 case 3: // From bottom right to top left of large area
1907 by_screen1_large[0] = 0xF060;
1908 break;
1909 }
1910 } else if (west_neighbor.area_size() == AreaSizeEnum::TallArea) {
1911 switch (west_neighbor.large_index()) {
1912 case 0: // From bottom of tall to bottom left of large
1913 by_screen1_large[2] = 0x0060;
1914 break;
1915 case 2: // From bottom of tall to top left of large
1916 by_screen1_large[0] = 0xF060;
1917 break;
1918 }
1919 }
1920 }
1921
1922 for (int j = 0; j < 4; j++) {
1923 RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j],
1924 by_screen1_large[j]));
1925 }
1926
1927 // byScreen2 = Transitioning left
1928 std::array<uint16_t, 4> by_screen2_large = {0x0080, 0x0080, 0x1080, 0x1080};
1929
1930 // Check east neighbor
1931 if ((i % 0x40) + 2 < 0x40 && i + 2 < kNumOverworldMaps) {
1932 auto& east_neighbor = overworld_maps_[i + 2];
1933
1934 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea) {
1935 switch (east_neighbor.large_index()) {
1936 case 0: // From bottom left to bottom right of large area
1937 by_screen2_large[3] = 0x0080;
1938 break;
1939 case 2: // From bottom left to top right of large area
1940 by_screen2_large[1] = 0xF080;
1941 break;
1942 }
1943 } else if (east_neighbor.area_size() == AreaSizeEnum::TallArea) {
1944 switch (east_neighbor.large_index()) {
1945 case 0: // From bottom of tall to bottom right of large
1946 by_screen2_large[3] = 0x0080;
1947 break;
1948 case 2: // From bottom of tall to top right of large
1949 by_screen2_large[1] = 0xF080;
1950 break;
1951 }
1952 }
1953 }
1954
1955 for (int j = 0; j < 4; j++) {
1956 RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j],
1957 by_screen2_large[j]));
1958 }
1959
1960 // byScreen3 = Transitioning down
1961 std::array<uint16_t, 4> by_screen3_large = {0x1800, 0x1840, 0x1800, 0x1840};
1962
1963 // Check north neighbor
1964 if ((i % 0x40) - 8 >= 0) {
1965 auto& north_neighbor = overworld_maps_[i - 8];
1966
1967 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea) {
1968 switch (north_neighbor.large_index()) {
1969 case 2: // From bottom right to top right of large area
1970 by_screen3_large[1] = 0x1800;
1971 break;
1972 case 3: // From bottom right to top left of large area
1973 by_screen3_large[0] = 0x17C0;
1974 break;
1975 }
1976 } else if (north_neighbor.area_size() == AreaSizeEnum::WideArea) {
1977 switch (north_neighbor.large_index()) {
1978 case 0: // From right of wide to top right of large
1979 by_screen3_large[1] = 0x1800;
1980 break;
1981 case 1: // From right of wide to top left of large
1982 by_screen3_large[0] = 0x17C0;
1983 break;
1984 }
1985 }
1986 }
1987
1988 for (int j = 0; j < 4; j++) {
1989 RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j],
1990 by_screen3_large[j]));
1991 }
1992
1993 // byScreen4 = Transitioning up
1994 std::array<uint16_t, 4> by_screen4_large = {0x2000, 0x2040, 0x2000, 0x2040};
1995
1996 // Check south neighbor
1997 if ((i % 0x40) + 16 < 0x40 && i + 16 < kNumOverworldMaps) {
1998 auto& south_neighbor = overworld_maps_[i + 16];
1999
2000 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2001 switch (south_neighbor.large_index()) {
2002 case 0: // From top right to bottom right of large area
2003 by_screen4_large[3] = 0x2000;
2004 break;
2005 case 1: // From top right to bottom left of large area
2006 by_screen4_large[2] = 0x1FC0;
2007 break;
2008 }
2009 } else if (south_neighbor.area_size() == AreaSizeEnum::WideArea) {
2010 switch (south_neighbor.large_index()) {
2011 case 0: // From right of wide to bottom right of large
2012 by_screen4_large[3] = 0x2000;
2013 break;
2014 case 1: // From right of wide to bottom left of large
2015 by_screen4_large[2] = 0x1FC0;
2016 break;
2017 }
2018 }
2019 }
2020
2021 for (int j = 0; j < 4; j++) {
2022 RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j],
2023 by_screen4_large[j]));
2024 }
2025
2026 return absl::OkStatus();
2027}
2028
2030 int i, int parent_x_pos, int parent_y_pos, int transition_target_north,
2031 int transition_target_west, int transition_pos_x, int transition_pos_y,
2032 int screen_change_1, int screen_change_2, int screen_change_3,
2033 int screen_change_4) {
2034 // Set transition targets for both quadrants
2035 const uint16_t offsets[] = {0, 2};
2036 for (auto offset : offsets) {
2038 rom()->WriteShort(transition_target_north + (i * 2) + offset,
2039 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
2041 rom()->WriteShort(transition_target_west + (i * 2) + offset,
2042 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
2043 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset,
2044 parent_x_pos * 0x0200));
2045 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset,
2046 parent_y_pos * 0x0200));
2047 }
2048
2049 // byScreen1 = Transitioning right
2050 std::array<uint16_t, 2> by_screen1_wide = {0x0060, 0x0060};
2051
2052 // Check west neighbor
2053 if ((i % 0x40) - 1 >= 0) {
2054 auto& west_neighbor = overworld_maps_[i - 1];
2055
2056 // From bottom right of large to left of wide
2057 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2058 west_neighbor.large_index() == 3) {
2059 by_screen1_wide[0] = 0xF060;
2060 }
2061 // From bottom of tall to left of wide
2062 else if (west_neighbor.area_size() == AreaSizeEnum::TallArea &&
2063 west_neighbor.large_index() == 2) {
2064 by_screen1_wide[0] = 0xF060;
2065 }
2066 }
2067
2068 for (int j = 0; j < 2; j++) {
2069 RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j],
2070 by_screen1_wide[j]));
2071 }
2072
2073 // byScreen2 = Transitioning left
2074 std::array<uint16_t, 2> by_screen2_wide = {0x0080, 0x0080};
2075
2076 // Check east neighbor
2077 if ((i % 0x40) + 2 < 0x40 && i + 2 < kNumOverworldMaps) {
2078 auto& east_neighbor = overworld_maps_[i + 2];
2079
2080 // From bottom left of large to right of wide
2081 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2082 east_neighbor.large_index() == 2) {
2083 by_screen2_wide[1] = 0xF080;
2084 }
2085 // From bottom of tall to right of wide
2086 else if (east_neighbor.area_size() == AreaSizeEnum::TallArea &&
2087 east_neighbor.large_index() == 2) {
2088 by_screen2_wide[1] = 0xF080;
2089 }
2090 }
2091
2092 for (int j = 0; j < 2; j++) {
2093 RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j],
2094 by_screen2_wide[j]));
2095 }
2096
2097 // byScreen3 = Transitioning down
2098 std::array<uint16_t, 2> by_screen3_wide = {0x1800, 0x1840};
2099
2100 // Check north neighbor
2101 if ((i % 0x40) - 8 >= 0) {
2102 auto& north_neighbor = overworld_maps_[i - 8];
2103
2104 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2105 switch (north_neighbor.large_index()) {
2106 case 2: // From bottom right of large to right of wide
2107 by_screen3_wide[1] = 0x1800;
2108 break;
2109 case 3: // From bottom right of large to left of wide
2110 by_screen3_wide[0] = 0x17C0;
2111 break;
2112 }
2113 } else if (north_neighbor.area_size() == AreaSizeEnum::WideArea) {
2114 switch (north_neighbor.large_index()) {
2115 case 0: // From right of wide to right of wide
2116 by_screen3_wide[1] = 0x1800;
2117 break;
2118 case 1: // From right of wide to left of wide
2119 by_screen3_wide[0] = 0x07C0;
2120 break;
2121 }
2122 }
2123 }
2124
2125 for (int j = 0; j < 2; j++) {
2126 RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j],
2127 by_screen3_wide[j]));
2128 }
2129
2130 // byScreen4 = Transitioning up
2131 std::array<uint16_t, 2> by_screen4_wide = {0x1000, 0x1040};
2132
2133 // Check south neighbor
2134 if ((i % 0x40) + 8 < 0x40 && i + 8 < kNumOverworldMaps) {
2135 auto& south_neighbor = overworld_maps_[i + 8];
2136
2137 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2138 switch (south_neighbor.large_index()) {
2139 case 0: // From top right of large to right of wide
2140 by_screen4_wide[1] = 0x1000;
2141 break;
2142 case 1: // From top right of large to left of wide
2143 by_screen4_wide[0] = 0x0FC0;
2144 break;
2145 }
2146 } else if (south_neighbor.area_size() == AreaSizeEnum::WideArea) {
2147 if (south_neighbor.large_index() == 1) {
2148 by_screen4_wide[0] = 0x0FC0;
2149 }
2150 switch (south_neighbor.large_index()) {
2151 case 0: // From right of wide to right of wide
2152 by_screen4_wide[1] = 0x1000;
2153 break;
2154 case 1: // From right of wide to left of wide
2155 by_screen4_wide[0] = 0x0FC0;
2156 break;
2157 }
2158 }
2159 }
2160
2161 for (int j = 0; j < 2; j++) {
2162 RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j],
2163 by_screen4_wide[j]));
2164 }
2165
2166 return absl::OkStatus();
2167}
2168
2170 int i, int parent_x_pos, int parent_y_pos, int transition_target_north,
2171 int transition_target_west, int transition_pos_x, int transition_pos_y,
2172 int screen_change_1, int screen_change_2, int screen_change_3,
2173 int screen_change_4) {
2174 // Set transition targets for both quadrants
2175 const uint16_t offsets[] = {0, 16};
2176 for (auto offset : offsets) {
2178 rom()->WriteShort(transition_target_north + (i * 2) + offset,
2179 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
2181 rom()->WriteShort(transition_target_west + (i * 2) + offset,
2182 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
2183 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset,
2184 parent_x_pos * 0x0200));
2185 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset,
2186 parent_y_pos * 0x0200));
2187 }
2188
2189 // byScreen1 = Transitioning right
2190 std::array<uint16_t, 2> by_screen1_tall = {0x0060, 0x1060};
2191
2192 // Check west neighbor
2193 if ((i % 0x40) - 1 >= 0) {
2194 auto& west_neighbor = overworld_maps_[i - 1];
2195
2196 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2197 switch (west_neighbor.large_index()) {
2198 case 1: // From bottom right of large to bottom of tall
2199 by_screen1_tall[1] = 0x0060;
2200 break;
2201 case 3: // From bottom right of large to top of tall
2202 by_screen1_tall[0] = 0xF060;
2203 break;
2204 }
2205 } else if (west_neighbor.area_size() == AreaSizeEnum::TallArea) {
2206 switch (west_neighbor.large_index()) {
2207 case 0: // From bottom of tall to bottom of tall
2208 by_screen1_tall[1] = 0x0060;
2209 break;
2210 case 2: // From bottom of tall to top of tall
2211 by_screen1_tall[0] = 0xF060;
2212 break;
2213 }
2214 }
2215 }
2216
2217 for (int j = 0; j < 2; j++) {
2218 RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j],
2219 by_screen1_tall[j]));
2220 }
2221
2222 // byScreen2 = Transitioning left
2223 std::array<uint16_t, 2> by_screen2_tall = {0x0040, 0x1040};
2224
2225 // Check east neighbor
2226 if ((i % 0x40) + 1 < 0x40 && i + 1 < kNumOverworldMaps) {
2227 auto& east_neighbor = overworld_maps_[i + 1];
2228
2229 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea) {
2230 switch (east_neighbor.large_index()) {
2231 case 0: // From bottom left of large to bottom of tall
2232 by_screen2_tall[1] = 0x0040;
2233 break;
2234 case 2: // From bottom left of large to top of tall
2235 by_screen2_tall[0] = 0xF040;
2236 break;
2237 }
2238 } else if (east_neighbor.area_size() == AreaSizeEnum::TallArea) {
2239 switch (east_neighbor.large_index()) {
2240 case 0: // From bottom of tall to bottom of tall
2241 by_screen2_tall[1] = 0x0040;
2242 break;
2243 case 2: // From bottom of tall to top of tall
2244 by_screen2_tall[0] = 0xF040;
2245 break;
2246 }
2247 }
2248 }
2249
2250 for (int j = 0; j < 2; j++) {
2251 RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j],
2252 by_screen2_tall[j]));
2253 }
2254
2255 // byScreen3 = Transitioning down
2256 std::array<uint16_t, 2> by_screen3_tall = {0x1800, 0x1800};
2257
2258 // Check north neighbor
2259 if ((i % 0x40) - 8 >= 0) {
2260 auto& north_neighbor = overworld_maps_[i - 8];
2261
2262 // From bottom right of large to top of tall
2263 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2264 north_neighbor.large_index() == 3) {
2265 by_screen3_tall[0] = 0x17C0;
2266 }
2267 // From right of wide to top of tall
2268 else if (north_neighbor.area_size() == AreaSizeEnum::WideArea &&
2269 north_neighbor.large_index() == 1) {
2270 by_screen3_tall[0] = 0x17C0;
2271 }
2272 }
2273
2274 for (int j = 0; j < 2; j++) {
2275 RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j],
2276 by_screen3_tall[j]));
2277 }
2278
2279 // byScreen4 = Transitioning up
2280 std::array<uint16_t, 2> by_screen4_tall = {0x2000, 0x2000};
2281
2282 // Check south neighbor
2283 if ((i % 0x40) + 16 < 0x40 && i + 16 < kNumOverworldMaps) {
2284 auto& south_neighbor = overworld_maps_[i + 16];
2285
2286 // From top right of large to bottom of tall
2287 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea &&
2288 south_neighbor.large_index() == 1) {
2289 by_screen4_tall[1] = 0x1FC0;
2290 }
2291 // From right of wide to bottom of tall
2292 else if (south_neighbor.area_size() == AreaSizeEnum::WideArea &&
2293 south_neighbor.large_index() == 1) {
2294 by_screen4_tall[1] = 0x1FC0;
2295 }
2296 }
2297
2298 for (int j = 0; j < 2; j++) {
2299 RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j],
2300 by_screen4_tall[j]));
2301 }
2302
2303 return absl::OkStatus();
2304}
2305
2307 util::logf("Saving Large Maps (v3+ Expanded)");
2308
2309 // Use expanded memory locations for v3+
2310 int transition_target_north = zelda3::transition_target_northExpanded;
2311 int transition_target_west = zelda3::transition_target_westExpanded;
2312 int transition_pos_x = zelda3::kOverworldTransitionPositionXExpanded;
2313 int transition_pos_y = zelda3::kOverworldTransitionPositionYExpanded;
2318
2319 std::vector<uint8_t> checked_map;
2320
2321 // Process all overworld maps (0xA0 for v3)
2322 for (int i = 0; i < kNumOverworldMaps; ++i) {
2323 // Skip if this map was already processed as part of a multi-area structure
2324 if (std::find(checked_map.begin(), checked_map.end(), i) !=
2325 checked_map.end()) {
2326 continue;
2327 }
2328
2329 int parent_y_pos = (overworld_maps_[i].parent() % 0x40) / 8;
2330 int parent_x_pos = (overworld_maps_[i].parent() % 0x40) % 8;
2331
2332 // Write the map parent ID to expanded parent table
2334 overworld_maps_[i].parent()));
2335
2336 // Handle transitions based on area size
2337 switch (overworld_maps_[i].area_size()) {
2340 i, parent_x_pos, parent_y_pos, transition_target_north,
2341 transition_target_west, transition_pos_x, transition_pos_y,
2342 screen_change_1, screen_change_2, screen_change_3,
2343 screen_change_4));
2344 checked_map.emplace_back(i);
2345 break;
2346
2349 i, parent_x_pos, parent_y_pos, transition_target_north,
2350 transition_target_west, transition_pos_x, transition_pos_y,
2351 screen_change_1, screen_change_2, screen_change_3,
2352 screen_change_4));
2353 // Mark all 4 quadrants as processed
2354 checked_map.emplace_back(i);
2355 checked_map.emplace_back(i + 1);
2356 checked_map.emplace_back(i + 8);
2357 checked_map.emplace_back(i + 9);
2358 break;
2359
2362 i, parent_x_pos, parent_y_pos, transition_target_north,
2363 transition_target_west, transition_pos_x, transition_pos_y,
2364 screen_change_1, screen_change_2, screen_change_3,
2365 screen_change_4));
2366 // Mark both horizontal quadrants as processed
2367 checked_map.emplace_back(i);
2368 checked_map.emplace_back(i + 1);
2369 break;
2370
2373 i, parent_x_pos, parent_y_pos, transition_target_north,
2374 transition_target_west, transition_pos_x, transition_pos_y,
2375 screen_change_1, screen_change_2, screen_change_3,
2376 screen_change_4));
2377 // Mark both vertical quadrants as processed
2378 checked_map.emplace_back(i);
2379 checked_map.emplace_back(i + 8);
2380 break;
2381 }
2382 }
2383
2384 return absl::OkStatus();
2385}
2386
2387namespace {
2388std::vector<uint64_t> GetAllTile16(OverworldMapTiles& map_tiles_) {
2389 std::vector<uint64_t> all_tile_16; // Ensure it's 64 bits
2390
2391 int sx = 0;
2392 int sy = 0;
2393 int c = 0;
2394 OverworldBlockset tiles_used;
2395 for (int i = 0; i < kNumOverworldMaps; i++) {
2396 if (i < kDarkWorldMapIdStart) {
2397 tiles_used = map_tiles_.light_world;
2398 } else if (i < kSpecialWorldMapIdStart && i >= kDarkWorldMapIdStart) {
2399 tiles_used = map_tiles_.dark_world;
2400 } else {
2401 tiles_used = map_tiles_.special_world;
2402 }
2403
2404 for (int y = 0; y < 32; y += 2) {
2405 for (int x = 0; x < 32; x += 2) {
2406 gfx::Tile32 current_tile(
2407 tiles_used[x + (sx * 32)][y + (sy * 32)],
2408 tiles_used[x + 1 + (sx * 32)][y + (sy * 32)],
2409 tiles_used[x + (sx * 32)][y + 1 + (sy * 32)],
2410 tiles_used[x + 1 + (sx * 32)][y + 1 + (sy * 32)]);
2411
2412 all_tile_16.emplace_back(current_tile.GetPackedValue());
2413 }
2414 }
2415
2416 sx++;
2417 if (sx >= 8) {
2418 sy++;
2419 sx = 0;
2420 }
2421
2422 c++;
2423 if (c >= 64) {
2424 sx = 0;
2425 sy = 0;
2426 c = 0;
2427 }
2428 }
2429
2430 return all_tile_16;
2431}
2432} // namespace
2433
2435 tiles32_unique_.clear();
2436 tiles32_list_.clear();
2437
2438 // Get all tiles16 and packs them into tiles32
2439 std::vector<uint64_t> all_tile_16 = GetAllTile16(map_tiles_);
2440
2441 // Convert to set then back to vector
2442 std::set<uint64_t> unique_tiles_set(all_tile_16.begin(), all_tile_16.end());
2443
2444 std::vector<uint64_t> unique_tiles(all_tile_16);
2445 unique_tiles.assign(unique_tiles_set.begin(), unique_tiles_set.end());
2446
2447 // Create the indexed tiles list
2448 std::unordered_map<uint64_t, uint16_t> all_tiles_indexed;
2449 for (size_t tile32_id = 0; tile32_id < unique_tiles.size(); tile32_id++) {
2450 all_tiles_indexed.insert(
2451 {unique_tiles[tile32_id], static_cast<uint16_t>(tile32_id)});
2452 }
2453
2454 // Add all tiles32 from all maps.
2455 // Convert all tiles32 non-unique IDs into unique array of IDs.
2456 for (int j = 0; j < NumberOfMap32; j++) {
2457 tiles32_list_.emplace_back(all_tiles_indexed[all_tile_16[j]]);
2458 }
2459
2460 // Create the unique tiles list
2461 for (size_t i = 0; i < unique_tiles.size(); ++i) {
2462 tiles32_unique_.emplace_back(gfx::Tile32(unique_tiles[i]));
2463 }
2464
2465 while (tiles32_unique_.size() % 4 != 0) {
2466 gfx::Tile32 padding_tile(0, 0, 0, 0);
2467 tiles32_unique_.emplace_back(padding_tile.GetPackedValue());
2468 }
2469
2470 if (tiles32_unique_.size() > LimitOfMap32) {
2471 return absl::InternalError(absl::StrFormat(
2472 "Number of unique Tiles32: %d Out of: %d\nUnique Tile32 count exceed "
2473 "the limit\nThe ROM Has not been saved\nYou can fill maps with grass "
2474 "tiles to free some space\nOr use the option Clear DW Tiles in the "
2475 "Overworld Menu",
2476 unique_tiles.size(), LimitOfMap32));
2477 }
2478
2479 if (core::FeatureFlags::get().kLogToConsole) {
2480 std::cout << "Number of unique Tiles32: " << tiles32_unique_.size()
2481 << " Saved:" << tiles32_unique_.size()
2482 << " Out of: " << LimitOfMap32 << std::endl;
2483 }
2484
2485 int v = tiles32_unique_.size();
2486 for (int i = v; i < LimitOfMap32; i++) {
2487 gfx::Tile32 padding_tile(420, 420, 420, 420);
2488 tiles32_unique_.emplace_back(padding_tile.GetPackedValue());
2489 }
2490
2491 return absl::OkStatus();
2492}
2493
2495 int bottomLeft = kMap32TileBLExpanded;
2496 int bottomRight = kMap32TileBRExpanded;
2497 int topRight = kMap32TileTRExpanded;
2498 int limit = 0x8A80;
2499
2500 // Updates the pointers too for the tile32
2501 // Top Right
2502 RETURN_IF_ERROR(rom()->WriteLong(0x0176EC, PcToSnes(kMap32TileTRExpanded)));
2504 rom()->WriteLong(0x0176F3, PcToSnes(kMap32TileTRExpanded + 1)));
2506 rom()->WriteLong(0x0176FA, PcToSnes(kMap32TileTRExpanded + 2)));
2508 rom()->WriteLong(0x017701, PcToSnes(kMap32TileTRExpanded + 3)));
2510 rom()->WriteLong(0x017708, PcToSnes(kMap32TileTRExpanded + 4)));
2512 rom()->WriteLong(0x01771A, PcToSnes(kMap32TileTRExpanded + 5)));
2513
2514 // BottomLeft
2515 RETURN_IF_ERROR(rom()->WriteLong(0x01772C, PcToSnes(kMap32TileBLExpanded)));
2517 rom()->WriteLong(0x017733, PcToSnes(kMap32TileBLExpanded + 1)));
2519 rom()->WriteLong(0x01773A, PcToSnes(kMap32TileBLExpanded + 2)));
2521 rom()->WriteLong(0x017741, PcToSnes(kMap32TileBLExpanded + 3)));
2523 rom()->WriteLong(0x017748, PcToSnes(kMap32TileBLExpanded + 4)));
2525 rom()->WriteLong(0x01775A, PcToSnes(kMap32TileBLExpanded + 5)));
2526
2527 // BottomRight
2528 RETURN_IF_ERROR(rom()->WriteLong(0x01776C, PcToSnes(kMap32TileBRExpanded)));
2530 rom()->WriteLong(0x017773, PcToSnes(kMap32TileBRExpanded + 1)));
2532 rom()->WriteLong(0x01777A, PcToSnes(kMap32TileBRExpanded + 2)));
2534 rom()->WriteLong(0x017781, PcToSnes(kMap32TileBRExpanded + 3)));
2536 rom()->WriteLong(0x017788, PcToSnes(kMap32TileBRExpanded + 4)));
2538 rom()->WriteLong(0x01779A, PcToSnes(kMap32TileBRExpanded + 5)));
2539
2540 constexpr int kTilesPer32x32Tile = 6;
2541 int unique_tile_index = 0;
2542 int num_unique_tiles = tiles32_unique_.size();
2543
2544 for (int i = 0; i < num_unique_tiles; i += kTilesPer32x32Tile) {
2545 if (unique_tile_index >= limit) {
2546 return absl::AbortedError("Too many unique tile32 definitions.");
2547 }
2548
2549 // Top Left.
2550 auto top_left = version_constants().kMap32TileTL;
2551 RETURN_IF_ERROR(rom()->WriteByte(
2552 top_left + i,
2553 (uint8_t)(tiles32_unique_[unique_tile_index].tile0_ & 0xFF)));
2554 RETURN_IF_ERROR(rom()->WriteByte(
2555 top_left + (i + 1),
2556 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile0_ & 0xFF)));
2557 RETURN_IF_ERROR(rom()->WriteByte(
2558 top_left + (i + 2),
2559 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile0_ & 0xFF)));
2560 RETURN_IF_ERROR(rom()->WriteByte(
2561 top_left + (i + 3),
2562 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile0_ & 0xFF)));
2563
2564 RETURN_IF_ERROR(rom()->WriteByte(
2565 top_left + (i + 4),
2566 (uint8_t)(((tiles32_unique_[unique_tile_index].tile0_ >> 4) & 0xF0) +
2567 ((tiles32_unique_[unique_tile_index + 1].tile0_ >> 8) &
2568 0x0F))));
2569 RETURN_IF_ERROR(rom()->WriteByte(
2570 top_left + (i + 5),
2571 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile0_ >> 4) &
2572 0xF0) +
2573 ((tiles32_unique_[unique_tile_index + 3].tile0_ >> 8) &
2574 0x0F))));
2575
2576 // Top Right.
2577 auto top_right = topRight;
2578 RETURN_IF_ERROR(rom()->WriteByte(
2579 top_right + i,
2580 (uint8_t)(tiles32_unique_[unique_tile_index].tile1_ & 0xFF)));
2581 RETURN_IF_ERROR(rom()->WriteByte(
2582 top_right + (i + 1),
2583 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile1_ & 0xFF)));
2584 RETURN_IF_ERROR(rom()->WriteByte(
2585 top_right + (i + 2),
2586 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile1_ & 0xFF)));
2587 RETURN_IF_ERROR(rom()->WriteByte(
2588 top_right + (i + 3),
2589 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile1_ & 0xFF)));
2590
2591 RETURN_IF_ERROR(rom()->WriteByte(
2592 top_right + (i + 4),
2593 (uint8_t)(((tiles32_unique_[unique_tile_index].tile1_ >> 4) & 0xF0) |
2594 ((tiles32_unique_[unique_tile_index + 1].tile1_ >> 8) &
2595 0x0F))));
2596 RETURN_IF_ERROR(rom()->WriteByte(
2597 top_right + (i + 5),
2598 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile1_ >> 4) &
2599 0xF0) |
2600 ((tiles32_unique_[unique_tile_index + 3].tile1_ >> 8) &
2601 0x0F))));
2602
2603 // Bottom Left.
2604 auto bottom_left = bottomLeft;
2605 RETURN_IF_ERROR(rom()->WriteByte(
2606 bottom_left + i,
2607 (uint8_t)(tiles32_unique_[unique_tile_index].tile2_ & 0xFF)));
2608 RETURN_IF_ERROR(rom()->WriteByte(
2609 bottom_left + (i + 1),
2610 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile2_ & 0xFF)));
2611 RETURN_IF_ERROR(rom()->WriteByte(
2612 bottom_left + (i + 2),
2613 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile2_ & 0xFF)));
2614 RETURN_IF_ERROR(rom()->WriteByte(
2615 bottom_left + (i + 3),
2616 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile2_ & 0xFF)));
2617
2618 RETURN_IF_ERROR(rom()->WriteByte(
2619 bottom_left + (i + 4),
2620 (uint8_t)(((tiles32_unique_[unique_tile_index].tile2_ >> 4) & 0xF0) |
2621 ((tiles32_unique_[unique_tile_index + 1].tile2_ >> 8) &
2622 0x0F))));
2623 RETURN_IF_ERROR(rom()->WriteByte(
2624 bottom_left + (i + 5),
2625 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile2_ >> 4) &
2626 0xF0) |
2627 ((tiles32_unique_[unique_tile_index + 3].tile2_ >> 8) &
2628 0x0F))));
2629
2630 // Bottom Right.
2631 auto bottom_right = bottomRight;
2632 RETURN_IF_ERROR(rom()->WriteByte(
2633 bottom_right + i,
2634 (uint8_t)(tiles32_unique_[unique_tile_index].tile3_ & 0xFF)));
2635 RETURN_IF_ERROR(rom()->WriteByte(
2636 bottom_right + (i + 1),
2637 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile3_ & 0xFF)));
2638 RETURN_IF_ERROR(rom()->WriteByte(
2639 bottom_right + (i + 2),
2640 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile3_ & 0xFF)));
2641 RETURN_IF_ERROR(rom()->WriteByte(
2642 bottom_right + (i + 3),
2643 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile3_ & 0xFF)));
2644
2645 RETURN_IF_ERROR(rom()->WriteByte(
2646 bottom_right + (i + 4),
2647 (uint8_t)(((tiles32_unique_[unique_tile_index].tile3_ >> 4) & 0xF0) |
2648 ((tiles32_unique_[unique_tile_index + 1].tile3_ >> 8) &
2649 0x0F))));
2650 RETURN_IF_ERROR(rom()->WriteByte(
2651 bottom_right + (i + 5),
2652 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile3_ >> 4) &
2653 0xF0) |
2654 ((tiles32_unique_[unique_tile_index + 3].tile3_ >> 8) &
2655 0x0F))));
2656
2657 unique_tile_index += 4;
2658 }
2659
2660 return absl::OkStatus();
2661}
2662
2664 util::logf("Saving Map32 Tiles");
2665 constexpr int kMaxUniqueTiles = 0x4540;
2666 constexpr int kTilesPer32x32Tile = 6;
2667
2668 int unique_tile_index = 0;
2669 int num_unique_tiles = tiles32_unique_.size();
2670
2671 for (int i = 0; i < num_unique_tiles; i += kTilesPer32x32Tile) {
2672 if (unique_tile_index >= kMaxUniqueTiles) {
2673 return absl::AbortedError("Too many unique tile32 definitions.");
2674 }
2675
2676 // Top Left.
2677 auto top_left = version_constants().kMap32TileTL;
2678
2679 RETURN_IF_ERROR(rom()->WriteByte(
2680 top_left + i,
2681 (uint8_t)(tiles32_unique_[unique_tile_index].tile0_ & 0xFF)));
2682 RETURN_IF_ERROR(rom()->WriteByte(
2683 top_left + (i + 1),
2684 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile0_ & 0xFF)));
2685 RETURN_IF_ERROR(rom()->WriteByte(
2686 top_left + (i + 2),
2687 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile0_ & 0xFF)));
2688 RETURN_IF_ERROR(rom()->WriteByte(
2689 top_left + (i + 3),
2690 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile0_ & 0xFF)));
2691
2692 RETURN_IF_ERROR(rom()->WriteByte(
2693 top_left + (i + 4),
2694 (uint8_t)(((tiles32_unique_[unique_tile_index].tile0_ >> 4) & 0xF0) +
2695 ((tiles32_unique_[unique_tile_index + 1].tile0_ >> 8) &
2696 0x0F))));
2697 RETURN_IF_ERROR(rom()->WriteByte(
2698 top_left + (i + 5),
2699 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile0_ >> 4) &
2700 0xF0) +
2701 ((tiles32_unique_[unique_tile_index + 3].tile0_ >> 8) &
2702 0x0F))));
2703
2704 // Top Right.
2705 auto top_right = version_constants().kMap32TileTR;
2706 RETURN_IF_ERROR(rom()->WriteByte(
2707 top_right + i,
2708 (uint8_t)(tiles32_unique_[unique_tile_index].tile1_ & 0xFF)));
2709 RETURN_IF_ERROR(rom()->WriteByte(
2710 top_right + (i + 1),
2711 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile1_ & 0xFF)));
2712 RETURN_IF_ERROR(rom()->WriteByte(
2713 top_right + (i + 2),
2714 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile1_ & 0xFF)));
2715 RETURN_IF_ERROR(rom()->WriteByte(
2716 top_right + (i + 3),
2717 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile1_ & 0xFF)));
2718
2719 RETURN_IF_ERROR(rom()->WriteByte(
2720 top_right + (i + 4),
2721 (uint8_t)(((tiles32_unique_[unique_tile_index].tile1_ >> 4) & 0xF0) |
2722 ((tiles32_unique_[unique_tile_index + 1].tile1_ >> 8) &
2723 0x0F))));
2724 RETURN_IF_ERROR(rom()->WriteByte(
2725 top_right + (i + 5),
2726 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile1_ >> 4) &
2727 0xF0) |
2728 ((tiles32_unique_[unique_tile_index + 3].tile1_ >> 8) &
2729 0x0F))));
2730
2731 // Bottom Left.
2733 RETURN_IF_ERROR(rom()->WriteByte(
2734 map32TilesBL + i,
2735 (uint8_t)(tiles32_unique_[unique_tile_index].tile2_ & 0xFF)));
2736 RETURN_IF_ERROR(rom()->WriteByte(
2737 map32TilesBL + (i + 1),
2738 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile2_ & 0xFF)));
2739 RETURN_IF_ERROR(rom()->WriteByte(
2740 map32TilesBL + (i + 2),
2741 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile2_ & 0xFF)));
2742 RETURN_IF_ERROR(rom()->WriteByte(
2743 map32TilesBL + (i + 3),
2744 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile2_ & 0xFF)));
2745
2746 RETURN_IF_ERROR(rom()->WriteByte(
2747 map32TilesBL + (i + 4),
2748 (uint8_t)(((tiles32_unique_[unique_tile_index].tile2_ >> 4) & 0xF0) |
2749 ((tiles32_unique_[unique_tile_index + 1].tile2_ >> 8) &
2750 0x0F))));
2751 RETURN_IF_ERROR(rom()->WriteByte(
2752 map32TilesBL + (i + 5),
2753 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile2_ >> 4) &
2754 0xF0) |
2755 ((tiles32_unique_[unique_tile_index + 3].tile2_ >> 8) &
2756 0x0F))));
2757
2758 // Bottom Right.
2760 RETURN_IF_ERROR(rom()->WriteByte(
2761 map32TilesBR + i,
2762 (uint8_t)(tiles32_unique_[unique_tile_index].tile3_ & 0xFF)));
2763 RETURN_IF_ERROR(rom()->WriteByte(
2764 map32TilesBR + (i + 1),
2765 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile3_ & 0xFF)));
2766 RETURN_IF_ERROR(rom()->WriteByte(
2767 map32TilesBR + (i + 2),
2768 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile3_ & 0xFF)));
2769 RETURN_IF_ERROR(rom()->WriteByte(
2770 map32TilesBR + (i + 3),
2771 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile3_ & 0xFF)));
2772
2773 RETURN_IF_ERROR(rom()->WriteByte(
2774 map32TilesBR + (i + 4),
2775 (uint8_t)(((tiles32_unique_[unique_tile_index].tile3_ >> 4) & 0xF0) |
2776 ((tiles32_unique_[unique_tile_index + 1].tile3_ >> 8) &
2777 0x0F))));
2778 RETURN_IF_ERROR(rom()->WriteByte(
2779 map32TilesBR + (i + 5),
2780 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile3_ >> 4) &
2781 0xF0) |
2782 ((tiles32_unique_[unique_tile_index + 3].tile3_ >> 8) &
2783 0x0F))));
2784
2785 unique_tile_index += 4;
2786 num_unique_tiles += 2;
2787 }
2788
2789 return absl::OkStatus();
2790}
2791
2794 rom()->WriteLong(SnesToPc(0x008865), PcToSnes(kMap16TilesExpanded)));
2796 rom()->WriteLong(SnesToPc(0x0EDE4F), PcToSnes(kMap16TilesExpanded)));
2798 rom()->WriteLong(SnesToPc(0x0EDEE9), PcToSnes(kMap16TilesExpanded)));
2799
2801 rom()->WriteLong(SnesToPc(0x1BBC2D), PcToSnes(kMap16TilesExpanded + 2)));
2803 rom()->WriteLong(SnesToPc(0x1BBC4C), PcToSnes(kMap16TilesExpanded)));
2805 rom()->WriteLong(SnesToPc(0x1BBCC2), PcToSnes(kMap16TilesExpanded + 4)));
2807 rom()->WriteLong(SnesToPc(0x1BBCCB), PcToSnes(kMap16TilesExpanded + 6)));
2808
2810 rom()->WriteLong(SnesToPc(0x1BBEF6), PcToSnes(kMap16TilesExpanded)));
2812 rom()->WriteLong(SnesToPc(0x1BBF23), PcToSnes(kMap16TilesExpanded)));
2814 rom()->WriteLong(SnesToPc(0x1BC041), PcToSnes(kMap16TilesExpanded)));
2816 rom()->WriteLong(SnesToPc(0x1BC9B3), PcToSnes(kMap16TilesExpanded)));
2817
2819 rom()->WriteLong(SnesToPc(0x1BC9BA), PcToSnes(kMap16TilesExpanded + 2)));
2821 rom()->WriteLong(SnesToPc(0x1BC9C1), PcToSnes(kMap16TilesExpanded + 4)));
2823 rom()->WriteLong(SnesToPc(0x1BC9C8), PcToSnes(kMap16TilesExpanded + 6)));
2824
2826 rom()->WriteLong(SnesToPc(0x1BCA40), PcToSnes(kMap16TilesExpanded)));
2828 rom()->WriteLong(SnesToPc(0x1BCA47), PcToSnes(kMap16TilesExpanded + 2)));
2830 rom()->WriteLong(SnesToPc(0x1BCA4E), PcToSnes(kMap16TilesExpanded + 4)));
2832 rom()->WriteLong(SnesToPc(0x1BCA55), PcToSnes(kMap16TilesExpanded + 6)));
2833
2835 rom()->WriteLong(SnesToPc(0x02F457), PcToSnes(kMap16TilesExpanded)));
2837 rom()->WriteLong(SnesToPc(0x02F45E), PcToSnes(kMap16TilesExpanded + 2)));
2839 rom()->WriteLong(SnesToPc(0x02F467), PcToSnes(kMap16TilesExpanded + 4)));
2841 rom()->WriteLong(SnesToPc(0x02F46E), PcToSnes(kMap16TilesExpanded + 6)));
2843 rom()->WriteLong(SnesToPc(0x02F51F), PcToSnes(kMap16TilesExpanded)));
2845 rom()->WriteLong(SnesToPc(0x02F526), PcToSnes(kMap16TilesExpanded + 4)));
2847 rom()->WriteLong(SnesToPc(0x02F52F), PcToSnes(kMap16TilesExpanded + 2)));
2849 rom()->WriteLong(SnesToPc(0x02F536), PcToSnes(kMap16TilesExpanded + 6)));
2850
2852 rom()->WriteShort(SnesToPc(0x02FE1C), PcToSnes(kMap16TilesExpanded)));
2854 rom()->WriteShort(SnesToPc(0x02FE23), PcToSnes(kMap16TilesExpanded + 4)));
2856 rom()->WriteShort(SnesToPc(0x02FE2C), PcToSnes(kMap16TilesExpanded + 2)));
2858 rom()->WriteShort(SnesToPc(0x02FE33), PcToSnes(kMap16TilesExpanded + 6)));
2859
2860 RETURN_IF_ERROR(rom()->WriteByte(
2861 SnesToPc(0x02FD28),
2862 static_cast<uint8_t>(PcToSnes(kMap16TilesExpanded) >> 16)));
2863 RETURN_IF_ERROR(rom()->WriteByte(
2864 SnesToPc(0x02FD39),
2865 static_cast<uint8_t>(PcToSnes(kMap16TilesExpanded) >> 16)));
2866
2867 int tpos = kMap16TilesExpanded;
2868 for (int i = 0; i < NumberOfMap16Ex; i += 1) // 4096
2869 {
2871 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_)));
2872 tpos += 2;
2874 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_)));
2875 tpos += 2;
2877 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_)));
2878 tpos += 2;
2880 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_)));
2881 tpos += 2;
2882 }
2883
2884 return absl::OkStatus();
2885}
2886
2888 util::logf("Saving Map16 Tiles");
2889 int tpos = kMap16Tiles;
2890 // 3760
2891 for (int i = 0; i < NumberOfMap16; i += 1) {
2893 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_)))
2894 tpos += 2;
2896 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_)))
2897 tpos += 2;
2899 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_)))
2900 tpos += 2;
2902 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_)))
2903 tpos += 2;
2904 }
2905 return absl::OkStatus();
2906}
2907
2914
2915absl::Status Overworld::SaveExits() {
2917 return absl::OkStatus();
2918}
2919
2920absl::Status Overworld::SaveItems() {
2922 return absl::OkStatus();
2923}
2924
2926 util::logf("Saving Map Overlays");
2927
2928 // Generate the new overlay code that handles interactive overlays
2929 std::vector<uint8_t> new_overlay_code = {
2930 0xC2, 0x30, // REP #$30
2931 0xA5, 0x8A, // LDA $8A
2932 0x0A, 0x18, // ASL : CLC
2933 0x65, 0x8A, // ADC $8A
2934 0xAA, // TAX
2935 0xBF, 0x00, 0x00, 0x00, // LDA, X
2936 0x85, 0x00, // STA $00
2937 0xBF, 0x00, 0x00, 0x00, // LDA, X +2
2938 0x85, 0x02, // STA $02
2939 0x4B, // PHK
2940 0xF4, 0x00, 0x00, // This position +3 ?
2941 0xDC, 0x00, 0x00, // JML [$00 00]
2942 0xE2, 0x30, // SEP #$30
2943 0xAB, // PLB
2944 0x6B, // RTL
2945 };
2946
2947 // Write overlay code to ROM
2948 constexpr int kOverlayCodeStart = 0x077657;
2949 RETURN_IF_ERROR(rom()->WriteVector(kOverlayCodeStart, new_overlay_code));
2950
2951 // Set up overlay pointers
2952 int ptr_start = kOverlayCodeStart + 0x20;
2953 int snes_ptr_start = PcToSnes(ptr_start);
2954
2955 // Write overlay pointer addresses in the code
2956 RETURN_IF_ERROR(rom()->WriteLong(kOverlayCodeStart + 10, snes_ptr_start));
2957 RETURN_IF_ERROR(rom()->WriteLong(kOverlayCodeStart + 16, snes_ptr_start + 2));
2958
2959 int pea_addr = PcToSnes(kOverlayCodeStart + 27);
2960 RETURN_IF_ERROR(rom()->WriteShort(kOverlayCodeStart + 23, pea_addr));
2961
2962 // Write overlay data to expanded space
2963 constexpr int kExpandedOverlaySpace = 0x120000;
2964 int pos = kExpandedOverlaySpace;
2965 int ptr_pos = kOverlayCodeStart + 32;
2966
2967 for (int i = 0; i < kNumOverworldMaps; i++) {
2968 int snes_addr = PcToSnes(pos);
2969 RETURN_IF_ERROR(rom()->WriteLong(ptr_pos, snes_addr & 0xFFFFFF));
2970 ptr_pos += 3;
2971
2972 // Write overlay data for each map that has overlays
2973 if (overworld_maps_[i].has_overlay()) {
2974 const auto& overlay_data = overworld_maps_[i].overlay_data();
2975 for (size_t t = 0; t < overlay_data.size(); t += 3) {
2976 if (t + 2 < overlay_data.size()) {
2977 // Generate LDA/STA sequence for each overlay tile
2978 RETURN_IF_ERROR(rom()->WriteByte(pos, 0xA9)); // LDA #$
2979 RETURN_IF_ERROR(rom()->WriteShort(
2980 pos + 1, overlay_data[t] | (overlay_data[t + 1] << 8)));
2981 pos += 3;
2982
2983 RETURN_IF_ERROR(rom()->WriteByte(pos, 0x8D)); // STA $xxxx
2984 RETURN_IF_ERROR(rom()->WriteShort(pos + 1, overlay_data[t + 2]));
2985 pos += 3;
2986 }
2987 }
2988 }
2989
2990 RETURN_IF_ERROR(rom()->WriteByte(pos, 0x6B)); // RTL
2991 pos++;
2992 }
2993
2994 return absl::OkStatus();
2995}
2996
2998 util::logf("Saving Overworld Tiles Types");
2999
3000 for (int i = 0; i < kNumTileTypes; i++) {
3002 rom()->WriteByte(overworldTilesType + i, all_tiles_types_[i]));
3003 }
3004
3005 return absl::OkStatus();
3006}
3007
3009 util::logf("Loading Diggable Tiles");
3010
3011 // Check if custom diggable tiles are enabled
3012 ASSIGN_OR_RETURN(uint8_t enable_flag,
3014
3015 if (enable_flag != 0x00 && enable_flag != 0xFF) {
3016 // Custom table is enabled, load from ROM
3017 std::array<uint8_t, kDiggableTilesBitfieldSize> bitfield;
3018 for (int i = 0; i < kDiggableTilesBitfieldSize; ++i) {
3019 ASSIGN_OR_RETURN(bitfield[i],
3020 rom()->ReadByte(kOverworldCustomDiggableTilesArray + i));
3021 }
3022 diggable_tiles_.FromBytes(bitfield.data());
3023 } else {
3024 // Use vanilla defaults
3026 }
3027
3028 return absl::OkStatus();
3029}
3030
3032 // Diggable tiles require v3+ (custom table at 0x140980+)
3033 const auto version = OverworldVersionHelper::GetVersion(*rom_);
3034 cached_version_ = version;
3036 return absl::OkStatus(); // Skip for vanilla/v1/v2
3037 }
3038
3039 util::logf("Saving Diggable Tiles");
3040
3041 // Write enable flag
3043
3044 // Write the 64-byte bitfield
3045 const auto& bitfield = diggable_tiles_.GetRawData();
3046 for (int i = 0; i < kDiggableTilesBitfieldSize; ++i) {
3048 rom()->WriteByte(kOverworldCustomDiggableTilesArray + i, bitfield[i]));
3049 }
3050
3051 return absl::OkStatus();
3052}
3053
3055 util::logf("Auto-detecting Diggable Tiles");
3056
3058
3059 // Iterate through all Map16 tiles and check if they're diggable
3060 for (uint16_t tile_id = 0; tile_id < static_cast<uint16_t>(tiles16_.size()) &&
3061 tile_id < kMaxDiggableTileId;
3062 ++tile_id) {
3064 diggable_tiles_.SetDiggable(tile_id, true);
3065 }
3066 }
3067
3068 util::logf("Auto-detected %d diggable tiles",
3070 return absl::OkStatus();
3071}
3072
3073absl::Status Overworld::SaveCustomOverworldASM(bool enable_bg_color,
3074 bool enable_main_palette,
3075 bool enable_mosaic,
3076 bool enable_gfx_groups,
3077 bool enable_subscreen_overlay,
3078 bool enable_animated) {
3079 // Check ROM version - this function requires at least v2 for basic features
3082 return absl::OkStatus(); // Cannot apply custom ASM settings to vanilla/v1
3083 }
3084
3085 util::logf("Applying Custom Overworld ASM");
3086
3087 // v2+ features: BG color, main palette enable flags
3089 uint8_t enable_value = enable_bg_color ? 0xFF : 0x00;
3091 rom()->WriteByte(OverworldCustomAreaSpecificBGEnabled, enable_value));
3092
3093 enable_value = enable_main_palette ? 0xFF : 0x00;
3095 rom()->WriteByte(OverworldCustomMainPaletteEnabled, enable_value));
3096
3097 // Write the main palette table
3098 for (int i = 0; i < kNumOverworldMaps; i++) {
3100 overworld_maps_[i].main_palette()));
3101 }
3102 }
3103
3104 // v3+ features: mosaic, gfx groups, animated, overlays
3106 uint8_t enable_value = enable_mosaic ? 0xFF : 0x00;
3108 rom()->WriteByte(OverworldCustomMosaicEnabled, enable_value));
3109
3110 enable_value = enable_gfx_groups ? 0xFF : 0x00;
3112 rom()->WriteByte(OverworldCustomTileGFXGroupEnabled, enable_value));
3113
3114 enable_value = enable_animated ? 0xFF : 0x00;
3116 rom()->WriteByte(OverworldCustomAnimatedGFXEnabled, enable_value));
3117
3118 enable_value = enable_subscreen_overlay ? 0xFF : 0x00;
3120 rom()->WriteByte(OverworldCustomSubscreenOverlayEnabled, enable_value));
3121
3122 // Write the mosaic table
3123 for (int i = 0; i < kNumOverworldMaps; i++) {
3124 const auto& mosaic = overworld_maps_[i].mosaic_expanded();
3125 // .... udlr bit format
3126 uint8_t mosaic_byte = (mosaic[0] ? 0x08 : 0x00) | // up
3127 (mosaic[1] ? 0x04 : 0x00) | // down
3128 (mosaic[2] ? 0x02 : 0x00) | // left
3129 (mosaic[3] ? 0x01 : 0x00); // right
3130
3132 rom()->WriteByte(OverworldCustomMosaicArray + i, mosaic_byte));
3133 }
3134
3135 // Write the main and animated gfx tiles table
3136 for (int i = 0; i < kNumOverworldMaps; i++) {
3137 for (int j = 0; j < 8; j++) {
3139 rom()->WriteByte(OverworldCustomTileGFXGroupArray + (i * 8) + j,
3140 overworld_maps_[i].custom_tileset(j)));
3141 }
3143 overworld_maps_[i].animated_gfx()));
3144 }
3145
3146 // Write the subscreen overlay table
3147 for (int i = 0; i < kNumOverworldMaps; i++) {
3149 rom()->WriteShort(OverworldCustomSubscreenOverlayArray + (i * 2),
3150 overworld_maps_[i].subscreen_overlay()));
3151 }
3152 }
3153
3154 return absl::OkStatus();
3155}
3156
3158 // Only write to custom address space for v2+ ROMs
3160 return absl::OkStatus(); // Vanilla/v1 ROM - skip custom address writes
3161 }
3162
3163 util::logf("Saving Area Specific Background Colors");
3164
3165 // Write area-specific background colors if enabled
3166 for (int i = 0; i < kNumOverworldMaps; i++) {
3167 uint16_t bg_color = overworld_maps_[i].area_specific_bg_color();
3168 RETURN_IF_ERROR(rom()->WriteShort(
3169 OverworldCustomAreaSpecificBGPalette + (i * 2), bg_color));
3170 }
3171
3172 return absl::OkStatus();
3173}
3174
3176 util::logf("Saving Map Properties");
3177 for (int i = 0; i < kDarkWorldMapIdStart; i++) {
3178 RETURN_IF_ERROR(rom()->WriteByte(kAreaGfxIdPtr + i,
3179 overworld_maps_[i].area_graphics()));
3181 overworld_maps_[i].area_palette()));
3182 RETURN_IF_ERROR(rom()->WriteByte(kOverworldSpriteset + i,
3183 overworld_maps_[i].sprite_graphics(0)));
3185 rom()->WriteByte(kOverworldSpriteset + kDarkWorldMapIdStart + i,
3186 overworld_maps_[i].sprite_graphics(1)));
3189 overworld_maps_[i].sprite_graphics(2)));
3191 overworld_maps_[i].sprite_palette(0)));
3194 overworld_maps_[i].sprite_palette(1)));
3195 RETURN_IF_ERROR(rom()->WriteByte(
3197 overworld_maps_[i].sprite_palette(2)));
3198 }
3199
3200 for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) {
3201 RETURN_IF_ERROR(rom()->WriteByte(kAreaGfxIdPtr + i,
3202 overworld_maps_[i].area_graphics()));
3203 RETURN_IF_ERROR(rom()->WriteByte(kOverworldSpriteset + i,
3204 overworld_maps_[i].sprite_graphics(0)));
3206 rom()->WriteByte(kOverworldSpriteset + kDarkWorldMapIdStart + i,
3207 overworld_maps_[i].sprite_graphics(1)));
3210 overworld_maps_[i].sprite_graphics(2)));
3212 overworld_maps_[i].area_palette()));
3215 overworld_maps_[i].sprite_palette(0)));
3216 RETURN_IF_ERROR(rom()->WriteByte(
3218 overworld_maps_[i].sprite_palette(1)));
3219 RETURN_IF_ERROR(rom()->WriteByte(kOverworldSpritePaletteIds + 192 + i,
3220 overworld_maps_[i].sprite_palette(2)));
3221 }
3222
3223 return absl::OkStatus();
3224}
3225
3226absl::Status Overworld::SaveMusic() {
3227 util::logf("Saving Music Data");
3228
3229 // Save music data for Light World maps
3230 for (int i = 0; i < kDarkWorldMapIdStart; i++) {
3232 overworld_maps_[i].area_music(0)));
3233 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicZelda + i,
3234 overworld_maps_[i].area_music(1)));
3236 overworld_maps_[i].area_music(2)));
3238 overworld_maps_[i].area_music(3)));
3239 }
3240
3241 // Save music data for Dark World maps
3242 for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) {
3245 overworld_maps_[i].area_music(0)));
3246 }
3247
3248 return absl::OkStatus();
3249}
3250
3252 util::logf("Saving V3 Area Sizes");
3253
3254 // Check if this is a v3 ROM
3255 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
3256 if (asm_version < 3 || asm_version == 0xFF) {
3257 return absl::OkStatus(); // Not a v3 ROM, nothing to do
3258 }
3259
3260 // Save area sizes to the expanded table
3261 for (int i = 0; i < kNumOverworldMaps; i++) {
3262 uint8_t area_size_byte =
3263 static_cast<uint8_t>(overworld_maps_[i].area_size());
3264 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, area_size_byte));
3265 }
3266
3267 // Save message IDs to expanded table
3268 for (int i = 0; i < kNumOverworldMaps; i++) {
3269 uint16_t message_id = overworld_maps_[i].message_id();
3271 rom()->WriteShort(kOverworldMessagesExpanded + (i * 2), message_id));
3272 }
3273
3274 return absl::OkStatus();
3275}
3276
3277} // 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:24
static Flags & get()
Definition features.h:92
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:637
std::vector< uint16_t > tiles32_list_
Definition overworld.h:644
absl::Status Load(Rom *rom)
Load all overworld data from ROM.
Definition overworld.cc:36
std::vector< OverworldItem > all_items_
Definition overworld.h:638
void OrganizeMapTiles(std::vector< uint8_t > &bytes, std::vector< uint8_t > &bytes2, int i, int sx, int sy, int &ttpos)
Definition overworld.cc:617
zelda3_version_pointers version_constants() const
Get version-specific ROM addresses.
Definition overworld.h:225
std::array< int, kNumOverworldMaps > map_pointers1
Definition overworld.h:655
std::vector< gfx::Tile32 > tiles32_unique_
Definition overworld.h:642
absl::Status SaveMapProperties()
Save per-area graphics, palettes, and messages.
absl::Status SaveMap32Tiles()
Save tile32 definitions to ROM.
std::vector< OverworldEntrance > all_entrances_
Definition overworld.h:635
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:607
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:647
void AssignWorldTiles(int x, int y, int sx, int sy, int tpos, OverworldBlockset &world)
Definition overworld.cc:572
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:648
auto current_graphics() const
Definition overworld.h:498
std::unordered_map< uint64_t, GraphicsConfigCache > gfx_config_cache_
Definition overworld.h:624
void LoadTileTypes()
Load tile type collision data.
Definition overworld.cc:996
absl::Status CreateTile32Tilemap()
Build tile32 tilemap from current tile16 data.
std::array< int, kNumOverworldMaps > map_pointers1_id
Definition overworld.h:653
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:584
bool HasExpandedPointerTables() const
Check if the ROM has expanded pointer tables for tail maps.
Definition overworld.h:425
static constexpr int kMaxCachedConfigs
Definition overworld.h:631
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:595
auto mutable_overworld_map(int i)
Definition overworld.h:479
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:888
uint64_t ComputeGraphicsConfigHash(int map_index)
Compute hash of graphics configuration for cache lookup.
std::vector< OverworldMap > overworld_maps_
Definition overworld.h:634
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:649
OverworldVersion cached_version_
Definition overworld.h:605
std::array< int, kNumOverworldMaps > map_pointers2_id
Definition overworld.h:654
absl::Status LoadOverworldMaps()
Load overworld map tile data.
Definition overworld.cc:747
std::vector< gfx::Tile16 > tiles16_
Definition overworld.h:640
absl::Status AutoDetectDiggableTiles()
absl::Status AssembleMap16Tiles()
Definition overworld.cc:536
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:651
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:637
std::array< int, kNumOverworldMaps > map_pointers2
Definition overworld.h:656
absl::StatusOr< uint16_t > GetTile16ForTile32(int index, int quadrant, int dimension, const uint32_t *map32address)
Definition overworld.cc:465
std::array< std::vector< uint8_t >, kNumOverworldMaps > map_data_p2
Definition overworld.h:652
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:615
absl::Status SaveMapOverlays()
Save interactive overlay data to ROM.
absl::Status AssembleMap32Tiles()
Definition overworld.cc:476
DiggableTiles diggable_tiles_
Definition overworld.h:650
static constexpr int kMaxBuiltMaps
Definition overworld.h:614
OverworldBlockset & GetMapTiles(int world_type)
Definition overworld.h:459
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:636
#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:424
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.
Definition editor.h:35
constexpr int kDiggableTilesBitfieldSize
constexpr int kAreaGfxIdPtr
Definition overworld.h:117
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:141
constexpr int kNumMapsPerWorld
Definition overworld.h:202
constexpr int kOverworldSpriteset
Definition overworld.h:110
constexpr int kMap16ExpandedFlagPos
Definition overworld.h:165
constexpr int LimitOfMap32
Definition overworld.h:199
constexpr int NumberOfMap16Ex
Definition overworld.h:198
absl::StatusOr< std::vector< OverworldEntrance > > LoadEntrances(Rom *rom)
absl::Status SaveItems(Rom *rom, const std::vector< OverworldItem > &items)
constexpr int kOverworldScreenTileMapChangeByScreen1
Definition overworld.h:146
constexpr int kOverworldScreenTileMapChangeByScreen2Expanded
constexpr int kOverworldMapDataOverflow
Definition overworld.h:151
constexpr int kOverworldMapSizeHighByte
Definition overworld.h:132
absl::StatusOr< std::vector< OverworldItem > > LoadItems(Rom *rom, std::vector< OverworldMap > &overworld_maps)
constexpr int overworldSpritesBeginingExpanded
Definition overworld.h:167
constexpr int kNumTileTypes
Definition overworld.h:192
constexpr int NumberOfMap32
Definition overworld.h:201
constexpr int kOverworldScreenSize
Definition overworld.h:143
constexpr int kOverworldScreenTileMapChangeByScreen4
Definition overworld.h:149
constexpr int kNumTile16Individual
Definition overworld.h:195
constexpr int kSpecialWorldMapIdStart
constexpr int OverworldCustomMosaicArray
constexpr int kOverworldCustomDiggableTilesEnabled
constexpr int kOverworldTransitionPositionXExpanded
constexpr int kMap16Tiles
Definition overworld.h:193
constexpr int overworldSpritesAgahnimExpanded
Definition overworld.h:169
constexpr int OverworldCustomAnimatedGFXEnabled
constexpr int OverworldCustomMainPaletteEnabled
constexpr int kNumOverworldMaps
Definition common.h:85
constexpr int OverworldCustomMainPaletteArray
constexpr int kOverworldTransitionPositionYExpanded
constexpr int kOverworldMapParentIdExpanded
constexpr int kOverworldMusicBeginning
Definition overworld.h:120
std::vector< std::vector< uint16_t > > OverworldBlockset
Represents tile32 data for the overworld.
constexpr int kMap32TileBLExpanded
Definition overworld.h:161
AreaSizeEnum
Area size enumeration for v3+ ROMs.
constexpr int kOverworldTransitionPositionX
Definition overworld.h:142
constexpr int kOverworldMusicDarkWorld
Definition overworld.h:124
constexpr int kOverworldSpecialPalGroup
Definition overworld.h:112
constexpr int kOverworldScreenSizeForLoading
Definition overworld.h:144
constexpr int kOverworldSpritePaletteIds
Definition overworld.h:108
constexpr int overworldTilesType
Definition overworld.h:185
constexpr int transition_target_westExpanded
absl::StatusOr< std::vector< OverworldExit > > LoadExits(Rom *rom)
constexpr int kMap32TileBRExpanded
Definition overworld.h:162
constexpr int kMap32TileCountExpanded
Definition overworld.h:163
constexpr int kTransitionTargetWest
Definition overworld.h:154
constexpr int OverworldCustomASMHasBeenApplied
Definition common.h:89
constexpr int kOverworldMusicAgahnim
Definition overworld.h:123
constexpr int kOverworldSpritesZelda
Definition overworld.h:115
constexpr int kOverworldMapParentId
Definition overworld.h:140
constexpr int kOverworldCustomDiggableTilesArray
constexpr int kMap32ExpandedFlagPos
Definition overworld.h:164
@ kZSCustomV1
Basic features, expanded pointers.
@ kVanilla
0xFF in ROM, no ZScream ASM applied
constexpr int kOverworldMessagesExpanded
constexpr int kOverworldMusicMasterSword
Definition overworld.h:122
constexpr int kOverworldScreenTileMapChangeByScreen3Expanded
constexpr int kOverworldMusicZelda
Definition overworld.h:121
constexpr int transition_target_northExpanded
constexpr int NumberOfMap16
Definition overworld.h:197
constexpr int kOverworldMapSize
Definition overworld.h:129
constexpr int kOverworldScreenTileMapChangeByScreen2
Definition overworld.h:147
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 kOverworldCompressedMapPos
Definition overworld.h:189
constexpr int kMaxDiggableTileId
constexpr int kOverworldScreenTileMapChangeByScreen4Expanded
constexpr int kOverworldSpritesBeginning
Definition overworld.h:113
constexpr int kOverworldScreenTileMapChangeByScreen3
Definition overworld.h:148
constexpr int kOverworldScreenTileMapChangeByScreen1Expanded
constexpr int OverworldCustomTileGFXGroupArray
constexpr int kMap16TilesExpanded
Definition overworld.h:159
constexpr int OverworldCustomSubscreenOverlayEnabled
constexpr int OverworldCustomAreaSpecificBGPalette
constexpr int kOverworldSpritesAgahnim
Definition overworld.h:114
constexpr int kTransitionTargetNorth
Definition overworld.h:153
constexpr int overworldSpritesZeldaExpanded
Definition overworld.h:168
constexpr int kOverworldCompressedOverflowPos
Definition overworld.h:190
constexpr int kOverlayCodeStart
constexpr int kMap32TileTRExpanded
Definition overworld.h:160
absl::Status SaveExits(Rom *rom, const std::vector< OverworldExit > &exits)
constexpr int kOverworldMapPaletteIds
Definition overworld.h:107
constexpr int kOverworldSpecialGfxGroup
Definition overworld.h:111
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 kMap32TileTL
Definition zelda.h:105
uint32_t kMap32TileBL
Definition zelda.h:107
uint32_t kMap32TileBR
Definition zelda.h:108