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