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