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 <future>
5#include <mutex>
6#include <set>
7#include <thread>
8#include <unordered_map>
9#include <vector>
10
11#include "absl/status/status.h"
12#include "app/core/features.h"
14#include "app/gfx/compression.h"
15#include "app/gfx/snes_tile.h"
16#include "app/rom.h"
17#include "app/snes.h"
20#include "util/hex.h"
21#include "util/log.h"
22#include "util/macro.h"
23
24namespace yaze {
25namespace zelda3 {
26
27absl::Status Overworld::Load(Rom* rom) {
28 gfx::ScopedTimer timer("Overworld::Load");
29
30 if (rom->size() == 0) {
31 return absl::InvalidArgumentError("ROM file not loaded");
32 }
33 rom_ = rom;
34
35 // Phase 1: Tile Assembly (can be parallelized)
36 {
37 gfx::ScopedTimer assembly_timer("AssembleTiles");
40 }
41
42 // Phase 2: Map Decompression (major bottleneck - now parallelized)
43 {
44 gfx::ScopedTimer decompression_timer("DecompressAllMapTiles");
46 }
47
48 // Phase 3: Map Object Creation (fast)
49 {
50 gfx::ScopedTimer map_creation_timer("CreateOverworldMapObjects");
51 for (int map_index = 0; map_index < kNumOverworldMaps; ++map_index)
52 overworld_maps_.emplace_back(map_index, rom_);
53
54 // Populate map_parent_ array with parent information from each map
55 for (int map_index = 0; map_index < kNumOverworldMaps; ++map_index) {
56 map_parent_[map_index] = overworld_maps_[map_index].parent();
57 }
58 }
59
60 // Phase 4: Map Configuration
61 uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
62 if (asm_version >= 3) {
64 } else {
66 }
67
68 // Phase 5: Data Loading (with individual timing)
69 {
70 gfx::ScopedTimer data_loading_timer("LoadOverworldData");
71
72 {
73 gfx::ScopedTimer tile_types_timer("LoadTileTypes");
75 }
76
77 {
78 gfx::ScopedTimer entrances_timer("LoadEntrances");
80 }
81
82 {
83 gfx::ScopedTimer holes_timer("LoadHoles");
85 }
86
87 {
88 gfx::ScopedTimer exits_timer("LoadExits");
90 }
91
92 {
93 gfx::ScopedTimer items_timer("LoadItems");
95 }
96
97 {
98 gfx::ScopedTimer overworld_maps_timer("LoadOverworldMaps");
100 }
101
102 {
103 gfx::ScopedTimer sprites_timer("LoadSprites");
105 }
106 }
107
108 is_loaded_ = true;
109 return absl::OkStatus();
110}
111
113 for (int i = 128; i < 145; i++) {
114 overworld_maps_[i].SetAsSmallMap(0);
115 }
116
117 overworld_maps_[129].SetAsLargeMap(129, 0);
118 overworld_maps_[130].SetAsLargeMap(129, 1);
119 overworld_maps_[137].SetAsLargeMap(129, 2);
120 overworld_maps_[138].SetAsLargeMap(129, 3);
121 overworld_maps_[136].SetAsSmallMap();
122
123 std::array<bool, kNumMapsPerWorld> map_checked;
124 std::ranges::fill(map_checked, false);
125
126 int xx = 0;
127 int yy = 0;
128 while (true) {
129 if (int i = xx + (yy * 8); map_checked[i] == false) {
130 if (overworld_maps_[i].is_large_map()) {
131 map_checked[i] = true;
132 overworld_maps_[i].SetAsLargeMap(i, 0);
133 overworld_maps_[i + 64].SetAsLargeMap(i + 64, 0);
134
135 map_checked[i + 1] = true;
136 overworld_maps_[i + 1].SetAsLargeMap(i, 1);
137 overworld_maps_[i + 65].SetAsLargeMap(i + 64, 1);
138
139 map_checked[i + 8] = true;
140 overworld_maps_[i + 8].SetAsLargeMap(i, 2);
141 overworld_maps_[i + 72].SetAsLargeMap(i + 64, 2);
142
143 map_checked[i + 9] = true;
144 overworld_maps_[i + 9].SetAsLargeMap(i, 3);
145 overworld_maps_[i + 73].SetAsLargeMap(i + 64, 3);
146 xx++;
147 } else {
148 overworld_maps_[i].SetAsSmallMap();
149 overworld_maps_[i + 64].SetAsSmallMap();
150 map_checked[i] = true;
151 }
152 }
153
154 xx++;
155 if (xx >= 8) {
156 xx = 0;
157 yy += 1;
158 if (yy >= 8) {
159 break;
160 }
161 }
162 }
163}
164
169void Overworld::AssignMapSizes(std::vector<OverworldMap>& maps) {
170 std::vector<bool> map_checked(kNumOverworldMaps, false);
171
172 int xx = 0;
173 int yy = 0;
174 int world = 0;
175
176 while (true) {
177 int i = world + xx + (yy * 8);
178
179 if (i >= static_cast<int>(map_checked.size())) {
180 break;
181 }
182
183 if (!map_checked[i]) {
184 switch (maps[i].area_size()) {
186 map_checked[i] = true;
187 maps[i].SetAreaSize(AreaSizeEnum::SmallArea);
188 break;
189
191 map_checked[i] = true;
192 maps[i].SetAsLargeMap(i, 0);
193
194 if (i + 1 < static_cast<int>(maps.size())) {
195 map_checked[i + 1] = true;
196 maps[i + 1].SetAsLargeMap(i, 1);
197 }
198
199 if (i + 8 < static_cast<int>(maps.size())) {
200 map_checked[i + 8] = true;
201 maps[i + 8].SetAsLargeMap(i, 2);
202 }
203
204 if (i + 9 < static_cast<int>(maps.size())) {
205 map_checked[i + 9] = true;
206 maps[i + 9].SetAsLargeMap(i, 3);
207 }
208
209 xx++;
210 break;
211
213 map_checked[i] = true;
214 // CRITICAL FIX: Set parent for wide area maps
215 // Map i is parent (left), map i+1 is child (right)
216 maps[i].SetParent(i); // Parent points to itself
217 maps[i].SetAreaSize(AreaSizeEnum::WideArea);
218
219 if (i + 1 < static_cast<int>(maps.size())) {
220 map_checked[i + 1] = true;
221 maps[i + 1].SetParent(i); // Child points to parent
222 maps[i + 1].SetAreaSize(AreaSizeEnum::WideArea);
223 }
224
225 xx++;
226 break;
227
229 map_checked[i] = true;
230 // CRITICAL FIX: Set parent for tall area maps
231 // Map i is parent (top), map i+8 is child (bottom)
232 maps[i].SetParent(i); // Parent points to itself
233 maps[i].SetAreaSize(AreaSizeEnum::TallArea);
234
235 if (i + 8 < static_cast<int>(maps.size())) {
236 map_checked[i + 8] = true;
237 maps[i + 8].SetParent(i); // Child points to parent
238 maps[i + 8].SetAreaSize(AreaSizeEnum::TallArea);
239 }
240 break;
241 }
242 }
243
244 xx++;
245 if (xx >= 8) {
246 xx = 0;
247 yy += 1;
248
249 if (yy >= 8) {
250 yy = 0;
251 world += 0x40;
252 }
253 }
254 }
255}
256
257absl::Status Overworld::ConfigureMultiAreaMap(int parent_index, AreaSizeEnum size) {
258 if (parent_index < 0 || parent_index >= kNumOverworldMaps) {
259 return absl::InvalidArgumentError(
260 absl::StrFormat("Invalid parent index: %d", parent_index));
261 }
262
263 // Check ROM version
264 uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
265
266 // Version requirements:
267 // - Vanilla (0xFF): Supports Small and Large only
268 // - v1-v2: Supports Small and Large only
269 // - v3+: Supports all 4 sizes (Small, Large, Wide, Tall)
270 if ((size == AreaSizeEnum::WideArea || size == AreaSizeEnum::TallArea) &&
271 (asm_version < 3 || asm_version == 0xFF)) {
272 return absl::FailedPreconditionError(
273 "Wide and Tall areas require ZSCustomOverworld v3+");
274 }
275
276 LOG_DEBUG("Overworld", "ConfigureMultiAreaMap: parent=%d, current_size=%d, new_size=%d, version=%d",
277 parent_index, static_cast<int>(overworld_maps_[parent_index].area_size()),
278 static_cast<int>(size), asm_version);
279
280 // CRITICAL: First, get OLD siblings (before changing) so we can reset them
281 std::vector<int> old_siblings;
282 auto old_size = overworld_maps_[parent_index].area_size();
283 int old_parent = overworld_maps_[parent_index].parent();
284
285 switch (old_size) {
287 old_siblings = {old_parent, old_parent + 1, old_parent + 8, old_parent + 9};
288 break;
290 old_siblings = {old_parent, old_parent + 1};
291 break;
293 old_siblings = {old_parent, old_parent + 8};
294 break;
295 default:
296 old_siblings = {parent_index}; // Was small, just this map
297 break;
298 }
299
300 // Reset all old siblings to SmallArea first (clean slate)
301 for (int old_sibling : old_siblings) {
302 if (old_sibling >= 0 && old_sibling < kNumOverworldMaps) {
303 overworld_maps_[old_sibling].SetAsSmallMap(old_sibling);
304 }
305 }
306
307 // Now configure NEW siblings based on requested size
308 std::vector<int> new_siblings;
309
310 switch (size) {
312 // Just configure this single map as small
313 overworld_maps_[parent_index].SetParent(parent_index);
314 overworld_maps_[parent_index].SetAreaSize(AreaSizeEnum::SmallArea);
315 new_siblings = {parent_index};
316 break;
317
319 new_siblings = {parent_index, parent_index + 1, parent_index + 8, parent_index + 9};
320 for (size_t i = 0; i < new_siblings.size(); ++i) {
321 int sibling = new_siblings[i];
322 if (sibling < 0 || sibling >= kNumOverworldMaps) continue;
323 overworld_maps_[sibling].SetAsLargeMap(parent_index, i);
324 }
325 break;
326
328 new_siblings = {parent_index, parent_index + 1};
329 for (int sibling : new_siblings) {
330 if (sibling < 0 || sibling >= kNumOverworldMaps) continue;
331 overworld_maps_[sibling].SetParent(parent_index);
332 overworld_maps_[sibling].SetAreaSize(AreaSizeEnum::WideArea);
333 }
334 break;
335
337 new_siblings = {parent_index, parent_index + 8};
338 for (int sibling : new_siblings) {
339 if (sibling < 0 || sibling >= kNumOverworldMaps) continue;
340 overworld_maps_[sibling].SetParent(parent_index);
341 overworld_maps_[sibling].SetAreaSize(AreaSizeEnum::TallArea);
342 }
343 break;
344 }
345
346 // Update ROM data for ALL affected siblings (old + new)
347 std::set<int> all_affected;
348 for (int s : old_siblings) all_affected.insert(s);
349 for (int s : new_siblings) all_affected.insert(s);
350
351 if (asm_version >= 3 && asm_version != 0xFF) {
352 // v3+: Update expanded tables
353 for (int sibling : all_affected) {
354 if (sibling < 0 || sibling >= kNumOverworldMaps) continue;
355
357 overworld_maps_[sibling].parent()));
358 RETURN_IF_ERROR(rom()->WriteByte(
359 kOverworldScreenSize + sibling,
360 static_cast<uint8_t>(overworld_maps_[sibling].area_size())));
361 }
362 } else if (asm_version < 3 && asm_version != 0xFF) {
363 // v1/v2: Update basic parent table
364 for (int sibling : all_affected) {
365 if (sibling < 0 || sibling >= kNumOverworldMaps) continue;
366
367 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + sibling,
368 overworld_maps_[sibling].parent()));
369 RETURN_IF_ERROR(rom()->WriteByte(
370 kOverworldScreenSize + (sibling & 0x3F),
371 static_cast<uint8_t>(overworld_maps_[sibling].area_size())));
372 }
373 } else {
374 // Vanilla: Update parent and screen size tables
375 for (int sibling : all_affected) {
376 if (sibling < 0 || sibling >= kNumOverworldMaps) continue;
377
378 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + sibling,
379 overworld_maps_[sibling].parent()));
380 RETURN_IF_ERROR(rom()->WriteByte(
381 kOverworldScreenSize + (sibling & 0x3F),
382 (overworld_maps_[sibling].area_size() == AreaSizeEnum::LargeArea) ? 0x00 : 0x01));
383 }
384 }
385
386 LOG_DEBUG("Overworld", "Configured %s area: parent=%d, old_siblings=%zu, new_siblings=%zu",
387 (size == AreaSizeEnum::LargeArea) ? "Large" :
388 (size == AreaSizeEnum::WideArea) ? "Wide" :
389 (size == AreaSizeEnum::TallArea) ? "Tall" : "Small",
390 parent_index, old_siblings.size(), new_siblings.size());
391
392 return absl::OkStatus();
393}
394
395absl::StatusOr<uint16_t> Overworld::GetTile16ForTile32(
396 int index, int quadrant, int dimension, const uint32_t* map32address) {
398 auto arg1, rom()->ReadByte(map32address[dimension] + quadrant + (index)));
399 ASSIGN_OR_RETURN(auto arg2,
400 rom()->ReadWord(map32address[dimension] + (index) +
401 (quadrant <= 1 ? 4 : 5)));
402 return (uint16_t)(arg1 +
403 (((arg2 >> (quadrant % 2 == 0 ? 4 : 0)) & 0x0F) * 256));
404}
405
407 constexpr int kMap32TilesLength = 0x33F0;
408 int num_tile32 = kMap32TilesLength;
409 uint32_t map32address[4] = {rom()->version_constants().kMap32TileTL,
410 rom()->version_constants().kMap32TileTR,
411 rom()->version_constants().kMap32TileBL,
412 rom()->version_constants().kMap32TileBR};
413
414 // Check if expanded tile32 data is actually present in ROM
415 // The flag position should contain 0x04 for vanilla, something else for expanded
416 uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
417 uint8_t expanded_flag = rom()->data()[kMap32ExpandedFlagPos];
418 util::logf("Expanded tile32 flag: %d", expanded_flag);
419 if (expanded_flag != 0x04 || asm_version >= 3) {
420 // ROM has expanded tile32 data - use expanded addresses
421 map32address[0] = rom()->version_constants().kMap32TileTL;
422 map32address[1] = kMap32TileTRExpanded;
423 map32address[2] = kMap32TileBLExpanded;
424 map32address[3] = kMap32TileBRExpanded;
425 num_tile32 = kMap32TileCountExpanded;
426 expanded_tile32_ = true;
427 }
428 // Otherwise use vanilla addresses (already set above)
429
430 // Loop through each 32x32 pixel tile in the rom
431 for (int i = 0; i < num_tile32; i += 6) {
432 // Loop through each quadrant of the 32x32 pixel tile.
433 for (int k = 0; k < 4; k++) {
434 // Generate the 16-bit tile for the current quadrant of the current
435 // 32x32 pixel tile.
437 uint16_t tl,
438 GetTile16ForTile32(i, k, (int)Dimension::map32TilesTL, map32address));
440 uint16_t tr,
441 GetTile16ForTile32(i, k, (int)Dimension::map32TilesTR, map32address));
443 uint16_t bl,
444 GetTile16ForTile32(i, k, (int)Dimension::map32TilesBL, map32address));
446 uint16_t br,
447 GetTile16ForTile32(i, k, (int)Dimension::map32TilesBR, map32address));
448
449 // Add the generated 16-bit tiles to the tiles32 vector.
450 tiles32_unique_.emplace_back(gfx::Tile32(tl, tr, bl, br));
451 }
452 }
453
454 map_tiles_.light_world.resize(0x200);
455 map_tiles_.dark_world.resize(0x200);
456 map_tiles_.special_world.resize(0x200);
457 for (int i = 0; i < 0x200; i++) {
458 map_tiles_.light_world[i].resize(0x200);
459 map_tiles_.dark_world[i].resize(0x200);
460 map_tiles_.special_world[i].resize(0x200);
461 }
462
463 return absl::OkStatus();
464}
465
467 int tpos = kMap16Tiles;
468 int num_tile16 = kNumTile16Individual;
469
470 // Check if expanded tile16 data is actually present in ROM
471 // The flag position should contain 0x0F for vanilla, something else for expanded
472 uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
473 uint8_t expanded_flag = rom()->data()[kMap16ExpandedFlagPos];
474 util::logf("Expanded tile16 flag: %d", expanded_flag);
475 if (rom()->data()[kMap16ExpandedFlagPos] == 0x0F || asm_version >= 3) {
476 // ROM has expanded tile16 data - use expanded addresses
477 tpos = kMap16TilesExpanded;
478 num_tile16 = NumberOfMap16Ex;
479 expanded_tile16_ = true;
480 }
481 // Otherwise use vanilla addresses (already set above)
482
483 for (int i = 0; i < num_tile16; i += 1) {
484 ASSIGN_OR_RETURN(auto t0_data, rom()->ReadWord(tpos));
485 gfx::TileInfo t0 = gfx::GetTilesInfo(t0_data);
486 tpos += 2;
487 ASSIGN_OR_RETURN(auto t1_data, rom()->ReadWord(tpos));
488 gfx::TileInfo t1 = gfx::GetTilesInfo(t1_data);
489 tpos += 2;
490 ASSIGN_OR_RETURN(auto t2_data, rom()->ReadWord(tpos));
491 gfx::TileInfo t2 = gfx::GetTilesInfo(t2_data);
492 tpos += 2;
493 ASSIGN_OR_RETURN(auto t3_data, rom()->ReadWord(tpos));
494 gfx::TileInfo t3 = gfx::GetTilesInfo(t3_data);
495 tpos += 2;
496 tiles16_.emplace_back(t0, t1, t2, t3);
497 }
498 return absl::OkStatus();
499}
500
501void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
502 OverworldBlockset& world) {
503 int position_x1 = (x * 2) + (sx * 32);
504 int position_y1 = (y * 2) + (sy * 32);
505 int position_x2 = (x * 2) + 1 + (sx * 32);
506 int position_y2 = (y * 2) + 1 + (sy * 32);
507 world[position_x1][position_y1] = tiles32_unique_[tpos].tile0_;
508 world[position_x2][position_y1] = tiles32_unique_[tpos].tile1_;
509 world[position_x1][position_y2] = tiles32_unique_[tpos].tile2_;
510 world[position_x2][position_y2] = tiles32_unique_[tpos].tile3_;
511}
512
513void Overworld::OrganizeMapTiles(std::vector<uint8_t>& bytes,
514 std::vector<uint8_t>& bytes2, int i, int sx,
515 int sy, int& ttpos) {
516 for (int y = 0; y < 16; y++) {
517 for (int x = 0; x < 16; x++) {
518 auto tidD = (uint16_t)((bytes2[ttpos] << 8) + bytes[ttpos]);
519 if (int tpos = tidD; tpos < tiles32_unique_.size()) {
520 if (i < kDarkWorldMapIdStart) {
521 AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.light_world);
522 } else if (i < kSpecialWorldMapIdStart && i >= kDarkWorldMapIdStart) {
523 AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.dark_world);
524 } else {
525 AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.special_world);
526 }
527 }
528 ttpos += 1;
529 }
530 }
531}
532
534 // Keep original method for fallback/compatibility
535 // Note: This method is void, so we can't return status
536 // The parallel version will be called from Load()
537}
538
540 // For now, fall back to the original sequential implementation
541 // The parallel version has synchronization issues that cause data corruption
542 util::logf("Using sequential decompression (parallel version disabled due to data integrity issues)");
543
544 const auto get_ow_map_gfx_ptr = [this](int index, uint32_t map_ptr) {
545 int p = (rom()->data()[map_ptr + 2 + (3 * index)] << 16) +
546 (rom()->data()[map_ptr + 1 + (3 * index)] << 8) +
547 (rom()->data()[map_ptr + (3 * index)]);
548 return SnesToPc(p);
549 };
550
551 constexpr uint32_t kBaseLowest = 0x0FFFFF;
552 constexpr uint32_t kBaseHighest = 0x0F8000;
553
554 uint32_t lowest = kBaseLowest;
555 uint32_t highest = kBaseHighest;
556 int sx = 0;
557 int sy = 0;
558 int c = 0;
559
560 for (int i = 0; i < kNumOverworldMaps; i++) {
561 auto p1 = get_ow_map_gfx_ptr(
562 i, rom()->version_constants().kCompressedAllMap32PointersHigh);
563 auto p2 = get_ow_map_gfx_ptr(
564 i, rom()->version_constants().kCompressedAllMap32PointersLow);
565
566 int ttpos = 0;
567
568 if (p1 >= highest)
569 highest = p1;
570 if (p2 >= highest)
571 highest = p2;
572
573 if (p1 <= lowest && p1 > kBaseHighest)
574 lowest = p1;
575 if (p2 <= lowest && p2 > kBaseHighest)
576 lowest = p2;
577
578 int size1, size2;
579 auto bytes = gfx::HyruleMagicDecompress(rom()->data() + p2, &size1, 1);
580 auto bytes2 = gfx::HyruleMagicDecompress(rom()->data() + p1, &size2, 1);
581 OrganizeMapTiles(bytes, bytes2, i, sx, sy, ttpos);
582
583 sx++;
584 if (sx >= 8) {
585 sy++;
586 sx = 0;
587 }
588
589 c++;
590 if (c >= 64) {
591 sx = 0;
592 sy = 0;
593 c = 0;
594 }
595 }
596
597 return absl::OkStatus();
598}
599
601 auto size = tiles16_.size();
602
603 // Performance optimization: Only build essential maps initially
604 // Essential maps are the first few maps of each world that are commonly accessed
605 constexpr int kEssentialMapsPerWorld = 8; // Build first 8 maps of each world
606 constexpr int kLightWorldEssential = kEssentialMapsPerWorld;
607 constexpr int kDarkWorldEssential = kDarkWorldMapIdStart + kEssentialMapsPerWorld;
608 constexpr int kSpecialWorldEssential = kSpecialWorldMapIdStart + kEssentialMapsPerWorld;
609
610 util::logf("Building essential maps only (first %d maps per world) for faster loading", kEssentialMapsPerWorld);
611
612 std::vector<std::future<absl::Status>> futures;
613
614 // Build essential maps only
615 for (int i = 0; i < kNumOverworldMaps; ++i) {
616 bool is_essential = false;
617
618 // Check if this is an essential map
619 if (i < kLightWorldEssential) {
620 is_essential = true;
621 } else if (i >= kDarkWorldMapIdStart && i < kDarkWorldEssential) {
622 is_essential = true;
623 } else if (i >= kSpecialWorldMapIdStart && i < kSpecialWorldEssential) {
624 is_essential = true;
625 }
626
627 if (is_essential) {
628 int world_type = 0;
630 world_type = 1;
631 } else if (i >= kSpecialWorldMapIdStart) {
632 world_type = 2;
633 }
634
635 auto task_function = [this, i, size, world_type]() {
636 return overworld_maps_[i].BuildMap(size, game_state_, world_type,
637 tiles16_, GetMapTiles(world_type));
638 };
639 futures.emplace_back(std::async(std::launch::async, task_function));
640 } else {
641 // Mark non-essential maps as not built yet
642 overworld_maps_[i].SetNotBuilt();
643 }
644 }
645
646 // Wait for essential maps to complete
647 for (auto& future : futures) {
648 future.wait();
649 RETURN_IF_ERROR(future.get());
650 }
651
652 util::logf("Essential maps built. Remaining maps will be built on-demand.");
653 return absl::OkStatus();
654}
655
656absl::Status Overworld::EnsureMapBuilt(int map_index) {
657 if (map_index < 0 || map_index >= kNumOverworldMaps) {
658 return absl::InvalidArgumentError("Invalid map index");
659 }
660
661 // Check if map is already built
662 if (overworld_maps_[map_index].is_built()) {
663 return absl::OkStatus();
664 }
665
666 // Build the map on-demand
667 auto size = tiles16_.size();
668 int world_type = 0;
669 if (map_index >= kDarkWorldMapIdStart && map_index < kSpecialWorldMapIdStart) {
670 world_type = 1;
671 } else if (map_index >= kSpecialWorldMapIdStart) {
672 world_type = 2;
673 }
674
675 util::logf("Building map %d on-demand", map_index);
676 return overworld_maps_[map_index].BuildMap(size, game_state_, world_type,
677 tiles16_, GetMapTiles(world_type));
678}
679
681 for (int i = 0; i < kNumTileTypes; ++i) {
683 rom()->data()[rom()->version_constants().kOverworldTilesType + i];
684 }
685}
686
688 int ow_entrance_map_ptr = kOverworldEntranceMap;
689 int ow_entrance_pos_ptr = kOverworldEntrancePos;
690 int ow_entrance_id_ptr = kOverworldEntranceEntranceId;
691 int num_entrances = 129;
692
693 // Check if expanded entrance data is actually present in ROM
694 // The flag position should contain 0xB8 for vanilla, something else for expanded
695 if (rom()->data()[kOverworldEntranceExpandedFlagPos] != 0xB8) {
696 // ROM has expanded entrance data - use expanded addresses
697 ow_entrance_map_ptr = kOverworldEntranceMapExpanded;
698 ow_entrance_pos_ptr = kOverworldEntrancePosExpanded;
699 ow_entrance_id_ptr = kOverworldEntranceEntranceIdExpanded;
700 expanded_entrances_ = true;
701 num_entrances = 256; // Expanded entrance count
702 }
703 // Otherwise use vanilla addresses (already set above)
704
705 for (int i = 0; i < num_entrances; i++) {
706 ASSIGN_OR_RETURN(auto map_id,
707 rom()->ReadWord(ow_entrance_map_ptr + (i * 2)));
708 ASSIGN_OR_RETURN(auto map_pos,
709 rom()->ReadWord(ow_entrance_pos_ptr + (i * 2)));
710 ASSIGN_OR_RETURN(auto entrance_id, rom()->ReadByte(ow_entrance_id_ptr + i));
711 int p = map_pos >> 1;
712 int x = (p % 64);
713 int y = (p >> 6);
714 bool deleted = false;
715 if (map_pos == 0xFFFF) {
716 deleted = true;
717 }
718 all_entrances_.emplace_back(
719 (x * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512),
720 (y * 16) + (((map_id % 64) / 8) * 512), entrance_id, map_id, map_pos,
721 deleted);
722 }
723
724 return absl::OkStatus();
725}
726
727absl::Status Overworld::LoadHoles() {
728 constexpr int kNumHoles = 0x13;
729 for (int i = 0; i < kNumHoles; i++) {
730 ASSIGN_OR_RETURN(auto map_id,
731 rom()->ReadWord(kOverworldHoleArea + (i * 2)));
732 ASSIGN_OR_RETURN(auto map_pos,
733 rom()->ReadWord(kOverworldHolePos + (i * 2)));
734 ASSIGN_OR_RETURN(auto entrance_id,
735 rom()->ReadByte(kOverworldHoleEntrance + i));
736 int p = (map_pos + 0x400) >> 1;
737 int x = (p % 64);
738 int y = (p >> 6);
739 all_holes_.emplace_back(
740 (x * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512),
741 (y * 16) + (((map_id % 64) / 8) * 512), entrance_id, map_id,
742 (uint16_t)(map_pos + 0x400), true);
743 }
744 return absl::OkStatus();
745}
746
747absl::Status Overworld::LoadExits() {
748 const int NumberOfOverworldExits = 0x4F;
749 std::vector<OverworldExit> exits;
750 for (int i = 0; i < NumberOfOverworldExits; i++) {
751 auto rom_data = rom()->data();
752
753 uint16_t exit_room_id;
754 uint16_t exit_map_id;
755 uint16_t exit_vram;
756 uint16_t exit_y_scroll;
757 uint16_t exit_x_scroll;
758 uint16_t exit_y_player;
759 uint16_t exit_x_player;
760 uint16_t exit_y_camera;
761 uint16_t exit_x_camera;
762 uint16_t exit_scroll_mod_y;
763 uint16_t exit_scroll_mod_x;
764 uint16_t exit_door_type_1;
765 uint16_t exit_door_type_2;
766 RETURN_IF_ERROR(rom()->ReadTransaction(
767 exit_room_id, (OWExitRoomId + (i * 2)), exit_map_id, OWExitMapId + i,
768 exit_vram, OWExitVram + (i * 2), exit_y_scroll, OWExitYScroll + (i * 2),
769 exit_x_scroll, OWExitXScroll + (i * 2), exit_y_player,
770 OWExitYPlayer + (i * 2), exit_x_player, OWExitXPlayer + (i * 2),
771 exit_y_camera, OWExitYCamera + (i * 2), exit_x_camera,
772 OWExitXCamera + (i * 2), exit_scroll_mod_y, OWExitUnk1 + i,
773 exit_scroll_mod_x, OWExitUnk2 + i, exit_door_type_1,
774 OWExitDoorType1 + (i * 2), exit_door_type_2,
775 OWExitDoorType2 + (i * 2)));
776
777 uint16_t py = (uint16_t)((rom_data[OWExitYPlayer + (i * 2) + 1] << 8) +
778 rom_data[OWExitYPlayer + (i * 2)]);
779 uint16_t px = (uint16_t)((rom_data[OWExitXPlayer + (i * 2) + 1] << 8) +
780 rom_data[OWExitXPlayer + (i * 2)]);
781
782 exits.emplace_back(exit_room_id, exit_map_id, exit_vram, exit_y_scroll,
783 exit_x_scroll, py, px, exit_y_camera, exit_x_camera,
784 exit_scroll_mod_y, exit_scroll_mod_x, exit_door_type_1,
785 exit_door_type_2, (px & py) == 0xFFFF);
786 }
788 return absl::OkStatus();
789}
790
791absl::Status Overworld::LoadItems() {
792 // Version 0x03 of the OW ASM added item support for the SW.
793 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
794
795 // Determine max number of overworld maps based on actual ASM version
796 // Only use expanded maps (0xA0) if v3+ ASM is actually applied
797 int max_ow =
798 (asm_version >= 0x03 && asm_version != 0xFF) ? kNumOverworldMaps : 0x80;
799
800 ASSIGN_OR_RETURN(uint32_t pointer_snes,
802 uint32_t item_pointer_address =
803 SnesToPc(pointer_snes); // 0x1BC2F9 -> 0x0DC2F9
804
805 for (int i = 0; i < max_ow; i++) {
806 ASSIGN_OR_RETURN(uint8_t bank_byte,
808 int bank = bank_byte & 0x7F;
809
810 ASSIGN_OR_RETURN(uint8_t addr_low,
811 rom()->ReadByte(item_pointer_address + (i * 2)));
812 ASSIGN_OR_RETURN(uint8_t addr_high,
813 rom()->ReadByte(item_pointer_address + (i * 2) + 1));
814
815 uint32_t addr = (bank << 16) + // 1B
816 (addr_high << 8) + // F9
817 addr_low; // 3C
818 addr = SnesToPc(addr);
819
820 // Check if this is a large map and skip if not the parent
821 if (overworld_maps_[i].area_size() != zelda3::AreaSizeEnum::SmallArea) {
822 if (overworld_maps_[i].parent() != (uint8_t)i) {
823 continue;
824 }
825 }
826
827 while (true) {
828 ASSIGN_OR_RETURN(uint8_t b1, rom()->ReadByte(addr));
829 ASSIGN_OR_RETURN(uint8_t b2, rom()->ReadByte(addr + 1));
830 ASSIGN_OR_RETURN(uint8_t b3, rom()->ReadByte(addr + 2));
831
832 if (b1 == 0xFF && b2 == 0xFF) {
833 break;
834 }
835
836 int p = (((b2 & 0x1F) << 8) + b1) >> 1;
837
838 int x = p % 0x40; // Use 0x40 instead of 64 to match ZS
839 int y = p >> 6;
840
841 int fakeID = i % 0x40; // Use modulo 0x40 to match ZS
842
843 int sy = fakeID / 8;
844 int sx = fakeID - (sy * 8);
845
846 all_items_.emplace_back(b3, (uint16_t)i, (x * 16) + (sx * 512),
847 (y * 16) + (sy * 512), false);
848 auto size = all_items_.size();
849
850 all_items_[size - 1].game_x_ = (uint8_t)x;
851 all_items_[size - 1].game_y_ = (uint8_t)y;
852 addr += 3;
853 }
854 }
855 return absl::OkStatus();
856}
857
859 std::vector<std::future<absl::Status>> futures;
860
861 // Determine sprite table locations based on actual ASM version in ROM
862 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
863
864 if (asm_version >= 3 && asm_version != 0xFF) {
865 // v3: Use expanded sprite tables
866 futures.emplace_back(std::async(std::launch::async, [this]() {
868 }));
869 futures.emplace_back(std::async(std::launch::async, [this]() {
871 }));
872 futures.emplace_back(std::async(std::launch::async, [this]() {
874 }));
875 } else {
876 // Vanilla/v2: Use original sprite tables
877 futures.emplace_back(std::async(std::launch::async, [this]() {
879 }));
880 futures.emplace_back(std::async(std::launch::async, [this]() {
882 }));
883 futures.emplace_back(std::async(std::launch::async, [this]() {
885 }));
886 }
887
888 for (auto& future : futures) {
889 future.wait();
890 RETURN_IF_ERROR(future.get());
891 }
892 return absl::OkStatus();
893}
894
895absl::Status Overworld::LoadSpritesFromMap(int sprites_per_gamestate_ptr,
896 int num_maps_per_gamestate,
897 int game_state) {
898 for (int i = 0; i < num_maps_per_gamestate; i++) {
899 if (map_parent_[i] != i)
900 continue;
901
902 int current_spr_ptr = sprites_per_gamestate_ptr + (i * 2);
903 ASSIGN_OR_RETURN(auto word_addr, rom()->ReadWord(current_spr_ptr));
904 int sprite_address = SnesToPc((0x09 << 0x10) | word_addr);
905 while (true) {
906 ASSIGN_OR_RETURN(uint8_t b1, rom()->ReadByte(sprite_address));
907 ASSIGN_OR_RETURN(uint8_t b2, rom()->ReadByte(sprite_address + 1));
908 ASSIGN_OR_RETURN(uint8_t b3, rom()->ReadByte(sprite_address + 2));
909 if (b1 == 0xFF)
910 break;
911
912 int editor_map_index = i;
913 if (game_state != 0) {
914 if (editor_map_index >= 128)
915 editor_map_index -= 128;
916 else if (editor_map_index >= 64)
917 editor_map_index -= 64;
918 }
919 int mapY = (editor_map_index / 8);
920 int mapX = (editor_map_index % 8);
921
922 int realX = ((b2 & 0x3F) * 16) + mapX * 512;
923 int realY = ((b1 & 0x3F) * 16) + mapY * 512;
924 all_sprites_[game_state].emplace_back(
925 *overworld_maps_[i].mutable_current_graphics(), (uint8_t)i, b3,
926 (uint8_t)(b2 & 0x3F), (uint8_t)(b1 & 0x3F), realX, realY);
927 all_sprites_[game_state].back().Draw();
928
929 sprite_address += 3;
930 }
931 }
932
933 return absl::OkStatus();
934}
935
959
961 util::logf("Saving Overworld Maps");
962
963 // Initialize map pointers
964 std::fill(map_pointers1_id.begin(), map_pointers1_id.end(), -1);
965 std::fill(map_pointers2_id.begin(), map_pointers2_id.end(), -1);
966
967 // Compress and save each map
969 for (int i = 0; i < kNumOverworldMaps; i++) {
970 std::vector<uint8_t> single_map_1(512);
971 std::vector<uint8_t> single_map_2(512);
972
973 // Copy tiles32 data to single_map_1 and single_map_2
974 int npos = 0;
975 for (int y = 0; y < 16; y++) {
976 for (int x = 0; x < 16; x++) {
977 auto packed = tiles32_list_[npos + (i * 256)];
978 single_map_1[npos] = packed & 0xFF; // Lower 8 bits
979 single_map_2[npos] = (packed >> 8) & 0xFF; // Next 8 bits
980 npos++;
981 }
982 }
983
984 int size_a, size_b;
985 // Compress single_map_1 and single_map_2
986 auto a = gfx::HyruleMagicCompress(single_map_1.data(), 256, &size_a, 1);
987 auto b = gfx::HyruleMagicCompress(single_map_2.data(), 256, &size_b, 1);
988 if (a.empty() || b.empty()) {
989 return absl::AbortedError("Error compressing map gfx.");
990 }
991
992 // Save compressed data and pointers
993 map_data_p1[i] = std::vector<uint8_t>(size_a);
994 map_data_p2[i] = std::vector<uint8_t>(size_b);
995
996 if ((pos + size_a) >= 0x5FE70 && (pos + size_a) <= 0x60000) {
997 pos = 0x60000;
998 }
999
1000 if ((pos + size_a) >= 0x6411F && (pos + size_a) <= 0x70000) {
1001 util::logf("Pos set to overflow region for map %s at %s",
1002 std::to_string(i), util::HexLong(pos));
1003 pos = kOverworldMapDataOverflow; // 0x0F8780;
1004 }
1005
1006 const auto compare_array = [](const std::vector<uint8_t>& array1,
1007 const std::vector<uint8_t>& array2) -> bool {
1008 if (array1.size() != array2.size()) {
1009 return false;
1010 }
1011
1012 for (size_t i = 0; i < array1.size(); i++) {
1013 if (array1[i] != array2[i]) {
1014 return false;
1015 }
1016 }
1017
1018 return true;
1019 };
1020
1021 for (int j = 0; j < i; j++) {
1022 if (compare_array(a, map_data_p1[j])) {
1023 // Reuse pointer id j for P1 (a)
1024 map_pointers1_id[i] = j;
1025 }
1026
1027 if (compare_array(b, map_data_p2[j])) {
1028 map_pointers2_id[i] = j;
1029 // Reuse pointer id j for P2 (b)
1030 }
1031 }
1032
1033 if (map_pointers1_id[i] == -1) {
1034 // Save compressed data and pointer for map1
1035 std::copy(a.begin(), a.end(), map_data_p1[i].begin());
1036 int snes_pos = PcToSnes(pos);
1037 map_pointers1[i] = snes_pos;
1038 util::logf("Saving map pointers1 and compressed data for map %s at %s",
1039 util::HexByte(i), util::HexLong(snes_pos));
1040 RETURN_IF_ERROR(rom()->WriteLong(
1041 rom()->version_constants().kCompressedAllMap32PointersLow + (3 * i),
1042 snes_pos));
1043 RETURN_IF_ERROR(rom()->WriteVector(pos, a));
1044 pos += size_a;
1045 } else {
1046 // Save pointer for map1
1047 int snes_pos = map_pointers1[map_pointers1_id[i]];
1048 util::logf("Saving map pointers1 for map %s at %s", util::HexByte(i),
1049 util::HexLong(snes_pos));
1050 RETURN_IF_ERROR(rom()->WriteLong(
1051 rom()->version_constants().kCompressedAllMap32PointersLow + (3 * i),
1052 snes_pos));
1053 }
1054
1055 if ((pos + b.size()) >= 0x5FE70 && (pos + b.size()) <= 0x60000) {
1056 pos = 0x60000;
1057 }
1058
1059 if ((pos + b.size()) >= 0x6411F && (pos + b.size()) <= 0x70000) {
1060 util::logf("Pos set to overflow region for map %s at %s",
1061 util::HexByte(i), util::HexLong(pos));
1063 }
1064
1065 if (map_pointers2_id[i] == -1) {
1066 // Save compressed data and pointer for map2
1067 std::copy(b.begin(), b.end(), map_data_p2[i].begin());
1068 int snes_pos = PcToSnes(pos);
1069 map_pointers2[i] = snes_pos;
1070 util::logf("Saving map pointers2 and compressed data for map %s at %s",
1071 util::HexByte(i), util::HexLong(snes_pos));
1072 RETURN_IF_ERROR(rom()->WriteLong(
1073 rom()->version_constants().kCompressedAllMap32PointersHigh + (3 * i),
1074 snes_pos));
1075 RETURN_IF_ERROR(rom()->WriteVector(pos, b));
1076 pos += size_b;
1077 } else {
1078 // Save pointer for map2
1079 int snes_pos = map_pointers2[map_pointers2_id[i]];
1080 util::logf("Saving map pointers2 for map %s at %s", util::HexByte(i),
1081 util::HexLong(snes_pos));
1082 RETURN_IF_ERROR(rom()->WriteLong(
1083 rom()->version_constants().kCompressedAllMap32PointersHigh + (3 * i),
1084 snes_pos));
1085 }
1086 }
1087
1088 // Check if too many maps data
1090 util::logf("Too many maps data %s", util::HexLong(pos));
1091 return absl::AbortedError("Too many maps data " + std::to_string(pos));
1092 }
1093
1095 return absl::OkStatus();
1096}
1097
1099 util::logf("Saving Large Maps");
1100
1101 // Check if this is a v3+ ROM to use expanded transition system
1102 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
1103 bool use_expanded_transitions = (asm_version >= 3 && asm_version != 0xFF);
1104
1105 if (use_expanded_transitions) {
1106 // Use new v3+ complex transition system with neighbor awareness
1107 return SaveLargeMapsExpanded();
1108 }
1109
1110 // Original vanilla/v2 logic preserved
1111 std::vector<uint8_t> checked_map;
1112
1113 for (int i = 0; i < kNumMapsPerWorld; ++i) {
1114 int y_pos = i / 8;
1115 int x_pos = i % 8;
1116 int parent_y_pos = overworld_maps_[i].parent() / 8;
1117 int parent_x_pos = overworld_maps_[i].parent() % 8;
1118
1119 // Always write the map parent since it should not matter
1120 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + i,
1121 overworld_maps_[i].parent()))
1122
1123 if (std::find(checked_map.begin(), checked_map.end(), i) !=
1124 checked_map.end()) {
1125 continue;
1126 }
1127
1128 // If it's large then save parent pos *
1129 // 0x200 otherwise pos * 0x200
1130 if (overworld_maps_[i].is_large_map()) {
1131 const uint8_t large_map_offsets[] = {0, 1, 8, 9};
1132 for (const auto& offset : large_map_offsets) {
1133 // Check 1
1134 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i + offset, 0x20));
1135 // Check 2
1137 rom()->WriteByte(kOverworldMapSizeHighByte + i + offset, 0x03));
1138 // Check 3
1140 rom()->WriteByte(kOverworldScreenSize + i + offset, 0x00));
1142 rom()->WriteByte(kOverworldScreenSize + i + offset + 64, 0x00));
1143 // Check 4
1144 RETURN_IF_ERROR(rom()->WriteByte(
1145 kOverworldScreenSizeForLoading + i + offset, 0x04));
1146 RETURN_IF_ERROR(rom()->WriteByte(
1148 0x04));
1150 offset + kSpecialWorldMapIdStart,
1151 0x04));
1152 }
1153
1154 // Check 5 and 6 - transition targets
1156 rom()->WriteShort(kTransitionTargetNorth + (i * 2),
1157 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
1159 rom()->WriteShort(kTransitionTargetWest + (i * 2),
1160 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
1161
1163 rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 2,
1164 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
1166 rom()->WriteShort(kTransitionTargetWest + (i * 2) + 2,
1167 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
1168
1170 rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 16,
1171 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
1173 rom()->WriteShort(kTransitionTargetWest + (i * 2) + 16,
1174 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
1175
1177 rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 18,
1178 (uint16_t)((parent_y_pos * 0x200) - 0xE0)));
1180 rom()->WriteShort(kTransitionTargetWest + (i * 2) + 18,
1181 (uint16_t)((parent_x_pos * 0x200) - 0x100)));
1182
1183 // Check 7 and 8 - transition positions
1184 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionX + (i * 2),
1185 (parent_x_pos * 0x200)));
1186 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionY + (i * 2),
1187 (parent_y_pos * 0x200)));
1188
1190 rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 02,
1191 (parent_x_pos * 0x200)));
1193 rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 02,
1194 (parent_y_pos * 0x200)));
1195
1197 rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 16,
1198 (parent_x_pos * 0x200)));
1200 rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 16,
1201 (parent_y_pos * 0x200)));
1202
1204 rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 18,
1205 (parent_x_pos * 0x200)));
1207 rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 18,
1208 (parent_y_pos * 0x200)));
1209
1210 // Check 9 - simple vanilla large area transitions
1211 RETURN_IF_ERROR(rom()->WriteShort(
1212 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 00, 0x0060));
1213 RETURN_IF_ERROR(rom()->WriteShort(
1214 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 02, 0x0060));
1215
1216 // If parentX == 0 then lower submaps == 0x0060 too
1217 if (parent_x_pos == 0) {
1218 RETURN_IF_ERROR(rom()->WriteShort(
1219 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 16, 0x0060));
1220 RETURN_IF_ERROR(rom()->WriteShort(
1221 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 18, 0x0060));
1222 } else {
1223 // Otherwise lower submaps == 0x1060
1224 RETURN_IF_ERROR(rom()->WriteShort(
1225 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 16, 0x1060));
1226 RETURN_IF_ERROR(rom()->WriteShort(
1227 kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 18, 0x1060));
1228
1229 // If the area to the left is a large map, we don't need to add an
1230 // offset to it. otherwise leave it the same. Just to make sure where
1231 // don't try to read outside of the array.
1232 if ((i - 1) >= 0) {
1233 // If the area to the left is a large area.
1234 if (overworld_maps_[i - 1].is_large_map()) {
1235 // If the area to the left is the bottom right of a large area.
1236 if (overworld_maps_[i - 1].large_index() == 1) {
1237 RETURN_IF_ERROR(rom()->WriteShort(
1239 0x0060));
1240 }
1241 }
1242 }
1243 }
1244
1245 // Always 0x0080
1246 RETURN_IF_ERROR(rom()->WriteShort(
1247 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 00, 0x0080));
1248 RETURN_IF_ERROR(rom()->WriteShort(
1249 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 2, 0x0080));
1250 // Lower always 0x1080
1251 RETURN_IF_ERROR(rom()->WriteShort(
1252 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 16, 0x1080));
1253 RETURN_IF_ERROR(rom()->WriteShort(
1254 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 18, 0x1080));
1255
1256 // If the area to the right is a large map, we don't need to add an offset
1257 // to it. otherwise leave it the same. Just to make sure where don't try
1258 // to read outside of the array.
1259 if ((i + 2) < 64) {
1260 // If the area to the right is a large area.
1261 if (overworld_maps_[i + 2].is_large_map()) {
1262 // If the area to the right is the top left of a large area.
1263 if (overworld_maps_[i + 2].large_index() == 0) {
1264 RETURN_IF_ERROR(rom()->WriteShort(
1265 kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 18, 0x0080));
1266 }
1267 }
1268 }
1269
1270 // Always 0x1800
1271 RETURN_IF_ERROR(rom()->WriteShort(
1272 kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x1800));
1273 RETURN_IF_ERROR(rom()->WriteShort(
1274 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 16, 0x1800));
1275 // Right side is always 0x1840
1276 RETURN_IF_ERROR(rom()->WriteShort(
1277 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 2, 0x1840));
1278 RETURN_IF_ERROR(rom()->WriteShort(
1279 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 18, 0x1840));
1280
1281 // If the area above is a large map, we don't need to add an offset to it.
1282 // otherwise leave it the same.
1283 // Just to make sure where don't try to read outside of the array.
1284 if (i - 8 >= 0) {
1285 // If the area just above us is a large area.
1286 if (overworld_maps_[i - 8].is_large_map()) {
1287 // If the area just above us is the bottom left of a large area.
1288 if (overworld_maps_[i - 8].large_index() == 2) {
1289 RETURN_IF_ERROR(rom()->WriteShort(
1290 kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 02, 0x1800));
1291 }
1292 }
1293 }
1294
1295 // Always 0x2000
1296 RETURN_IF_ERROR(rom()->WriteShort(
1297 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 00, 0x2000));
1298 RETURN_IF_ERROR(rom()->WriteShort(
1299 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 16, 0x2000));
1300 // Right side always 0x2040
1301 RETURN_IF_ERROR(rom()->WriteShort(
1302 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 2, 0x2040));
1303 RETURN_IF_ERROR(rom()->WriteShort(
1304 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 18, 0x2040));
1305
1306 // If the area below is a large map, we don't need to add an offset to it.
1307 // otherwise leave it the same.
1308 // Just to make sure where don't try to read outside of the array.
1309 if (i + 16 < 64) {
1310 // If the area just below us is a large area.
1311 if (overworld_maps_[i + 16].is_large_map()) {
1312 // If the area just below us is the top left of a large area.
1313 if (overworld_maps_[i + 16].large_index() == 0) {
1314 RETURN_IF_ERROR(rom()->WriteShort(
1315 kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 18, 0x2000));
1316 }
1317 }
1318 }
1319
1320 checked_map.emplace_back(i);
1321 checked_map.emplace_back((i + 1));
1322 checked_map.emplace_back((i + 8));
1323 checked_map.emplace_back((i + 9));
1324
1325 } else {
1326 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i, 0x00));
1327 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSizeHighByte + i, 0x01));
1328
1329 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, 0x01));
1330 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i + 64, 0x01));
1331
1333 rom()->WriteByte(kOverworldScreenSizeForLoading + i, 0x02));
1334 RETURN_IF_ERROR(rom()->WriteByte(
1336 RETURN_IF_ERROR(rom()->WriteByte(
1338
1339 RETURN_IF_ERROR(rom()->WriteShort(
1340 kOverworldScreenTileMapChangeByScreen1 + (i * 2), 0x0060));
1341
1342 // If the area to the left is a large map, we don't need to add an offset
1343 // to it. otherwise leave it the same.
1344 // Just to make sure where don't try to read outside of the array.
1345 if (i - 1 >= 0 && parent_x_pos != 0) {
1346 if (overworld_maps_[i - 1].is_large_map()) {
1347 if (overworld_maps_[i - 1].large_index() == 3) {
1348 RETURN_IF_ERROR(rom()->WriteShort(
1349 kOverworldScreenTileMapChangeByScreen1 + (i * 2), 0xF060));
1350 }
1351 }
1352 }
1353
1354 RETURN_IF_ERROR(rom()->WriteShort(
1355 kOverworldScreenTileMapChangeByScreen2 + (i * 2), 0x0040));
1356
1357 if (i + 1 < 64 && parent_x_pos != 7) {
1358 if (overworld_maps_[i + 1].is_large_map()) {
1359 if (overworld_maps_[i + 1].large_index() == 2) {
1360 RETURN_IF_ERROR(rom()->WriteShort(
1361 kOverworldScreenTileMapChangeByScreen2 + (i * 2), 0xF040));
1362 }
1363 }
1364 }
1365
1366 RETURN_IF_ERROR(rom()->WriteShort(
1367 kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x1800));
1368
1369 // If the area above is a large map, we don't need to add an offset to it.
1370 // otherwise leave it the same.
1371 // Just to make sure where don't try to read outside of the array.
1372 if (i - 8 >= 0) {
1373 // If the area just above us is a large area.
1374 if (overworld_maps_[i - 8].is_large_map()) {
1375 // If we are under the bottom right of the large area.
1376 if (overworld_maps_[i - 8].large_index() == 3) {
1377 RETURN_IF_ERROR(rom()->WriteShort(
1378 kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x17C0));
1379 }
1380 }
1381 }
1382
1383 RETURN_IF_ERROR(rom()->WriteShort(
1384 kOverworldScreenTileMapChangeByScreen4 + (i * 2), 0x1000));
1385
1386 // If the area below is a large map, we don't need to add an offset to it.
1387 // otherwise leave it the same.
1388 // Just to make sure where don't try to read outside of the array.
1389 if (i + 8 < 64) {
1390 // If the area just below us is a large area.
1391 if (overworld_maps_[i + 8].is_large_map()) {
1392 // If we are on top of the top right of the large area.
1393 if (overworld_maps_[i + 8].large_index() == 1) {
1394 RETURN_IF_ERROR(rom()->WriteShort(
1395 kOverworldScreenTileMapChangeByScreen4 + (i * 2), 0x0FC0));
1396 }
1397 }
1398 }
1399
1400 RETURN_IF_ERROR(rom()->WriteShort(kTransitionTargetNorth + (i * 2),
1401 (uint16_t)((y_pos * 0x200) - 0xE0)));
1402 RETURN_IF_ERROR(rom()->WriteShort(kTransitionTargetWest + (i * 2),
1403 (uint16_t)((x_pos * 0x200) - 0x100)));
1404
1405 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionX + (i * 2),
1406 (x_pos * 0x200)));
1407 RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionY + (i * 2),
1408 (y_pos * 0x200)));
1409
1410 checked_map.emplace_back(i);
1411 }
1412 }
1413
1414 constexpr int OverworldScreenTileMapChangeMask = 0x1262C;
1415
1417 rom()->WriteShort(OverworldScreenTileMapChangeMask + 0, 0x1F80));
1419 rom()->WriteShort(OverworldScreenTileMapChangeMask + 2, 0x1F80));
1421 rom()->WriteShort(OverworldScreenTileMapChangeMask + 4, 0x007F));
1423 rom()->WriteShort(OverworldScreenTileMapChangeMask + 6, 0x007F));
1424
1425 return absl::OkStatus();
1426}
1427
1429 int i, int parent_x_pos, int parent_y_pos, int transition_target_north,
1430 int transition_target_west, int transition_pos_x, int transition_pos_y,
1431 int screen_change_1, int screen_change_2, int screen_change_3,
1432 int screen_change_4) {
1433 // Set basic transition targets
1435 rom()->WriteShort(transition_target_north + (i * 2),
1436 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
1438 rom()->WriteShort(transition_target_west + (i * 2),
1439 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
1440
1442 rom()->WriteShort(transition_pos_x + (i * 2), parent_x_pos * 0x0200));
1444 rom()->WriteShort(transition_pos_y + (i * 2), parent_y_pos * 0x0200));
1445
1446 // byScreen1 = Transitioning right
1447 uint16_t by_screen1_small = 0x0060;
1448
1449 // Check west neighbor for transition adjustments
1450 if ((i % 0x40) - 1 >= 0) {
1451 auto& west_neighbor = overworld_maps_[i - 1];
1452
1453 // Transition from bottom right quadrant of large area to small area
1454 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1455 west_neighbor.large_index() == 3) {
1456 by_screen1_small = 0xF060;
1457 }
1458 // Transition from bottom quadrant of tall area to small area
1459 else if (west_neighbor.area_size() == AreaSizeEnum::TallArea &&
1460 west_neighbor.large_index() == 2) {
1461 by_screen1_small = 0xF060;
1462 }
1463 }
1464
1466 rom()->WriteShort(screen_change_1 + (i * 2), by_screen1_small));
1467
1468 // byScreen2 = Transitioning left
1469 uint16_t by_screen2_small = 0x0040;
1470
1471 // Check east neighbor for transition adjustments
1472 if ((i % 0x40) + 1 < 0x40 && i + 1 < kNumOverworldMaps) {
1473 auto& east_neighbor = overworld_maps_[i + 1];
1474
1475 // Transition from bottom left quadrant of large area to small area
1476 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1477 east_neighbor.large_index() == 2) {
1478 by_screen2_small = 0xF040;
1479 }
1480 // Transition from bottom quadrant of tall area to small area
1481 else if (east_neighbor.area_size() == AreaSizeEnum::TallArea &&
1482 east_neighbor.large_index() == 2) {
1483 by_screen2_small = 0xF040;
1484 }
1485 }
1486
1488 rom()->WriteShort(screen_change_2 + (i * 2), by_screen2_small));
1489
1490 // byScreen3 = Transitioning down
1491 uint16_t by_screen3_small = 0x1800;
1492
1493 // Check north neighbor for transition adjustments
1494 if ((i % 0x40) - 8 >= 0) {
1495 auto& north_neighbor = overworld_maps_[i - 8];
1496
1497 // Transition from bottom right quadrant of large area to small area
1498 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1499 north_neighbor.large_index() == 3) {
1500 by_screen3_small = 0x17C0;
1501 }
1502 // Transition from right quadrant of wide area to small area
1503 else if (north_neighbor.area_size() == AreaSizeEnum::WideArea &&
1504 north_neighbor.large_index() == 1) {
1505 by_screen3_small = 0x17C0;
1506 }
1507 }
1508
1510 rom()->WriteShort(screen_change_3 + (i * 2), by_screen3_small));
1511
1512 // byScreen4 = Transitioning up
1513 uint16_t by_screen4_small = 0x1000;
1514
1515 // Check south neighbor for transition adjustments
1516 if ((i % 0x40) + 8 < 0x40 && i + 8 < kNumOverworldMaps) {
1517 auto& south_neighbor = overworld_maps_[i + 8];
1518
1519 // Transition from top right quadrant of large area to small area
1520 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1521 south_neighbor.large_index() == 1) {
1522 by_screen4_small = 0x0FC0;
1523 }
1524 // Transition from right quadrant of wide area to small area
1525 else if (south_neighbor.area_size() == AreaSizeEnum::WideArea &&
1526 south_neighbor.large_index() == 1) {
1527 by_screen4_small = 0x0FC0;
1528 }
1529 }
1530
1532 rom()->WriteShort(screen_change_4 + (i * 2), by_screen4_small));
1533
1534 return absl::OkStatus();
1535}
1536
1538 int i, int parent_x_pos, int parent_y_pos, int transition_target_north,
1539 int transition_target_west, int transition_pos_x, int transition_pos_y,
1540 int screen_change_1, int screen_change_2, int screen_change_3,
1541 int screen_change_4) {
1542 // Set transition targets for all 4 quadrants
1543 const uint16_t offsets[] = {0, 2, 16, 18};
1544 for (auto offset : offsets) {
1546 rom()->WriteShort(transition_target_north + (i * 2) + offset,
1547 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
1549 rom()->WriteShort(transition_target_west + (i * 2) + offset,
1550 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
1551 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset,
1552 parent_x_pos * 0x0200));
1553 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset,
1554 parent_y_pos * 0x0200));
1555 }
1556
1557 // Complex neighbor-aware transition calculations for large areas
1558 // byScreen1 = Transitioning right
1559 std::array<uint16_t, 4> by_screen1_large = {0x0060, 0x0060, 0x1060, 0x1060};
1560
1561 // Check west neighbor
1562 if ((i % 0x40) - 1 >= 0) {
1563 auto& west_neighbor = overworld_maps_[i - 1];
1564
1565 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea) {
1566 switch (west_neighbor.large_index()) {
1567 case 1: // From bottom right to bottom left of large area
1568 by_screen1_large[2] = 0x0060;
1569 break;
1570 case 3: // From bottom right to top left of large area
1571 by_screen1_large[0] = 0xF060;
1572 break;
1573 }
1574 } else if (west_neighbor.area_size() == AreaSizeEnum::TallArea) {
1575 switch (west_neighbor.large_index()) {
1576 case 0: // From bottom of tall to bottom left of large
1577 by_screen1_large[2] = 0x0060;
1578 break;
1579 case 2: // From bottom of tall to top left of large
1580 by_screen1_large[0] = 0xF060;
1581 break;
1582 }
1583 }
1584 }
1585
1586 for (int j = 0; j < 4; j++) {
1587 RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j],
1588 by_screen1_large[j]));
1589 }
1590
1591 // byScreen2 = Transitioning left
1592 std::array<uint16_t, 4> by_screen2_large = {0x0080, 0x0080, 0x1080, 0x1080};
1593
1594 // Check east neighbor
1595 if ((i % 0x40) + 2 < 0x40 && i + 2 < kNumOverworldMaps) {
1596 auto& east_neighbor = overworld_maps_[i + 2];
1597
1598 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea) {
1599 switch (east_neighbor.large_index()) {
1600 case 0: // From bottom left to bottom right of large area
1601 by_screen2_large[3] = 0x0080;
1602 break;
1603 case 2: // From bottom left to top right of large area
1604 by_screen2_large[1] = 0xF080;
1605 break;
1606 }
1607 } else if (east_neighbor.area_size() == AreaSizeEnum::TallArea) {
1608 switch (east_neighbor.large_index()) {
1609 case 0: // From bottom of tall to bottom right of large
1610 by_screen2_large[3] = 0x0080;
1611 break;
1612 case 2: // From bottom of tall to top right of large
1613 by_screen2_large[1] = 0xF080;
1614 break;
1615 }
1616 }
1617 }
1618
1619 for (int j = 0; j < 4; j++) {
1620 RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j],
1621 by_screen2_large[j]));
1622 }
1623
1624 // byScreen3 = Transitioning down
1625 std::array<uint16_t, 4> by_screen3_large = {0x1800, 0x1840, 0x1800, 0x1840};
1626
1627 // Check north neighbor
1628 if ((i % 0x40) - 8 >= 0) {
1629 auto& north_neighbor = overworld_maps_[i - 8];
1630
1631 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea) {
1632 switch (north_neighbor.large_index()) {
1633 case 2: // From bottom right to top right of large area
1634 by_screen3_large[1] = 0x1800;
1635 break;
1636 case 3: // From bottom right to top left of large area
1637 by_screen3_large[0] = 0x17C0;
1638 break;
1639 }
1640 } else if (north_neighbor.area_size() == AreaSizeEnum::WideArea) {
1641 switch (north_neighbor.large_index()) {
1642 case 0: // From right of wide to top right of large
1643 by_screen3_large[1] = 0x1800;
1644 break;
1645 case 1: // From right of wide to top left of large
1646 by_screen3_large[0] = 0x17C0;
1647 break;
1648 }
1649 }
1650 }
1651
1652 for (int j = 0; j < 4; j++) {
1653 RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j],
1654 by_screen3_large[j]));
1655 }
1656
1657 // byScreen4 = Transitioning up
1658 std::array<uint16_t, 4> by_screen4_large = {0x2000, 0x2040, 0x2000, 0x2040};
1659
1660 // Check south neighbor
1661 if ((i % 0x40) + 16 < 0x40 && i + 16 < kNumOverworldMaps) {
1662 auto& south_neighbor = overworld_maps_[i + 16];
1663
1664 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea) {
1665 switch (south_neighbor.large_index()) {
1666 case 0: // From top right to bottom right of large area
1667 by_screen4_large[3] = 0x2000;
1668 break;
1669 case 1: // From top right to bottom left of large area
1670 by_screen4_large[2] = 0x1FC0;
1671 break;
1672 }
1673 } else if (south_neighbor.area_size() == AreaSizeEnum::WideArea) {
1674 switch (south_neighbor.large_index()) {
1675 case 0: // From right of wide to bottom right of large
1676 by_screen4_large[3] = 0x2000;
1677 break;
1678 case 1: // From right of wide to bottom left of large
1679 by_screen4_large[2] = 0x1FC0;
1680 break;
1681 }
1682 }
1683 }
1684
1685 for (int j = 0; j < 4; j++) {
1686 RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j],
1687 by_screen4_large[j]));
1688 }
1689
1690 return absl::OkStatus();
1691}
1692
1694 int i, int parent_x_pos, int parent_y_pos, int transition_target_north,
1695 int transition_target_west, int transition_pos_x, int transition_pos_y,
1696 int screen_change_1, int screen_change_2, int screen_change_3,
1697 int screen_change_4) {
1698 // Set transition targets for both quadrants
1699 const uint16_t offsets[] = {0, 2};
1700 for (auto offset : offsets) {
1702 rom()->WriteShort(transition_target_north + (i * 2) + offset,
1703 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
1705 rom()->WriteShort(transition_target_west + (i * 2) + offset,
1706 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
1707 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset,
1708 parent_x_pos * 0x0200));
1709 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset,
1710 parent_y_pos * 0x0200));
1711 }
1712
1713 // byScreen1 = Transitioning right
1714 std::array<uint16_t, 2> by_screen1_wide = {0x0060, 0x0060};
1715
1716 // Check west neighbor
1717 if ((i % 0x40) - 1 >= 0) {
1718 auto& west_neighbor = overworld_maps_[i - 1];
1719
1720 // From bottom right of large to left of wide
1721 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1722 west_neighbor.large_index() == 3) {
1723 by_screen1_wide[0] = 0xF060;
1724 }
1725 // From bottom of tall to left of wide
1726 else if (west_neighbor.area_size() == AreaSizeEnum::TallArea &&
1727 west_neighbor.large_index() == 2) {
1728 by_screen1_wide[0] = 0xF060;
1729 }
1730 }
1731
1732 for (int j = 0; j < 2; j++) {
1733 RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j],
1734 by_screen1_wide[j]));
1735 }
1736
1737 // byScreen2 = Transitioning left
1738 std::array<uint16_t, 2> by_screen2_wide = {0x0080, 0x0080};
1739
1740 // Check east neighbor
1741 if ((i % 0x40) + 2 < 0x40 && i + 2 < kNumOverworldMaps) {
1742 auto& east_neighbor = overworld_maps_[i + 2];
1743
1744 // From bottom left of large to right of wide
1745 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1746 east_neighbor.large_index() == 2) {
1747 by_screen2_wide[1] = 0xF080;
1748 }
1749 // From bottom of tall to right of wide
1750 else if (east_neighbor.area_size() == AreaSizeEnum::TallArea &&
1751 east_neighbor.large_index() == 2) {
1752 by_screen2_wide[1] = 0xF080;
1753 }
1754 }
1755
1756 for (int j = 0; j < 2; j++) {
1757 RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j],
1758 by_screen2_wide[j]));
1759 }
1760
1761 // byScreen3 = Transitioning down
1762 std::array<uint16_t, 2> by_screen3_wide = {0x1800, 0x1840};
1763
1764 // Check north neighbor
1765 if ((i % 0x40) - 8 >= 0) {
1766 auto& north_neighbor = overworld_maps_[i - 8];
1767
1768 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea) {
1769 switch (north_neighbor.large_index()) {
1770 case 2: // From bottom right of large to right of wide
1771 by_screen3_wide[1] = 0x1800;
1772 break;
1773 case 3: // From bottom right of large to left of wide
1774 by_screen3_wide[0] = 0x17C0;
1775 break;
1776 }
1777 } else if (north_neighbor.area_size() == AreaSizeEnum::WideArea) {
1778 switch (north_neighbor.large_index()) {
1779 case 0: // From right of wide to right of wide
1780 by_screen3_wide[1] = 0x1800;
1781 break;
1782 case 1: // From right of wide to left of wide
1783 by_screen3_wide[0] = 0x07C0;
1784 break;
1785 }
1786 }
1787 }
1788
1789 for (int j = 0; j < 2; j++) {
1790 RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j],
1791 by_screen3_wide[j]));
1792 }
1793
1794 // byScreen4 = Transitioning up
1795 std::array<uint16_t, 2> by_screen4_wide = {0x1000, 0x1040};
1796
1797 // Check south neighbor
1798 if ((i % 0x40) + 8 < 0x40 && i + 8 < kNumOverworldMaps) {
1799 auto& south_neighbor = overworld_maps_[i + 8];
1800
1801 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea) {
1802 switch (south_neighbor.large_index()) {
1803 case 0: // From top right of large to right of wide
1804 by_screen4_wide[1] = 0x1000;
1805 break;
1806 case 1: // From top right of large to left of wide
1807 by_screen4_wide[0] = 0x0FC0;
1808 break;
1809 }
1810 } else if (south_neighbor.area_size() == AreaSizeEnum::WideArea) {
1811 if (south_neighbor.large_index() == 1) {
1812 by_screen4_wide[0] = 0x0FC0;
1813 }
1814 switch (south_neighbor.large_index()) {
1815 case 0: // From right of wide to right of wide
1816 by_screen4_wide[1] = 0x1000;
1817 break;
1818 case 1: // From right of wide to left of wide
1819 by_screen4_wide[0] = 0x0FC0;
1820 break;
1821 }
1822 }
1823 }
1824
1825 for (int j = 0; j < 2; j++) {
1826 RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j],
1827 by_screen4_wide[j]));
1828 }
1829
1830 return absl::OkStatus();
1831}
1832
1834 int i, int parent_x_pos, int parent_y_pos, int transition_target_north,
1835 int transition_target_west, int transition_pos_x, int transition_pos_y,
1836 int screen_change_1, int screen_change_2, int screen_change_3,
1837 int screen_change_4) {
1838 // Set transition targets for both quadrants
1839 const uint16_t offsets[] = {0, 16};
1840 for (auto offset : offsets) {
1842 rom()->WriteShort(transition_target_north + (i * 2) + offset,
1843 (uint16_t)((parent_y_pos * 0x0200) - 0x00E0)));
1845 rom()->WriteShort(transition_target_west + (i * 2) + offset,
1846 (uint16_t)((parent_x_pos * 0x0200) - 0x0100)));
1847 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset,
1848 parent_x_pos * 0x0200));
1849 RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset,
1850 parent_y_pos * 0x0200));
1851 }
1852
1853 // byScreen1 = Transitioning right
1854 std::array<uint16_t, 2> by_screen1_tall = {0x0060, 0x1060};
1855
1856 // Check west neighbor
1857 if ((i % 0x40) - 1 >= 0) {
1858 auto& west_neighbor = overworld_maps_[i - 1];
1859
1860 if (west_neighbor.area_size() == AreaSizeEnum::LargeArea) {
1861 switch (west_neighbor.large_index()) {
1862 case 1: // From bottom right of large to bottom of tall
1863 by_screen1_tall[1] = 0x0060;
1864 break;
1865 case 3: // From bottom right of large to top of tall
1866 by_screen1_tall[0] = 0xF060;
1867 break;
1868 }
1869 } else if (west_neighbor.area_size() == AreaSizeEnum::TallArea) {
1870 switch (west_neighbor.large_index()) {
1871 case 0: // From bottom of tall to bottom of tall
1872 by_screen1_tall[1] = 0x0060;
1873 break;
1874 case 2: // From bottom of tall to top of tall
1875 by_screen1_tall[0] = 0xF060;
1876 break;
1877 }
1878 }
1879 }
1880
1881 for (int j = 0; j < 2; j++) {
1882 RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j],
1883 by_screen1_tall[j]));
1884 }
1885
1886 // byScreen2 = Transitioning left
1887 std::array<uint16_t, 2> by_screen2_tall = {0x0040, 0x1040};
1888
1889 // Check east neighbor
1890 if ((i % 0x40) + 1 < 0x40 && i + 1 < kNumOverworldMaps) {
1891 auto& east_neighbor = overworld_maps_[i + 1];
1892
1893 if (east_neighbor.area_size() == AreaSizeEnum::LargeArea) {
1894 switch (east_neighbor.large_index()) {
1895 case 0: // From bottom left of large to bottom of tall
1896 by_screen2_tall[1] = 0x0040;
1897 break;
1898 case 2: // From bottom left of large to top of tall
1899 by_screen2_tall[0] = 0xF040;
1900 break;
1901 }
1902 } else if (east_neighbor.area_size() == AreaSizeEnum::TallArea) {
1903 switch (east_neighbor.large_index()) {
1904 case 0: // From bottom of tall to bottom of tall
1905 by_screen2_tall[1] = 0x0040;
1906 break;
1907 case 2: // From bottom of tall to top of tall
1908 by_screen2_tall[0] = 0xF040;
1909 break;
1910 }
1911 }
1912 }
1913
1914 for (int j = 0; j < 2; j++) {
1915 RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j],
1916 by_screen2_tall[j]));
1917 }
1918
1919 // byScreen3 = Transitioning down
1920 std::array<uint16_t, 2> by_screen3_tall = {0x1800, 0x1800};
1921
1922 // Check north neighbor
1923 if ((i % 0x40) - 8 >= 0) {
1924 auto& north_neighbor = overworld_maps_[i - 8];
1925
1926 // From bottom right of large to top of tall
1927 if (north_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1928 north_neighbor.large_index() == 3) {
1929 by_screen3_tall[0] = 0x17C0;
1930 }
1931 // From right of wide to top of tall
1932 else if (north_neighbor.area_size() == AreaSizeEnum::WideArea &&
1933 north_neighbor.large_index() == 1) {
1934 by_screen3_tall[0] = 0x17C0;
1935 }
1936 }
1937
1938 for (int j = 0; j < 2; j++) {
1939 RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j],
1940 by_screen3_tall[j]));
1941 }
1942
1943 // byScreen4 = Transitioning up
1944 std::array<uint16_t, 2> by_screen4_tall = {0x2000, 0x2000};
1945
1946 // Check south neighbor
1947 if ((i % 0x40) + 16 < 0x40 && i + 16 < kNumOverworldMaps) {
1948 auto& south_neighbor = overworld_maps_[i + 16];
1949
1950 // From top right of large to bottom of tall
1951 if (south_neighbor.area_size() == AreaSizeEnum::LargeArea &&
1952 south_neighbor.large_index() == 1) {
1953 by_screen4_tall[1] = 0x1FC0;
1954 }
1955 // From right of wide to bottom of tall
1956 else if (south_neighbor.area_size() == AreaSizeEnum::WideArea &&
1957 south_neighbor.large_index() == 1) {
1958 by_screen4_tall[1] = 0x1FC0;
1959 }
1960 }
1961
1962 for (int j = 0; j < 2; j++) {
1963 RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j],
1964 by_screen4_tall[j]));
1965 }
1966
1967 return absl::OkStatus();
1968}
1969
1971 util::logf("Saving Large Maps (v3+ Expanded)");
1972
1973 // Use expanded memory locations for v3+
1974 int transition_target_north = zelda3::transition_target_northExpanded;
1975 int transition_target_west = zelda3::transition_target_westExpanded;
1976 int transition_pos_x = zelda3::kOverworldTransitionPositionXExpanded;
1977 int transition_pos_y = zelda3::kOverworldTransitionPositionYExpanded;
1982
1983 std::vector<uint8_t> checked_map;
1984
1985 // Process all overworld maps (0xA0 for v3)
1986 for (int i = 0; i < kNumOverworldMaps; ++i) {
1987 // Skip if this map was already processed as part of a multi-area structure
1988 if (std::find(checked_map.begin(), checked_map.end(), i) !=
1989 checked_map.end()) {
1990 continue;
1991 }
1992
1993 int parent_y_pos = (overworld_maps_[i].parent() % 0x40) / 8;
1994 int parent_x_pos = (overworld_maps_[i].parent() % 0x40) % 8;
1995
1996 // Write the map parent ID to expanded parent table
1998 overworld_maps_[i].parent()));
1999
2000 // Handle transitions based on area size
2001 switch (overworld_maps_[i].area_size()) {
2004 i, parent_x_pos, parent_y_pos, transition_target_north,
2005 transition_target_west, transition_pos_x, transition_pos_y,
2006 screen_change_1, screen_change_2, screen_change_3,
2007 screen_change_4));
2008 checked_map.emplace_back(i);
2009 break;
2010
2013 i, parent_x_pos, parent_y_pos, transition_target_north,
2014 transition_target_west, transition_pos_x, transition_pos_y,
2015 screen_change_1, screen_change_2, screen_change_3,
2016 screen_change_4));
2017 // Mark all 4 quadrants as processed
2018 checked_map.emplace_back(i);
2019 checked_map.emplace_back(i + 1);
2020 checked_map.emplace_back(i + 8);
2021 checked_map.emplace_back(i + 9);
2022 break;
2023
2026 i, parent_x_pos, parent_y_pos, transition_target_north,
2027 transition_target_west, transition_pos_x, transition_pos_y,
2028 screen_change_1, screen_change_2, screen_change_3,
2029 screen_change_4));
2030 // Mark both horizontal quadrants as processed
2031 checked_map.emplace_back(i);
2032 checked_map.emplace_back(i + 1);
2033 break;
2034
2037 i, parent_x_pos, parent_y_pos, transition_target_north,
2038 transition_target_west, transition_pos_x, transition_pos_y,
2039 screen_change_1, screen_change_2, screen_change_3,
2040 screen_change_4));
2041 // Mark both vertical quadrants as processed
2042 checked_map.emplace_back(i);
2043 checked_map.emplace_back(i + 8);
2044 break;
2045 }
2046 }
2047
2048 return absl::OkStatus();
2049}
2050
2051namespace {
2052std::vector<uint64_t> GetAllTile16(OverworldMapTiles& map_tiles_) {
2053 std::vector<uint64_t> all_tile_16; // Ensure it's 64 bits
2054
2055 int sx = 0;
2056 int sy = 0;
2057 int c = 0;
2058 OverworldBlockset tiles_used;
2059 for (int i = 0; i < kNumOverworldMaps; i++) {
2060 if (i < kDarkWorldMapIdStart) {
2061 tiles_used = map_tiles_.light_world;
2062 } else if (i < kSpecialWorldMapIdStart && i >= kDarkWorldMapIdStart) {
2063 tiles_used = map_tiles_.dark_world;
2064 } else {
2065 tiles_used = map_tiles_.special_world;
2066 }
2067
2068 for (int y = 0; y < 32; y += 2) {
2069 for (int x = 0; x < 32; x += 2) {
2070 gfx::Tile32 current_tile(
2071 tiles_used[x + (sx * 32)][y + (sy * 32)],
2072 tiles_used[x + 1 + (sx * 32)][y + (sy * 32)],
2073 tiles_used[x + (sx * 32)][y + 1 + (sy * 32)],
2074 tiles_used[x + 1 + (sx * 32)][y + 1 + (sy * 32)]);
2075
2076 all_tile_16.emplace_back(current_tile.GetPackedValue());
2077 }
2078 }
2079
2080 sx++;
2081 if (sx >= 8) {
2082 sy++;
2083 sx = 0;
2084 }
2085
2086 c++;
2087 if (c >= 64) {
2088 sx = 0;
2089 sy = 0;
2090 c = 0;
2091 }
2092 }
2093
2094 return all_tile_16;
2095}
2096} // namespace
2097
2099 tiles32_unique_.clear();
2100 tiles32_list_.clear();
2101
2102 // Get all tiles16 and packs them into tiles32
2103 std::vector<uint64_t> all_tile_16 = GetAllTile16(map_tiles_);
2104
2105 // Convert to set then back to vector
2106 std::set<uint64_t> unique_tiles_set(all_tile_16.begin(), all_tile_16.end());
2107
2108 std::vector<uint64_t> unique_tiles(all_tile_16);
2109 unique_tiles.assign(unique_tiles_set.begin(), unique_tiles_set.end());
2110
2111 // Create the indexed tiles list
2112 std::unordered_map<uint64_t, uint16_t> all_tiles_indexed;
2113 for (size_t tile32_id = 0; tile32_id < unique_tiles.size(); tile32_id++) {
2114 all_tiles_indexed.insert(
2115 {unique_tiles[tile32_id], static_cast<uint16_t>(tile32_id)});
2116 }
2117
2118 // Add all tiles32 from all maps.
2119 // Convert all tiles32 non-unique IDs into unique array of IDs.
2120 for (int j = 0; j < NumberOfMap32; j++) {
2121 tiles32_list_.emplace_back(all_tiles_indexed[all_tile_16[j]]);
2122 }
2123
2124 // Create the unique tiles list
2125 for (size_t i = 0; i < unique_tiles.size(); ++i) {
2126 tiles32_unique_.emplace_back(gfx::Tile32(unique_tiles[i]));
2127 }
2128
2129 while (tiles32_unique_.size() % 4 != 0) {
2130 gfx::Tile32 padding_tile(0, 0, 0, 0);
2131 tiles32_unique_.emplace_back(padding_tile.GetPackedValue());
2132 }
2133
2134 if (tiles32_unique_.size() > LimitOfMap32) {
2135 return absl::InternalError(absl::StrFormat(
2136 "Number of unique Tiles32: %d Out of: %d\nUnique Tile32 count exceed "
2137 "the limit\nThe ROM Has not been saved\nYou can fill maps with grass "
2138 "tiles to free some space\nOr use the option Clear DW Tiles in the "
2139 "Overworld Menu",
2140 unique_tiles.size(), LimitOfMap32));
2141 }
2142
2143 if (core::FeatureFlags::get().kLogToConsole) {
2144 std::cout << "Number of unique Tiles32: " << tiles32_unique_.size()
2145 << " Saved:" << tiles32_unique_.size()
2146 << " Out of: " << LimitOfMap32 << std::endl;
2147 }
2148
2149 int v = tiles32_unique_.size();
2150 for (int i = v; i < LimitOfMap32; i++) {
2151 gfx::Tile32 padding_tile(420, 420, 420, 420);
2152 tiles32_unique_.emplace_back(padding_tile.GetPackedValue());
2153 }
2154
2155 return absl::OkStatus();
2156}
2157
2159 int bottomLeft = kMap32TileBLExpanded;
2160 int bottomRight = kMap32TileBRExpanded;
2161 int topRight = kMap32TileTRExpanded;
2162 int limit = 0x8A80;
2163
2164 // Updates the pointers too for the tile32
2165 // Top Right
2166 RETURN_IF_ERROR(rom()->WriteLong(0x0176EC, PcToSnes(kMap32TileTRExpanded)));
2168 rom()->WriteLong(0x0176F3, PcToSnes(kMap32TileTRExpanded + 1)));
2170 rom()->WriteLong(0x0176FA, PcToSnes(kMap32TileTRExpanded + 2)));
2172 rom()->WriteLong(0x017701, PcToSnes(kMap32TileTRExpanded + 3)));
2174 rom()->WriteLong(0x017708, PcToSnes(kMap32TileTRExpanded + 4)));
2176 rom()->WriteLong(0x01771A, PcToSnes(kMap32TileTRExpanded + 5)));
2177
2178 // BottomLeft
2179 RETURN_IF_ERROR(rom()->WriteLong(0x01772C, PcToSnes(kMap32TileBLExpanded)));
2181 rom()->WriteLong(0x017733, PcToSnes(kMap32TileBLExpanded + 1)));
2183 rom()->WriteLong(0x01773A, PcToSnes(kMap32TileBLExpanded + 2)));
2185 rom()->WriteLong(0x017741, PcToSnes(kMap32TileBLExpanded + 3)));
2187 rom()->WriteLong(0x017748, PcToSnes(kMap32TileBLExpanded + 4)));
2189 rom()->WriteLong(0x01775A, PcToSnes(kMap32TileBLExpanded + 5)));
2190
2191 // BottomRight
2192 RETURN_IF_ERROR(rom()->WriteLong(0x01776C, PcToSnes(kMap32TileBRExpanded)));
2194 rom()->WriteLong(0x017773, PcToSnes(kMap32TileBRExpanded + 1)));
2196 rom()->WriteLong(0x01777A, PcToSnes(kMap32TileBRExpanded + 2)));
2198 rom()->WriteLong(0x017781, PcToSnes(kMap32TileBRExpanded + 3)));
2200 rom()->WriteLong(0x017788, PcToSnes(kMap32TileBRExpanded + 4)));
2202 rom()->WriteLong(0x01779A, PcToSnes(kMap32TileBRExpanded + 5)));
2203
2204 constexpr int kTilesPer32x32Tile = 6;
2205 int unique_tile_index = 0;
2206 int num_unique_tiles = tiles32_unique_.size();
2207
2208 for (int i = 0; i < num_unique_tiles; i += kTilesPer32x32Tile) {
2209 if (unique_tile_index >= limit) {
2210 return absl::AbortedError("Too many unique tile32 definitions.");
2211 }
2212
2213 // Top Left.
2214 auto top_left = rom()->version_constants().kMap32TileTL;
2215 RETURN_IF_ERROR(rom()->WriteByte(
2216 top_left + i,
2217 (uint8_t)(tiles32_unique_[unique_tile_index].tile0_ & 0xFF)));
2218 RETURN_IF_ERROR(rom()->WriteByte(
2219 top_left + (i + 1),
2220 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile0_ & 0xFF)));
2221 RETURN_IF_ERROR(rom()->WriteByte(
2222 top_left + (i + 2),
2223 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile0_ & 0xFF)));
2224 RETURN_IF_ERROR(rom()->WriteByte(
2225 top_left + (i + 3),
2226 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile0_ & 0xFF)));
2227
2228 RETURN_IF_ERROR(rom()->WriteByte(
2229 top_left + (i + 4),
2230 (uint8_t)(((tiles32_unique_[unique_tile_index].tile0_ >> 4) & 0xF0) +
2231 ((tiles32_unique_[unique_tile_index + 1].tile0_ >> 8) &
2232 0x0F))));
2233 RETURN_IF_ERROR(rom()->WriteByte(
2234 top_left + (i + 5),
2235 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile0_ >> 4) &
2236 0xF0) +
2237 ((tiles32_unique_[unique_tile_index + 3].tile0_ >> 8) &
2238 0x0F))));
2239
2240 // Top Right.
2241 auto top_right = topRight;
2242 RETURN_IF_ERROR(rom()->WriteByte(
2243 top_right + i,
2244 (uint8_t)(tiles32_unique_[unique_tile_index].tile1_ & 0xFF)));
2245 RETURN_IF_ERROR(rom()->WriteByte(
2246 top_right + (i + 1),
2247 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile1_ & 0xFF)));
2248 RETURN_IF_ERROR(rom()->WriteByte(
2249 top_right + (i + 2),
2250 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile1_ & 0xFF)));
2251 RETURN_IF_ERROR(rom()->WriteByte(
2252 top_right + (i + 3),
2253 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile1_ & 0xFF)));
2254
2255 RETURN_IF_ERROR(rom()->WriteByte(
2256 top_right + (i + 4),
2257 (uint8_t)(((tiles32_unique_[unique_tile_index].tile1_ >> 4) & 0xF0) |
2258 ((tiles32_unique_[unique_tile_index + 1].tile1_ >> 8) &
2259 0x0F))));
2260 RETURN_IF_ERROR(rom()->WriteByte(
2261 top_right + (i + 5),
2262 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile1_ >> 4) &
2263 0xF0) |
2264 ((tiles32_unique_[unique_tile_index + 3].tile1_ >> 8) &
2265 0x0F))));
2266
2267 // Bottom Left.
2268 auto bottom_left = bottomLeft;
2269 RETURN_IF_ERROR(rom()->WriteByte(
2270 bottom_left + i,
2271 (uint8_t)(tiles32_unique_[unique_tile_index].tile2_ & 0xFF)));
2272 RETURN_IF_ERROR(rom()->WriteByte(
2273 bottom_left + (i + 1),
2274 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile2_ & 0xFF)));
2275 RETURN_IF_ERROR(rom()->WriteByte(
2276 bottom_left + (i + 2),
2277 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile2_ & 0xFF)));
2278 RETURN_IF_ERROR(rom()->WriteByte(
2279 bottom_left + (i + 3),
2280 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile2_ & 0xFF)));
2281
2282 RETURN_IF_ERROR(rom()->WriteByte(
2283 bottom_left + (i + 4),
2284 (uint8_t)(((tiles32_unique_[unique_tile_index].tile2_ >> 4) & 0xF0) |
2285 ((tiles32_unique_[unique_tile_index + 1].tile2_ >> 8) &
2286 0x0F))));
2287 RETURN_IF_ERROR(rom()->WriteByte(
2288 bottom_left + (i + 5),
2289 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile2_ >> 4) &
2290 0xF0) |
2291 ((tiles32_unique_[unique_tile_index + 3].tile2_ >> 8) &
2292 0x0F))));
2293
2294 // Bottom Right.
2295 auto bottom_right = bottomRight;
2296 RETURN_IF_ERROR(rom()->WriteByte(
2297 bottom_right + i,
2298 (uint8_t)(tiles32_unique_[unique_tile_index].tile3_ & 0xFF)));
2299 RETURN_IF_ERROR(rom()->WriteByte(
2300 bottom_right + (i + 1),
2301 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile3_ & 0xFF)));
2302 RETURN_IF_ERROR(rom()->WriteByte(
2303 bottom_right + (i + 2),
2304 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile3_ & 0xFF)));
2305 RETURN_IF_ERROR(rom()->WriteByte(
2306 bottom_right + (i + 3),
2307 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile3_ & 0xFF)));
2308
2309 RETURN_IF_ERROR(rom()->WriteByte(
2310 bottom_right + (i + 4),
2311 (uint8_t)(((tiles32_unique_[unique_tile_index].tile3_ >> 4) & 0xF0) |
2312 ((tiles32_unique_[unique_tile_index + 1].tile3_ >> 8) &
2313 0x0F))));
2314 RETURN_IF_ERROR(rom()->WriteByte(
2315 bottom_right + (i + 5),
2316 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile3_ >> 4) &
2317 0xF0) |
2318 ((tiles32_unique_[unique_tile_index + 3].tile3_ >> 8) &
2319 0x0F))));
2320
2321 unique_tile_index += 4;
2322 }
2323
2324 return absl::OkStatus();
2325}
2326
2328 util::logf("Saving Map32 Tiles");
2329 constexpr int kMaxUniqueTiles = 0x4540;
2330 constexpr int kTilesPer32x32Tile = 6;
2331
2332 int unique_tile_index = 0;
2333 int num_unique_tiles = tiles32_unique_.size();
2334
2335 for (int i = 0; i < num_unique_tiles; i += kTilesPer32x32Tile) {
2336 if (unique_tile_index >= kMaxUniqueTiles) {
2337 return absl::AbortedError("Too many unique tile32 definitions.");
2338 }
2339
2340 // Top Left.
2341 auto top_left = rom()->version_constants().kMap32TileTL;
2342
2343 RETURN_IF_ERROR(rom()->WriteByte(
2344 top_left + i,
2345 (uint8_t)(tiles32_unique_[unique_tile_index].tile0_ & 0xFF)));
2346 RETURN_IF_ERROR(rom()->WriteByte(
2347 top_left + (i + 1),
2348 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile0_ & 0xFF)));
2349 RETURN_IF_ERROR(rom()->WriteByte(
2350 top_left + (i + 2),
2351 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile0_ & 0xFF)));
2352 RETURN_IF_ERROR(rom()->WriteByte(
2353 top_left + (i + 3),
2354 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile0_ & 0xFF)));
2355
2356 RETURN_IF_ERROR(rom()->WriteByte(
2357 top_left + (i + 4),
2358 (uint8_t)(((tiles32_unique_[unique_tile_index].tile0_ >> 4) & 0xF0) +
2359 ((tiles32_unique_[unique_tile_index + 1].tile0_ >> 8) &
2360 0x0F))));
2361 RETURN_IF_ERROR(rom()->WriteByte(
2362 top_left + (i + 5),
2363 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile0_ >> 4) &
2364 0xF0) +
2365 ((tiles32_unique_[unique_tile_index + 3].tile0_ >> 8) &
2366 0x0F))));
2367
2368 // Top Right.
2369 auto top_right = rom()->version_constants().kMap32TileTR;
2370 RETURN_IF_ERROR(rom()->WriteByte(
2371 top_right + i,
2372 (uint8_t)(tiles32_unique_[unique_tile_index].tile1_ & 0xFF)));
2373 RETURN_IF_ERROR(rom()->WriteByte(
2374 top_right + (i + 1),
2375 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile1_ & 0xFF)));
2376 RETURN_IF_ERROR(rom()->WriteByte(
2377 top_right + (i + 2),
2378 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile1_ & 0xFF)));
2379 RETURN_IF_ERROR(rom()->WriteByte(
2380 top_right + (i + 3),
2381 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile1_ & 0xFF)));
2382
2383 RETURN_IF_ERROR(rom()->WriteByte(
2384 top_right + (i + 4),
2385 (uint8_t)(((tiles32_unique_[unique_tile_index].tile1_ >> 4) & 0xF0) |
2386 ((tiles32_unique_[unique_tile_index + 1].tile1_ >> 8) &
2387 0x0F))));
2388 RETURN_IF_ERROR(rom()->WriteByte(
2389 top_right + (i + 5),
2390 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile1_ >> 4) &
2391 0xF0) |
2392 ((tiles32_unique_[unique_tile_index + 3].tile1_ >> 8) &
2393 0x0F))));
2394
2395 // Bottom Left.
2396 const auto map32TilesBL = rom()->version_constants().kMap32TileBL;
2397 RETURN_IF_ERROR(rom()->WriteByte(
2398 map32TilesBL + i,
2399 (uint8_t)(tiles32_unique_[unique_tile_index].tile2_ & 0xFF)));
2400 RETURN_IF_ERROR(rom()->WriteByte(
2401 map32TilesBL + (i + 1),
2402 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile2_ & 0xFF)));
2403 RETURN_IF_ERROR(rom()->WriteByte(
2404 map32TilesBL + (i + 2),
2405 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile2_ & 0xFF)));
2406 RETURN_IF_ERROR(rom()->WriteByte(
2407 map32TilesBL + (i + 3),
2408 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile2_ & 0xFF)));
2409
2410 RETURN_IF_ERROR(rom()->WriteByte(
2411 map32TilesBL + (i + 4),
2412 (uint8_t)(((tiles32_unique_[unique_tile_index].tile2_ >> 4) & 0xF0) |
2413 ((tiles32_unique_[unique_tile_index + 1].tile2_ >> 8) &
2414 0x0F))));
2415 RETURN_IF_ERROR(rom()->WriteByte(
2416 map32TilesBL + (i + 5),
2417 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile2_ >> 4) &
2418 0xF0) |
2419 ((tiles32_unique_[unique_tile_index + 3].tile2_ >> 8) &
2420 0x0F))));
2421
2422 // Bottom Right.
2423 const auto map32TilesBR = rom()->version_constants().kMap32TileBR;
2424 RETURN_IF_ERROR(rom()->WriteByte(
2425 map32TilesBR + i,
2426 (uint8_t)(tiles32_unique_[unique_tile_index].tile3_ & 0xFF)));
2427 RETURN_IF_ERROR(rom()->WriteByte(
2428 map32TilesBR + (i + 1),
2429 (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile3_ & 0xFF)));
2430 RETURN_IF_ERROR(rom()->WriteByte(
2431 map32TilesBR + (i + 2),
2432 (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile3_ & 0xFF)));
2433 RETURN_IF_ERROR(rom()->WriteByte(
2434 map32TilesBR + (i + 3),
2435 (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile3_ & 0xFF)));
2436
2437 RETURN_IF_ERROR(rom()->WriteByte(
2438 map32TilesBR + (i + 4),
2439 (uint8_t)(((tiles32_unique_[unique_tile_index].tile3_ >> 4) & 0xF0) |
2440 ((tiles32_unique_[unique_tile_index + 1].tile3_ >> 8) &
2441 0x0F))));
2442 RETURN_IF_ERROR(rom()->WriteByte(
2443 map32TilesBR + (i + 5),
2444 (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile3_ >> 4) &
2445 0xF0) |
2446 ((tiles32_unique_[unique_tile_index + 3].tile3_ >> 8) &
2447 0x0F))));
2448
2449 unique_tile_index += 4;
2450 num_unique_tiles += 2;
2451 }
2452
2453 return absl::OkStatus();
2454}
2455
2458 rom()->WriteLong(SnesToPc(0x008865), PcToSnes(kMap16TilesExpanded)));
2460 rom()->WriteLong(SnesToPc(0x0EDE4F), PcToSnes(kMap16TilesExpanded)));
2462 rom()->WriteLong(SnesToPc(0x0EDEE9), PcToSnes(kMap16TilesExpanded)));
2463
2465 rom()->WriteLong(SnesToPc(0x1BBC2D), PcToSnes(kMap16TilesExpanded + 2)));
2467 rom()->WriteLong(SnesToPc(0x1BBC4C), PcToSnes(kMap16TilesExpanded)));
2469 rom()->WriteLong(SnesToPc(0x1BBCC2), PcToSnes(kMap16TilesExpanded + 4)));
2471 rom()->WriteLong(SnesToPc(0x1BBCCB), PcToSnes(kMap16TilesExpanded + 6)));
2472
2474 rom()->WriteLong(SnesToPc(0x1BBEF6), PcToSnes(kMap16TilesExpanded)));
2476 rom()->WriteLong(SnesToPc(0x1BBF23), PcToSnes(kMap16TilesExpanded)));
2478 rom()->WriteLong(SnesToPc(0x1BC041), PcToSnes(kMap16TilesExpanded)));
2480 rom()->WriteLong(SnesToPc(0x1BC9B3), PcToSnes(kMap16TilesExpanded)));
2481
2483 rom()->WriteLong(SnesToPc(0x1BC9BA), PcToSnes(kMap16TilesExpanded + 2)));
2485 rom()->WriteLong(SnesToPc(0x1BC9C1), PcToSnes(kMap16TilesExpanded + 4)));
2487 rom()->WriteLong(SnesToPc(0x1BC9C8), PcToSnes(kMap16TilesExpanded + 6)));
2488
2490 rom()->WriteLong(SnesToPc(0x1BCA40), PcToSnes(kMap16TilesExpanded)));
2492 rom()->WriteLong(SnesToPc(0x1BCA47), PcToSnes(kMap16TilesExpanded + 2)));
2494 rom()->WriteLong(SnesToPc(0x1BCA4E), PcToSnes(kMap16TilesExpanded + 4)));
2496 rom()->WriteLong(SnesToPc(0x1BCA55), PcToSnes(kMap16TilesExpanded + 6)));
2497
2499 rom()->WriteLong(SnesToPc(0x02F457), PcToSnes(kMap16TilesExpanded)));
2501 rom()->WriteLong(SnesToPc(0x02F45E), PcToSnes(kMap16TilesExpanded + 2)));
2503 rom()->WriteLong(SnesToPc(0x02F467), PcToSnes(kMap16TilesExpanded + 4)));
2505 rom()->WriteLong(SnesToPc(0x02F46E), PcToSnes(kMap16TilesExpanded + 6)));
2507 rom()->WriteLong(SnesToPc(0x02F51F), PcToSnes(kMap16TilesExpanded)));
2509 rom()->WriteLong(SnesToPc(0x02F526), PcToSnes(kMap16TilesExpanded + 4)));
2511 rom()->WriteLong(SnesToPc(0x02F52F), PcToSnes(kMap16TilesExpanded + 2)));
2513 rom()->WriteLong(SnesToPc(0x02F536), PcToSnes(kMap16TilesExpanded + 6)));
2514
2516 rom()->WriteShort(SnesToPc(0x02FE1C), PcToSnes(kMap16TilesExpanded)));
2518 rom()->WriteShort(SnesToPc(0x02FE23), PcToSnes(kMap16TilesExpanded + 4)));
2520 rom()->WriteShort(SnesToPc(0x02FE2C), PcToSnes(kMap16TilesExpanded + 2)));
2522 rom()->WriteShort(SnesToPc(0x02FE33), PcToSnes(kMap16TilesExpanded + 6)));
2523
2524 RETURN_IF_ERROR(rom()->WriteByte(
2525 SnesToPc(0x02FD28),
2526 static_cast<uint8_t>(PcToSnes(kMap16TilesExpanded) >> 16)));
2527 RETURN_IF_ERROR(rom()->WriteByte(
2528 SnesToPc(0x02FD39),
2529 static_cast<uint8_t>(PcToSnes(kMap16TilesExpanded) >> 16)));
2530
2531 int tpos = kMap16TilesExpanded;
2532 for (int i = 0; i < NumberOfMap16Ex; i += 1) // 4096
2533 {
2535 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_)));
2536 tpos += 2;
2538 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_)));
2539 tpos += 2;
2541 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_)));
2542 tpos += 2;
2544 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_)));
2545 tpos += 2;
2546 }
2547
2548 return absl::OkStatus();
2549}
2550
2552 util::logf("Saving Map16 Tiles");
2553 int tpos = kMap16Tiles;
2554 // 3760
2555 for (int i = 0; i < NumberOfMap16; i += 1) {
2557 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_)))
2558 tpos += 2;
2560 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_)))
2561 tpos += 2;
2563 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_)))
2564 tpos += 2;
2566 rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_)))
2567 tpos += 2;
2568 }
2569 return absl::OkStatus();
2570}
2571
2573 util::logf("Saving Entrances");
2574
2575 // Use expanded entrance tables if available
2576 if (expanded_entrances_) {
2577 for (int i = 0; i < kNumOverworldEntrances; i++) {
2578 RETURN_IF_ERROR(rom()->WriteShort(kOverworldEntranceMapExpanded + (i * 2),
2579 all_entrances_[i].map_id_))
2580 RETURN_IF_ERROR(rom()->WriteShort(kOverworldEntrancePosExpanded + (i * 2),
2581 all_entrances_[i].map_pos_))
2583 all_entrances_[i].entrance_id_))
2584 }
2585 } else {
2586 for (int i = 0; i < kNumOverworldEntrances; i++) {
2587 RETURN_IF_ERROR(rom()->WriteShort(kOverworldEntranceMap + (i * 2),
2588 all_entrances_[i].map_id_))
2589 RETURN_IF_ERROR(rom()->WriteShort(kOverworldEntrancePos + (i * 2),
2590 all_entrances_[i].map_pos_))
2592 all_entrances_[i].entrance_id_))
2593 }
2594 }
2595
2596 for (int i = 0; i < kNumOverworldHoles; i++) {
2598 rom()->WriteShort(kOverworldHoleArea + (i * 2), all_holes_[i].map_id_))
2600 rom()->WriteShort(kOverworldHolePos + (i * 2), all_holes_[i].map_pos_))
2602 all_holes_[i].entrance_id_))
2603 }
2604
2605 return absl::OkStatus();
2606}
2607
2608absl::Status Overworld::SaveExits() {
2609 util::logf("Saving Exits");
2610
2611 // ASM version 0x03 added SW support and the exit leading to Zora's Domain specifically
2612 // needs to be updated because its camera values are incorrect.
2613 // We only update it if it was a vanilla ROM though because we don't know if the
2614 // user has already adjusted it or not.
2615 uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
2616 if (asm_version == 0x00) {
2617 // Apply special fix for Zora's Domain exit (index 0x4D)
2618 // TODO: Implement SpecialUpdatePosition for OverworldExit
2619 // if (all_exits_.size() > 0x4D) {
2620 // all_exits_[0x4D].SpecialUpdatePosition();
2621 // }
2622 }
2623
2624 for (int i = 0; i < kNumOverworldExits; i++) {
2626 rom()->WriteShort(OWExitRoomId + (i * 2), all_exits_[i].room_id_));
2627 RETURN_IF_ERROR(rom()->WriteByte(OWExitMapId + i, all_exits_[i].map_id_));
2629 rom()->WriteShort(OWExitVram + (i * 2), all_exits_[i].map_pos_));
2631 rom()->WriteShort(OWExitYScroll + (i * 2), all_exits_[i].y_scroll_));
2633 rom()->WriteShort(OWExitXScroll + (i * 2), all_exits_[i].x_scroll_));
2635 rom()->WriteByte(OWExitYPlayer + (i * 2), all_exits_[i].y_player_));
2637 rom()->WriteByte(OWExitXPlayer + (i * 2), all_exits_[i].x_player_));
2639 rom()->WriteByte(OWExitYCamera + (i * 2), all_exits_[i].y_camera_));
2641 rom()->WriteByte(OWExitXCamera + (i * 2), all_exits_[i].x_camera_));
2643 rom()->WriteByte(OWExitUnk1 + i, all_exits_[i].scroll_mod_y_));
2645 rom()->WriteByte(OWExitUnk2 + i, all_exits_[i].scroll_mod_x_));
2646 RETURN_IF_ERROR(rom()->WriteShort(OWExitDoorType1 + (i * 2),
2647 all_exits_[i].door_type_1_));
2648 RETURN_IF_ERROR(rom()->WriteShort(OWExitDoorType2 + (i * 2),
2649 all_exits_[i].door_type_2_));
2650 }
2651
2652 return absl::OkStatus();
2653}
2654
2655namespace {
2656bool CompareItemsArrays(std::vector<OverworldItem> item_array1,
2657 std::vector<OverworldItem> item_array2) {
2658 if (item_array1.size() != item_array2.size()) {
2659 return false;
2660 }
2661
2662 bool match;
2663 for (size_t i = 0; i < item_array1.size(); i++) {
2664 match = false;
2665 for (size_t j = 0; j < item_array2.size(); j++) {
2666 // Check all sprite in 2nd array if one match
2667 if (item_array1[i].x_ == item_array2[j].x_ &&
2668 item_array1[i].y_ == item_array2[j].y_ &&
2669 item_array1[i].id_ == item_array2[j].id_) {
2670 match = true;
2671 break;
2672 }
2673 }
2674
2675 if (!match) {
2676 return false;
2677 }
2678 }
2679
2680 return true;
2681}
2682} // namespace
2683
2684absl::Status Overworld::SaveItems() {
2685 std::vector<std::vector<OverworldItem>> room_items(
2687
2688 for (int i = 0; i < kNumOverworldMapItemPointers; i++) {
2689 room_items[i] = std::vector<OverworldItem>();
2690 for (const OverworldItem& item : all_items_) {
2691 if (item.room_map_id_ == i) {
2692 room_items[i].emplace_back(item);
2693 if (item.id_ == 0x86) {
2694 RETURN_IF_ERROR(rom()->WriteWord(
2695 0x16DC5 + (i * 2), (item.game_x_ + (item.game_y_ * 64)) * 2));
2696 }
2697 }
2698 }
2699 }
2700
2701 int data_pos = kOverworldItemsPointers + 0x100;
2702 int item_pointers[kNumOverworldMapItemPointers];
2703 int item_pointers_reuse[kNumOverworldMapItemPointers];
2704 int empty_pointer = 0;
2705 for (int i = 0; i < kNumOverworldMapItemPointers; i++) {
2706 item_pointers_reuse[i] = -1;
2707 for (int ci = 0; ci < i; ci++) {
2708 if (room_items[i].empty()) {
2709 item_pointers_reuse[i] = -2;
2710 break;
2711 }
2712
2713 // Copy into separator vectors from i to ci, then ci to end
2714 if (CompareItemsArrays(
2715 std::vector<OverworldItem>(room_items[i].begin(),
2716 room_items[i].end()),
2717 std::vector<OverworldItem>(room_items[ci].begin(),
2718 room_items[ci].end()))) {
2719 item_pointers_reuse[i] = ci;
2720 break;
2721 }
2722 }
2723 }
2724
2725 for (int i = 0; i < kNumOverworldMapItemPointers; i++) {
2726 if (item_pointers_reuse[i] == -1) {
2727 item_pointers[i] = data_pos;
2728 for (const OverworldItem& item : room_items[i]) {
2729 short map_pos =
2730 static_cast<short>(((item.game_y_ << 6) + item.game_x_) << 1);
2731
2732 uint32_t data = static_cast<uint8_t>(map_pos & 0xFF) |
2733 static_cast<uint8_t>(map_pos >> 8) |
2734 static_cast<uint8_t>(item.id_);
2735 RETURN_IF_ERROR(rom()->WriteLong(data_pos, data));
2736 data_pos += 3;
2737 }
2738
2739 empty_pointer = data_pos;
2740 RETURN_IF_ERROR(rom()->WriteWord(data_pos, 0xFFFF));
2741 data_pos += 2;
2742 } else if (item_pointers_reuse[i] == -2) {
2743 item_pointers[i] = empty_pointer;
2744 } else {
2745 item_pointers[i] = item_pointers[item_pointers_reuse[i]];
2746 }
2747
2748 int snesaddr = PcToSnes(item_pointers[i]);
2750 rom()->WriteWord(kOverworldItemsPointers + (i * 2), snesaddr));
2751 }
2752
2753 if (data_pos > kOverworldItemsEndData) {
2754 return absl::AbortedError("Too many items");
2755 }
2756
2757 util::logf("End of Items : %d", data_pos);
2758
2759 return absl::OkStatus();
2760}
2761
2763 util::logf("Saving Map Overlays");
2764
2765 // Generate the new overlay code that handles interactive overlays
2766 std::vector<uint8_t> new_overlay_code = {
2767 0xC2, 0x30, // REP #$30
2768 0xA5, 0x8A, // LDA $8A
2769 0x0A, 0x18, // ASL : CLC
2770 0x65, 0x8A, // ADC $8A
2771 0xAA, // TAX
2772 0xBF, 0x00, 0x00, 0x00, // LDA, X
2773 0x85, 0x00, // STA $00
2774 0xBF, 0x00, 0x00, 0x00, // LDA, X +2
2775 0x85, 0x02, // STA $02
2776 0x4B, // PHK
2777 0xF4, 0x00, 0x00, // This position +3 ?
2778 0xDC, 0x00, 0x00, // JML [$00 00]
2779 0xE2, 0x30, // SEP #$30
2780 0xAB, // PLB
2781 0x6B, // RTL
2782 };
2783
2784 // Write overlay code to ROM
2785 constexpr int kOverlayCodeStart = 0x077657;
2786 RETURN_IF_ERROR(rom()->WriteVector(kOverlayCodeStart, new_overlay_code));
2787
2788 // Set up overlay pointers
2789 int ptr_start = kOverlayCodeStart + 0x20;
2790 int snes_ptr_start = PcToSnes(ptr_start);
2791
2792 // Write overlay pointer addresses in the code
2793 RETURN_IF_ERROR(rom()->WriteLong(kOverlayCodeStart + 10, snes_ptr_start));
2794 RETURN_IF_ERROR(rom()->WriteLong(kOverlayCodeStart + 16, snes_ptr_start + 2));
2795
2796 int pea_addr = PcToSnes(kOverlayCodeStart + 27);
2797 RETURN_IF_ERROR(rom()->WriteShort(kOverlayCodeStart + 23, pea_addr));
2798
2799 // Write overlay data to expanded space
2800 constexpr int kExpandedOverlaySpace = 0x120000;
2801 int pos = kExpandedOverlaySpace;
2802 int ptr_pos = kOverlayCodeStart + 32;
2803
2804 for (int i = 0; i < kNumOverworldMaps; i++) {
2805 int snes_addr = PcToSnes(pos);
2806 RETURN_IF_ERROR(rom()->WriteLong(ptr_pos, snes_addr & 0xFFFFFF));
2807 ptr_pos += 3;
2808
2809 // Write overlay data for each map that has overlays
2810 if (overworld_maps_[i].has_overlay()) {
2811 const auto& overlay_data = overworld_maps_[i].overlay_data();
2812 for (size_t t = 0; t < overlay_data.size(); t += 3) {
2813 if (t + 2 < overlay_data.size()) {
2814 // Generate LDA/STA sequence for each overlay tile
2815 RETURN_IF_ERROR(rom()->WriteByte(pos, 0xA9)); // LDA #$
2816 RETURN_IF_ERROR(rom()->WriteShort(
2817 pos + 1, overlay_data[t] | (overlay_data[t + 1] << 8)));
2818 pos += 3;
2819
2820 RETURN_IF_ERROR(rom()->WriteByte(pos, 0x8D)); // STA $xxxx
2821 RETURN_IF_ERROR(rom()->WriteShort(pos + 1, overlay_data[t + 2]));
2822 pos += 3;
2823 }
2824 }
2825 }
2826
2827 RETURN_IF_ERROR(rom()->WriteByte(pos, 0x6B)); // RTL
2828 pos++;
2829 }
2830
2831 return absl::OkStatus();
2832}
2833
2835 util::logf("Saving Overworld Tiles Types");
2836
2837 for (int i = 0; i < kNumTileTypes; i++) {
2839 rom()->WriteByte(overworldTilesType + i, all_tiles_types_[i]));
2840 }
2841
2842 return absl::OkStatus();
2843}
2844
2845absl::Status Overworld::SaveCustomOverworldASM(bool enable_bg_color,
2846 bool enable_main_palette,
2847 bool enable_mosaic,
2848 bool enable_gfx_groups,
2849 bool enable_subscreen_overlay,
2850 bool enable_animated) {
2851 util::logf("Applying Custom Overworld ASM");
2852
2853 // Set the enable/disable settings
2854 uint8_t enable_value = enable_bg_color ? 0xFF : 0x00;
2856 rom()->WriteByte(OverworldCustomAreaSpecificBGEnabled, enable_value));
2857
2858 enable_value = enable_main_palette ? 0xFF : 0x00;
2860 rom()->WriteByte(OverworldCustomMainPaletteEnabled, enable_value));
2861
2862 enable_value = enable_mosaic ? 0xFF : 0x00;
2863 RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomMosaicEnabled, enable_value));
2864
2865 enable_value = enable_gfx_groups ? 0xFF : 0x00;
2867 rom()->WriteByte(OverworldCustomTileGFXGroupEnabled, enable_value));
2868
2869 enable_value = enable_animated ? 0xFF : 0x00;
2871 rom()->WriteByte(OverworldCustomAnimatedGFXEnabled, enable_value));
2872
2873 enable_value = enable_subscreen_overlay ? 0xFF : 0x00;
2875 rom()->WriteByte(OverworldCustomSubscreenOverlayEnabled, enable_value));
2876
2877 // Write the main palette table
2878 for (int i = 0; i < kNumOverworldMaps; i++) {
2880 overworld_maps_[i].main_palette()));
2881 }
2882
2883 // Write the mosaic table
2884 for (int i = 0; i < kNumOverworldMaps; i++) {
2885 const auto& mosaic = overworld_maps_[i].mosaic_expanded();
2886 // .... udlr bit format
2887 uint8_t mosaic_byte = (mosaic[0] ? 0x08 : 0x00) | // up
2888 (mosaic[1] ? 0x04 : 0x00) | // down
2889 (mosaic[2] ? 0x02 : 0x00) | // left
2890 (mosaic[3] ? 0x01 : 0x00); // right
2891
2893 rom()->WriteByte(OverworldCustomMosaicArray + i, mosaic_byte));
2894 }
2895
2896 // Write the main and animated gfx tiles table
2897 for (int i = 0; i < kNumOverworldMaps; i++) {
2898 for (int j = 0; j < 8; j++) {
2900 rom()->WriteByte(OverworldCustomTileGFXGroupArray + (i * 8) + j,
2901 overworld_maps_[i].custom_tileset(j)));
2902 }
2904 overworld_maps_[i].animated_gfx()));
2905 }
2906
2907 // Write the subscreen overlay table
2908 for (int i = 0; i < kNumOverworldMaps; i++) {
2910 rom()->WriteShort(OverworldCustomSubscreenOverlayArray + (i * 2),
2911 overworld_maps_[i].subscreen_overlay()));
2912 }
2913
2914 return absl::OkStatus();
2915}
2916
2918 util::logf("Saving Area Specific Background Colors");
2919
2920 // Write area-specific background colors if enabled
2921 for (int i = 0; i < kNumOverworldMaps; i++) {
2922 uint16_t bg_color = overworld_maps_[i].area_specific_bg_color();
2923 RETURN_IF_ERROR(rom()->WriteShort(
2924 OverworldCustomAreaSpecificBGPalette + (i * 2), bg_color));
2925 }
2926
2927 return absl::OkStatus();
2928}
2929
2931 util::logf("Saving Map Properties");
2932 for (int i = 0; i < kDarkWorldMapIdStart; i++) {
2933 RETURN_IF_ERROR(rom()->WriteByte(kAreaGfxIdPtr + i,
2934 overworld_maps_[i].area_graphics()));
2936 overworld_maps_[i].area_palette()));
2937 RETURN_IF_ERROR(rom()->WriteByte(kOverworldSpriteset + i,
2938 overworld_maps_[i].sprite_graphics(0)));
2940 rom()->WriteByte(kOverworldSpriteset + kDarkWorldMapIdStart + i,
2941 overworld_maps_[i].sprite_graphics(1)));
2944 overworld_maps_[i].sprite_graphics(2)));
2946 overworld_maps_[i].sprite_palette(0)));
2949 overworld_maps_[i].sprite_palette(1)));
2950 RETURN_IF_ERROR(rom()->WriteByte(
2952 overworld_maps_[i].sprite_palette(2)));
2953 }
2954
2955 for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) {
2956 RETURN_IF_ERROR(rom()->WriteByte(kAreaGfxIdPtr + i,
2957 overworld_maps_[i].area_graphics()));
2958 RETURN_IF_ERROR(rom()->WriteByte(kOverworldSpriteset + i,
2959 overworld_maps_[i].sprite_graphics(0)));
2961 rom()->WriteByte(kOverworldSpriteset + kDarkWorldMapIdStart + i,
2962 overworld_maps_[i].sprite_graphics(1)));
2965 overworld_maps_[i].sprite_graphics(2)));
2967 overworld_maps_[i].area_palette()));
2970 overworld_maps_[i].sprite_palette(0)));
2971 RETURN_IF_ERROR(rom()->WriteByte(
2973 overworld_maps_[i].sprite_palette(1)));
2974 RETURN_IF_ERROR(rom()->WriteByte(kOverworldSpritePaletteIds + 192 + i,
2975 overworld_maps_[i].sprite_palette(2)));
2976 }
2977
2978 return absl::OkStatus();
2979}
2980
2981absl::Status Overworld::SaveMusic() {
2982 util::logf("Saving Music Data");
2983
2984 // Save music data for Light World maps
2985 for (int i = 0; i < kDarkWorldMapIdStart; i++) {
2987 overworld_maps_[i].area_music(0)));
2988 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicZelda + i,
2989 overworld_maps_[i].area_music(1)));
2991 overworld_maps_[i].area_music(2)));
2993 overworld_maps_[i].area_music(3)));
2994 }
2995
2996 // Save music data for Dark World maps
2997 for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) {
3000 overworld_maps_[i].area_music(0)));
3001 }
3002
3003 return absl::OkStatus();
3004}
3005
3007 util::logf("Saving V3 Area Sizes");
3008
3009 // Check if this is a v3 ROM
3010 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
3011 if (asm_version < 3 || asm_version == 0xFF) {
3012 return absl::OkStatus(); // Not a v3 ROM, nothing to do
3013 }
3014
3015 // Save area sizes to the expanded table
3016 for (int i = 0; i < kNumOverworldMaps; i++) {
3017 uint8_t area_size_byte =
3018 static_cast<uint8_t>(overworld_maps_[i].area_size());
3019 RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, area_size_byte));
3020 }
3021
3022 // Save message IDs to expanded table
3023 for (int i = 0; i < kNumOverworldMaps; i++) {
3024 uint16_t message_id = overworld_maps_[i].message_id();
3026 rom()->WriteShort(kOverworldMessagesExpanded + (i * 2), message_id));
3027 }
3028
3029 return absl::OkStatus();
3030}
3031
3032} // namespace zelda3
3033} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
static Flags & get()
Definition features.h:79
RAII timer for automatic timing management.
Tile composition of four 16x16 tiles.
Definition snes_tile.h:88
uint64_t GetPackedValue() const
Definition snes_tile.h:118
SNES 16-bit tile metadata container.
Definition snes_tile.h:50
absl::Status SaveMap32Expanded()
absl::Status LoadExits()
Definition overworld.cc:747
absl::Status DecompressAllMapTilesParallel()
Definition overworld.cc:539
std::vector< uint16_t > tiles32_list_
Definition overworld.h:367
absl::Status Load(Rom *rom)
Definition overworld.cc:27
std::vector< OverworldItem > all_items_
Definition overworld.h:361
void OrganizeMapTiles(std::vector< uint8_t > &bytes, std::vector< uint8_t > &bytes2, int i, int sx, int sy, int &ttpos)
Definition overworld.cc:513
std::array< int, kNumOverworldMaps > map_pointers1
Definition overworld.h:377
std::vector< gfx::Tile32 > tiles32_unique_
Definition overworld.h:365
absl::Status SaveMapProperties()
absl::Status SaveMap32Tiles()
std::vector< OverworldEntrance > all_entrances_
Definition overworld.h:358
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)
OverworldMapTiles map_tiles_
Definition overworld.h:352
absl::Status SaveMap16Tiles()
absl::Status SaveAreaSizes()
absl::Status SaveLargeMaps()
std::array< uint8_t, kNumOverworldMaps > map_parent_
Definition overworld.h:370
void AssignWorldTiles(int x, int y, int sx, int sy, int tpos, OverworldBlockset &world)
Definition overworld.cc:501
std::array< uint8_t, kNumTileTypes > all_tiles_types_
Definition overworld.h:371
absl::Status CreateTile32Tilemap()
std::array< int, kNumOverworldMaps > map_pointers1_id
Definition overworld.h:375
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)
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)
absl::Status LoadItems()
Definition overworld.cc:791
absl::Status SaveEntrances()
absl::Status SaveExits()
absl::Status LoadSprites()
Definition overworld.cc:858
absl::Status EnsureMapBuilt(int map_index)
Build a map on-demand if it hasn't been built yet.
Definition overworld.cc:656
absl::Status LoadHoles()
Definition overworld.cc:727
std::vector< OverworldMap > overworld_maps_
Definition overworld.h:357
absl::Status SaveItems()
absl::Status SaveAreaSpecificBGColors()
absl::Status LoadEntrances()
Definition overworld.cc:687
absl::Status Save(Rom *rom)
Definition overworld.cc:936
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)
absl::Status SaveOverworldMaps()
Definition overworld.cc:960
std::array< std::vector< Sprite >, 3 > all_sprites_
Definition overworld.h:372
std::array< int, kNumOverworldMaps > map_pointers2_id
Definition overworld.h:376
absl::Status LoadOverworldMaps()
Definition overworld.cc:600
std::vector< gfx::Tile16 > tiles16_
Definition overworld.h:363
absl::Status AssembleMap16Tiles()
Definition overworld.cc:466
absl::Status LoadSpritesFromMap(int sprite_start, int sprite_count, int sprite_index)
Definition overworld.cc:895
std::array< std::vector< uint8_t >, kNumOverworldMaps > map_data_p1
Definition overworld.h:373
absl::Status SaveLargeMapsExpanded()
void AssignMapSizes(std::vector< OverworldMap > &maps)
Loads all maps from ROM to see what size they are.
Definition overworld.cc:169
absl::Status SaveMap16Expanded()
std::vector< OverworldExit > all_exits_
Definition overworld.h:360
std::array< int, kNumOverworldMaps > map_pointers2
Definition overworld.h:378
absl::StatusOr< uint16_t > GetTile16ForTile32(int index, int quadrant, int dimension, const uint32_t *map32address)
Definition overworld.cc:395
std::array< std::vector< uint8_t >, kNumOverworldMaps > map_data_p2
Definition overworld.h:374
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)
absl::Status SaveMapOverlays()
absl::Status AssembleMap32Tiles()
Definition overworld.cc:406
OverworldBlockset & GetMapTiles(int world_type)
Definition overworld.h:244
absl::Status SaveOverworldTilesType()
absl::Status SaveMusic()
absl::Status ConfigureMultiAreaMap(int parent_index, AreaSizeEnum size)
Configure a multi-area map structure (Large/Wide/Tall)
Definition overworld.cc:257
std::vector< OverworldEntrance > all_holes_
Definition overworld.h:359
#define LOG_DEBUG(category, format,...)
Definition log.h:104
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:61
std::vector< uint8_t > HyruleMagicDecompress(uint8_t const *src, int *const size, int const p_big_endian)
TileInfo GetTilesInfo(uint16_t tile)
Definition snes_tile.cc:354
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:116
std::string HexLong(uint32_t dword, HexStringParams params)
Definition hex.cc:52
bool CompareItemsArrays(std::vector< OverworldItem > item_array1, std::vector< OverworldItem > item_array2)
std::vector< uint64_t > GetAllTile16(OverworldMapTiles &map_tiles_)
constexpr int OWExitYScroll
constexpr int kAreaGfxIdPtr
Definition overworld.h:40
constexpr int OverworldCustomTileGFXGroupEnabled
constexpr int kOverworldHoleArea
constexpr int OverworldCustomAreaSpecificBGEnabled
constexpr int kOverworldTransitionPositionY
Definition overworld.h:64
constexpr int kOverworldEntrancePos
constexpr int kOverworldHoleEntrance
constexpr int kNumMapsPerWorld
Definition overworld.h:127
constexpr int kOverworldSpriteset
Definition overworld.h:33
constexpr int kMap16ExpandedFlagPos
Definition overworld.h:88
constexpr int LimitOfMap32
Definition overworld.h:124
constexpr int NumberOfMap16Ex
Definition overworld.h:123
constexpr int kOverworldScreenTileMapChangeByScreen1
Definition overworld.h:69
constexpr int kOverworldScreenTileMapChangeByScreen2Expanded
constexpr int kOverworldMapDataOverflow
Definition overworld.h:74
constexpr int kOverworldMapSizeHighByte
Definition overworld.h:55
constexpr int kOverworldEntranceEntranceIdExpanded
constexpr int overworldItemsAddressBank
Definition overworld.h:107
constexpr int overworldSpritesBeginingExpanded
Definition overworld.h:91
constexpr int kNumTileTypes
Definition overworld.h:117
constexpr int NumberOfMap32
Definition overworld.h:126
constexpr int kNumOverworldMapItemPointers
constexpr int kOverworldScreenSize
Definition overworld.h:66
constexpr int kOverworldScreenTileMapChangeByScreen4
Definition overworld.h:72
constexpr int kNumTile16Individual
Definition overworld.h:120
constexpr int kNumOverworldHoles
constexpr int kSpecialWorldMapIdStart
constexpr int OWExitDoorType2
constexpr int kOverworldEntranceEntranceId
constexpr int OverworldCustomMosaicArray
constexpr int kOverworldTransitionPositionXExpanded
constexpr int kMap16Tiles
Definition overworld.h:118
constexpr int overworldSpritesAgahnimExpanded
Definition overworld.h:93
constexpr int OverworldCustomAnimatedGFXEnabled
constexpr int OverworldCustomMainPaletteEnabled
constexpr int kOverworldHolePos
constexpr int kNumOverworldMaps
Definition overworld.h:119
constexpr int OverworldCustomMainPaletteArray
constexpr int kOverworldTransitionPositionYExpanded
constexpr int kOverworldEntranceMap
constexpr int kOverworldItemsEndData
constexpr int OWExitYCamera
constexpr int kOverworldMapParentIdExpanded
constexpr int kOverworldMusicBeginning
Definition overworld.h:43
constexpr int kMap32TileBLExpanded
Definition overworld.h:84
constexpr int kOverworldTransitionPositionX
Definition overworld.h:65
constexpr int OWExitXScroll
constexpr int kOverworldMusicDarkWorld
Definition overworld.h:47
constexpr int OWExitRoomId
constexpr int OWExitXCamera
constexpr int OWExitUnk1
constexpr int OWExitYPlayer
constexpr int kOverworldScreenSizeForLoading
Definition overworld.h:67
constexpr int kOverworldSpritePaletteIds
Definition overworld.h:31
constexpr int overworldTilesType
Definition overworld.h:101
constexpr int transition_target_westExpanded
constexpr int overworldItemsAddress
Definition overworld.h:106
constexpr int kMap32TileBRExpanded
Definition overworld.h:85
constexpr int kMap32TileCountExpanded
Definition overworld.h:86
constexpr int OWExitMapId
constexpr int OWExitUnk2
constexpr int kTransitionTargetWest
Definition overworld.h:77
constexpr int OverworldCustomASMHasBeenApplied
constexpr int kOverworldMusicAgahnim
Definition overworld.h:46
constexpr int kOverworldSpritesZelda
Definition overworld.h:38
constexpr int kOverworldMapParentId
Definition overworld.h:63
constexpr int kMap32ExpandedFlagPos
Definition overworld.h:87
constexpr int kOverworldEntrancePosExpanded
constexpr int OWExitDoorType1
constexpr int kOverworldItemsPointers
constexpr int kOverworldMessagesExpanded
constexpr int kOverworldMusicMasterSword
Definition overworld.h:45
constexpr int kOverworldScreenTileMapChangeByScreen3Expanded
constexpr int kOverworldMusicZelda
Definition overworld.h:44
constexpr int kNumOverworldExits
constexpr int transition_target_northExpanded
constexpr int NumberOfMap16
Definition overworld.h:122
constexpr int kOverworldMapSize
Definition overworld.h:52
constexpr int kOverworldScreenTileMapChangeByScreen2
Definition overworld.h:70
constexpr int kOverworldEntranceMapExpanded
constexpr int OverworldCustomAnimatedGFXArray
constexpr int kDarkWorldMapIdStart
constexpr int OverworldCustomMosaicEnabled
constexpr int kOverworldCompressedMapPos
Definition overworld.h:114
constexpr int kOverworldScreenTileMapChangeByScreen4Expanded
constexpr int kOverworldSpritesBeginning
Definition overworld.h:36
std::vector< std::vector< uint16_t > > OverworldBlockset
Represents tile32 data for the overworld.
constexpr int kOverworldScreenTileMapChangeByScreen3
Definition overworld.h:71
constexpr int kOverworldScreenTileMapChangeByScreen1Expanded
constexpr int OverworldCustomTileGFXGroupArray
constexpr int kMap16TilesExpanded
Definition overworld.h:82
constexpr int kOverworldEntranceExpandedFlagPos
Definition overworld.h:89
constexpr int OverworldCustomSubscreenOverlayEnabled
constexpr int kNumOverworldEntrances
constexpr int OverworldCustomAreaSpecificBGPalette
constexpr int kOverworldSpritesAgahnim
Definition overworld.h:37
constexpr int kTransitionTargetNorth
Definition overworld.h:76
constexpr int overworldSpritesZeldaExpanded
Definition overworld.h:92
constexpr int kOverworldCompressedOverflowPos
Definition overworld.h:115
constexpr int kOverlayCodeStart
constexpr int kMap32TileTRExpanded
Definition overworld.h:83
constexpr int OWExitXPlayer
constexpr int OWExitVram
constexpr int kOverworldMapPaletteIds
Definition overworld.h:30
constexpr int OverworldCustomSubscreenOverlayArray
Main namespace for the application.
uint32_t PcToSnes(uint32_t addr)
Definition snes.h:17
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
Overworld map tile32 data.