#include "overworld.h" #include #include #include #include #include #include #include #include #include #include #include #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_format.h" #include "app/gfx/debug/performance/performance_profiler.h" #include "app/gfx/types/snes_tile.h" #include "app/gfx/util/compression.h" #include "app/rom.h" #include "app/snes.h" #include "core/features.h" #include "util/hex.h" #include "util/log.h" #include "util/macro.h" #include "zelda3/common.h" #include "zelda3/overworld/overworld_entrance.h" #include "zelda3/overworld/overworld_exit.h" #include "zelda3/overworld/overworld_item.h" #include "zelda3/overworld/overworld_map.h" #include "zelda3/overworld/overworld_version_helper.h" namespace yaze::zelda3 { absl::Status Overworld::Load(Rom* rom) { gfx::ScopedTimer timer("Overworld::Load"); if (rom->size() == 0) { return absl::InvalidArgumentError("ROM file not loaded"); } rom_ = rom; // Phase 1: Tile Assembly (can be parallelized) { gfx::ScopedTimer assembly_timer("AssembleTiles"); RETURN_IF_ERROR(AssembleMap32Tiles()); RETURN_IF_ERROR(AssembleMap16Tiles()); } // Phase 2: Map Decompression (major bottleneck - now parallelized) { gfx::ScopedTimer decompression_timer("DecompressAllMapTiles"); RETURN_IF_ERROR(DecompressAllMapTilesParallel()); } // Phase 3: Map Object Creation (fast) { gfx::ScopedTimer map_creation_timer("CreateOverworldMapObjects"); for (int map_index = 0; map_index < kNumOverworldMaps; ++map_index) overworld_maps_.emplace_back(map_index, rom_); // Populate map_parent_ array with parent information from each map for (int map_index = 0; map_index < kNumOverworldMaps; ++map_index) { map_parent_[map_index] = overworld_maps_[map_index].parent(); } } // Phase 4: Map Configuration uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied]; if (asm_version >= 3) { AssignMapSizes(overworld_maps_); } else { FetchLargeMaps(); } // Phase 5: Data Loading (with individual timing) { gfx::ScopedTimer data_loading_timer("LoadOverworldData"); { gfx::ScopedTimer tile_types_timer("LoadTileTypes"); LoadTileTypes(); } { gfx::ScopedTimer entrances_timer("LoadEntrances"); ASSIGN_OR_RETURN(all_entrances_, LoadEntrances(rom_)); } { gfx::ScopedTimer holes_timer("LoadHoles"); ASSIGN_OR_RETURN(all_holes_, LoadHoles(rom_)); } { gfx::ScopedTimer exits_timer("LoadExits"); ASSIGN_OR_RETURN(all_exits_, LoadExits(rom_)); } { gfx::ScopedTimer items_timer("LoadItems"); ASSIGN_OR_RETURN(all_items_, LoadItems(rom_, overworld_maps_)); } { gfx::ScopedTimer overworld_maps_timer("LoadOverworldMaps"); RETURN_IF_ERROR(LoadOverworldMaps()); } { gfx::ScopedTimer sprites_timer("LoadSprites"); RETURN_IF_ERROR(LoadSprites()); } } is_loaded_ = true; return absl::OkStatus(); } void Overworld::FetchLargeMaps() { for (int i = 128; i < 145; i++) { overworld_maps_[i].SetAsSmallMap(0); } overworld_maps_[129].SetAsLargeMap(129, 0); overworld_maps_[130].SetAsLargeMap(129, 1); overworld_maps_[137].SetAsLargeMap(129, 2); overworld_maps_[138].SetAsLargeMap(129, 3); overworld_maps_[136].SetAsSmallMap(); std::array map_checked; std::ranges::fill(map_checked, false); int xx = 0; int yy = 0; while (true) { if (int i = xx + (yy * 8); map_checked[i] == false) { if (overworld_maps_[i].is_large_map()) { map_checked[i] = true; overworld_maps_[i].SetAsLargeMap(i, 0); overworld_maps_[i + 64].SetAsLargeMap(i + 64, 0); map_checked[i + 1] = true; overworld_maps_[i + 1].SetAsLargeMap(i, 1); overworld_maps_[i + 65].SetAsLargeMap(i + 64, 1); map_checked[i + 8] = true; overworld_maps_[i + 8].SetAsLargeMap(i, 2); overworld_maps_[i + 72].SetAsLargeMap(i + 64, 2); map_checked[i + 9] = true; overworld_maps_[i + 9].SetAsLargeMap(i, 3); overworld_maps_[i + 73].SetAsLargeMap(i + 64, 3); xx++; } else { overworld_maps_[i].SetAsSmallMap(); overworld_maps_[i + 64].SetAsSmallMap(); map_checked[i] = true; } } xx++; if (xx >= 8) { xx = 0; yy += 1; if (yy >= 8) { break; } } } } /** * @brief Loads all maps from ROM to see what size they are. * @param maps The maps to update (passed by reference) */ void Overworld::AssignMapSizes(std::vector& maps) { std::vector map_checked(kNumOverworldMaps, false); int xx = 0; int yy = 0; int world = 0; while (true) { int i = world + xx + (yy * 8); if (i >= static_cast(map_checked.size())) { break; } if (!map_checked[i]) { switch (maps[i].area_size()) { case AreaSizeEnum::SmallArea: map_checked[i] = true; maps[i].SetAreaSize(AreaSizeEnum::SmallArea); break; case AreaSizeEnum::LargeArea: map_checked[i] = true; maps[i].SetAsLargeMap(i, 0); if (i + 1 < static_cast(maps.size())) { map_checked[i + 1] = true; maps[i + 1].SetAsLargeMap(i, 1); } if (i + 8 < static_cast(maps.size())) { map_checked[i + 8] = true; maps[i + 8].SetAsLargeMap(i, 2); } if (i + 9 < static_cast(maps.size())) { map_checked[i + 9] = true; maps[i + 9].SetAsLargeMap(i, 3); } xx++; break; case AreaSizeEnum::WideArea: map_checked[i] = true; // CRITICAL FIX: Set parent for wide area maps // Map i is parent (left), map i+1 is child (right) maps[i].SetParent(i); // Parent points to itself maps[i].SetAreaSize(AreaSizeEnum::WideArea); if (i + 1 < static_cast(maps.size())) { map_checked[i + 1] = true; maps[i + 1].SetParent(i); // Child points to parent maps[i + 1].SetAreaSize(AreaSizeEnum::WideArea); } xx++; break; case AreaSizeEnum::TallArea: map_checked[i] = true; // CRITICAL FIX: Set parent for tall area maps // Map i is parent (top), map i+8 is child (bottom) maps[i].SetParent(i); // Parent points to itself maps[i].SetAreaSize(AreaSizeEnum::TallArea); if (i + 8 < static_cast(maps.size())) { map_checked[i + 8] = true; maps[i + 8].SetParent(i); // Child points to parent maps[i + 8].SetAreaSize(AreaSizeEnum::TallArea); } break; } } xx++; if (xx >= 8) { xx = 0; yy += 1; if (yy >= 8) { yy = 0; world += 0x40; } } } } absl::Status Overworld::ConfigureMultiAreaMap(int parent_index, AreaSizeEnum size) { if (parent_index < 0 || parent_index >= kNumOverworldMaps) { return absl::InvalidArgumentError( absl::StrFormat("Invalid parent index: %d", parent_index)); } // Check ROM version uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied]; // Version requirements: // - Vanilla (0xFF): Supports Small and Large only // - v1-v2: Supports Small and Large only // - v3+: Supports all 4 sizes (Small, Large, Wide, Tall) if ((size == AreaSizeEnum::WideArea || size == AreaSizeEnum::TallArea) && (asm_version < 3 || asm_version == 0xFF)) { return absl::FailedPreconditionError( "Wide and Tall areas require ZSCustomOverworld v3+"); } LOG_DEBUG("Overworld", "ConfigureMultiAreaMap: parent=%d, current_size=%d, new_size=%d, " "version=%d", parent_index, static_cast(overworld_maps_[parent_index].area_size()), static_cast(size), asm_version); // CRITICAL: First, get OLD siblings (before changing) so we can reset them std::vector old_siblings; auto old_size = overworld_maps_[parent_index].area_size(); int old_parent = overworld_maps_[parent_index].parent(); switch (old_size) { case AreaSizeEnum::LargeArea: old_siblings = {old_parent, old_parent + 1, old_parent + 8, old_parent + 9}; break; case AreaSizeEnum::WideArea: old_siblings = {old_parent, old_parent + 1}; break; case AreaSizeEnum::TallArea: old_siblings = {old_parent, old_parent + 8}; break; default: old_siblings = {parent_index}; // Was small, just this map break; } // Reset all old siblings to SmallArea first (clean slate) for (int old_sibling : old_siblings) { if (old_sibling >= 0 && old_sibling < kNumOverworldMaps) { overworld_maps_[old_sibling].SetAsSmallMap(old_sibling); } } // Now configure NEW siblings based on requested size std::vector new_siblings; switch (size) { case AreaSizeEnum::SmallArea: // Just configure this single map as small overworld_maps_[parent_index].SetParent(parent_index); overworld_maps_[parent_index].SetAreaSize(AreaSizeEnum::SmallArea); new_siblings = {parent_index}; break; case AreaSizeEnum::LargeArea: new_siblings = {parent_index, parent_index + 1, parent_index + 8, parent_index + 9}; for (size_t i = 0; i < new_siblings.size(); ++i) { int sibling = new_siblings[i]; if (sibling < 0 || sibling >= kNumOverworldMaps) continue; overworld_maps_[sibling].SetAsLargeMap(parent_index, i); } break; case AreaSizeEnum::WideArea: new_siblings = {parent_index, parent_index + 1}; for (int sibling : new_siblings) { if (sibling < 0 || sibling >= kNumOverworldMaps) continue; overworld_maps_[sibling].SetParent(parent_index); overworld_maps_[sibling].SetAreaSize(AreaSizeEnum::WideArea); } break; case AreaSizeEnum::TallArea: new_siblings = {parent_index, parent_index + 8}; for (int sibling : new_siblings) { if (sibling < 0 || sibling >= kNumOverworldMaps) continue; overworld_maps_[sibling].SetParent(parent_index); overworld_maps_[sibling].SetAreaSize(AreaSizeEnum::TallArea); } break; } // Update ROM data for ALL affected siblings (old + new) std::set all_affected; for (int sibling : old_siblings) { all_affected.insert(sibling); } for (int sibling : new_siblings) { all_affected.insert(sibling); } if (asm_version >= 3 && asm_version != 0xFF) { // v3+: Update expanded tables for (int sibling : all_affected) { if (sibling < 0 || sibling >= kNumOverworldMaps) continue; RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentIdExpanded + sibling, overworld_maps_[sibling].parent())); RETURN_IF_ERROR(rom()->WriteByte( kOverworldScreenSize + sibling, static_cast(overworld_maps_[sibling].area_size()))); } } else if (asm_version < 3 && asm_version != 0xFF) { // v1/v2: Update basic parent table for (int sibling : all_affected) { if (sibling < 0 || sibling >= kNumOverworldMaps) continue; RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + sibling, overworld_maps_[sibling].parent())); RETURN_IF_ERROR(rom()->WriteByte( kOverworldScreenSize + (sibling & 0x3F), static_cast(overworld_maps_[sibling].area_size()))); } } else { // Vanilla: Update parent and screen size tables for (int sibling : all_affected) { if (sibling < 0 || sibling >= kNumOverworldMaps) continue; RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + sibling, overworld_maps_[sibling].parent())); RETURN_IF_ERROR(rom()->WriteByte( kOverworldScreenSize + (sibling & 0x3F), (overworld_maps_[sibling].area_size() == AreaSizeEnum::LargeArea) ? 0x00 : 0x01)); } } LOG_DEBUG("Overworld", "Configured %s area: parent=%d, old_siblings=%zu, new_siblings=%zu", (size == AreaSizeEnum::LargeArea) ? "Large" : (size == AreaSizeEnum::WideArea) ? "Wide" : (size == AreaSizeEnum::TallArea) ? "Tall" : "Small", parent_index, old_siblings.size(), new_siblings.size()); return absl::OkStatus(); } absl::StatusOr Overworld::GetTile16ForTile32( int index, int quadrant, int dimension, const uint32_t* map32address) { ASSIGN_OR_RETURN( auto arg1, rom()->ReadByte(map32address[dimension] + quadrant + (index))); ASSIGN_OR_RETURN(auto arg2, rom()->ReadWord(map32address[dimension] + (index) + (quadrant <= 1 ? 4 : 5))); return (uint16_t)(arg1 + (((arg2 >> (quadrant % 2 == 0 ? 4 : 0)) & 0x0F) * 256)); } absl::Status Overworld::AssembleMap32Tiles() { constexpr int kMap32TilesLength = 0x33F0; int num_tile32 = kMap32TilesLength; uint32_t map32address[4] = {rom()->version_constants().kMap32TileTL, rom()->version_constants().kMap32TileTR, rom()->version_constants().kMap32TileBL, rom()->version_constants().kMap32TileBR}; // Check if expanded tile32 data is actually present in ROM // The flag position should contain 0x04 for vanilla, something else for expanded uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied]; uint8_t expanded_flag = rom()->data()[kMap32ExpandedFlagPos]; util::logf("Expanded tile32 flag: %d", expanded_flag); if (expanded_flag != 0x04 || asm_version >= 3) { // ROM has expanded tile32 data - use expanded addresses map32address[0] = rom()->version_constants().kMap32TileTL; map32address[1] = kMap32TileTRExpanded; map32address[2] = kMap32TileBLExpanded; map32address[3] = kMap32TileBRExpanded; num_tile32 = kMap32TileCountExpanded; expanded_tile32_ = true; } // Otherwise use vanilla addresses (already set above) // Loop through each 32x32 pixel tile in the rom for (int i = 0; i < num_tile32; i += 6) { // Loop through each quadrant of the 32x32 pixel tile. for (int k = 0; k < 4; k++) { // Generate the 16-bit tile for the current quadrant of the current // 32x32 pixel tile. ASSIGN_OR_RETURN( uint16_t tl, GetTile16ForTile32(i, k, (int)Dimension::map32TilesTL, map32address)); ASSIGN_OR_RETURN( uint16_t tr, GetTile16ForTile32(i, k, (int)Dimension::map32TilesTR, map32address)); ASSIGN_OR_RETURN( uint16_t bl, GetTile16ForTile32(i, k, (int)Dimension::map32TilesBL, map32address)); ASSIGN_OR_RETURN( uint16_t br, GetTile16ForTile32(i, k, (int)Dimension::map32TilesBR, map32address)); // Add the generated 16-bit tiles to the tiles32 vector. tiles32_unique_.emplace_back(gfx::Tile32(tl, tr, bl, br)); } } map_tiles_.light_world.resize(0x200); map_tiles_.dark_world.resize(0x200); map_tiles_.special_world.resize(0x200); for (int i = 0; i < 0x200; i++) { map_tiles_.light_world[i].resize(0x200); map_tiles_.dark_world[i].resize(0x200); map_tiles_.special_world[i].resize(0x200); } return absl::OkStatus(); } absl::Status Overworld::AssembleMap16Tiles() { int tpos = kMap16Tiles; int num_tile16 = kNumTile16Individual; // Check if expanded tile16 data is actually present in ROM // The flag position should contain 0x0F for vanilla, something else for expanded uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied]; uint8_t expanded_flag = rom()->data()[kMap16ExpandedFlagPos]; util::logf("Expanded tile16 flag: %d", expanded_flag); if (rom()->data()[kMap16ExpandedFlagPos] == 0x0F || asm_version >= 3) { // ROM has expanded tile16 data - use expanded addresses tpos = kMap16TilesExpanded; num_tile16 = NumberOfMap16Ex; expanded_tile16_ = true; } // Otherwise use vanilla addresses (already set above) for (int i = 0; i < num_tile16; i += 1) { ASSIGN_OR_RETURN(auto t0_data, rom()->ReadWord(tpos)); gfx::TileInfo t0 = gfx::GetTilesInfo(t0_data); tpos += 2; ASSIGN_OR_RETURN(auto t1_data, rom()->ReadWord(tpos)); gfx::TileInfo t1 = gfx::GetTilesInfo(t1_data); tpos += 2; ASSIGN_OR_RETURN(auto t2_data, rom()->ReadWord(tpos)); gfx::TileInfo t2 = gfx::GetTilesInfo(t2_data); tpos += 2; ASSIGN_OR_RETURN(auto t3_data, rom()->ReadWord(tpos)); gfx::TileInfo t3 = gfx::GetTilesInfo(t3_data); tpos += 2; tiles16_.emplace_back(t0, t1, t2, t3); } return absl::OkStatus(); } void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos, OverworldBlockset& world) { int position_x1 = (x * 2) + (sx * 32); int position_y1 = (y * 2) + (sy * 32); int position_x2 = (x * 2) + 1 + (sx * 32); int position_y2 = (y * 2) + 1 + (sy * 32); world[position_x1][position_y1] = tiles32_unique_[tpos].tile0_; world[position_x2][position_y1] = tiles32_unique_[tpos].tile1_; world[position_x1][position_y2] = tiles32_unique_[tpos].tile2_; world[position_x2][position_y2] = tiles32_unique_[tpos].tile3_; } void Overworld::OrganizeMapTiles(std::vector& bytes, std::vector& bytes2, int i, int sx, int sy, int& ttpos) { for (int y = 0; y < 16; y++) { for (int x = 0; x < 16; x++) { auto tidD = (uint16_t)((bytes2[ttpos] << 8) + bytes[ttpos]); if (int tpos = tidD; tpos < tiles32_unique_.size()) { if (i < kDarkWorldMapIdStart) { AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.light_world); } else if (i < kSpecialWorldMapIdStart && i >= kDarkWorldMapIdStart) { AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.dark_world); } else { AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.special_world); } } ttpos += 1; } } } absl::Status Overworld::DecompressAllMapTilesParallel() { const auto get_ow_map_gfx_ptr = [this](int index, uint32_t map_ptr) { int p = (rom()->data()[map_ptr + 2 + (3 * index)] << 16) + (rom()->data()[map_ptr + 1 + (3 * index)] << 8) + (rom()->data()[map_ptr + (3 * index)]); return SnesToPc(p); }; constexpr uint32_t kBaseLowest = 0x0FFFFF; constexpr uint32_t kBaseHighest = 0x0F8000; uint32_t lowest = kBaseLowest; uint32_t highest = kBaseHighest; int sx = 0; int sy = 0; int c = 0; for (int i = 0; i < kNumOverworldMaps; i++) { auto p1 = get_ow_map_gfx_ptr( i, rom()->version_constants().kCompressedAllMap32PointersHigh); auto p2 = get_ow_map_gfx_ptr( i, rom()->version_constants().kCompressedAllMap32PointersLow); int ttpos = 0; if (p1 >= highest) highest = p1; if (p2 >= highest) highest = p2; if (p1 <= lowest && p1 > kBaseHighest) lowest = p1; if (p2 <= lowest && p2 > kBaseHighest) lowest = p2; int size1, size2; auto bytes = gfx::HyruleMagicDecompress(rom()->data() + p2, &size1, 1); auto bytes2 = gfx::HyruleMagicDecompress(rom()->data() + p1, &size2, 1); OrganizeMapTiles(bytes, bytes2, i, sx, sy, ttpos); sx++; if (sx >= 8) { sy++; sx = 0; } c++; if (c >= 64) { sx = 0; sy = 0; c = 0; } } return absl::OkStatus(); } absl::Status Overworld::LoadOverworldMaps() { auto size = tiles16_.size(); // Performance optimization: Only build essential maps initially // Essential maps are the first few maps of each world that are commonly accessed constexpr int kEssentialMapsPerWorld = 16; constexpr int kLightWorldEssential = kEssentialMapsPerWorld; constexpr int kDarkWorldEssential = kDarkWorldMapIdStart + kEssentialMapsPerWorld; constexpr int kSpecialWorldEssential = kSpecialWorldMapIdStart + kEssentialMapsPerWorld; util::logf( "Building essential maps only (first %d maps per world) for faster " "loading", kEssentialMapsPerWorld); std::vector> futures; // Build essential maps only for (int i = 0; i < kNumOverworldMaps; ++i) { bool is_essential = false; // Check if this is an essential map if (i < kLightWorldEssential) { is_essential = true; } else if (i >= kDarkWorldMapIdStart && i < kDarkWorldEssential) { is_essential = true; } else if (i >= kSpecialWorldMapIdStart && i < kSpecialWorldEssential) { is_essential = true; } if (is_essential) { int world_type = 0; if (i >= kDarkWorldMapIdStart && i < kSpecialWorldMapIdStart) { world_type = 1; } else if (i >= kSpecialWorldMapIdStart) { world_type = 2; } auto task_function = [this, i, size, world_type]() { return overworld_maps_[i].BuildMap(size, game_state_, world_type, tiles16_, GetMapTiles(world_type)); }; futures.emplace_back(std::async(std::launch::async, task_function)); } else { // Mark non-essential maps as not built yet overworld_maps_[i].SetNotBuilt(); } } // Wait for essential maps to complete for (auto& future : futures) { future.wait(); RETURN_IF_ERROR(future.get()); } util::logf("Essential maps built. Remaining maps will be built on-demand."); return absl::OkStatus(); } absl::Status Overworld::EnsureMapBuilt(int map_index) { if (map_index < 0 || map_index >= kNumOverworldMaps) { return absl::InvalidArgumentError("Invalid map index"); } // Check if map is already built if (overworld_maps_[map_index].is_built()) { return absl::OkStatus(); } // Build the map on-demand auto size = tiles16_.size(); int world_type = 0; if (map_index >= kDarkWorldMapIdStart && map_index < kSpecialWorldMapIdStart) { world_type = 1; } else if (map_index >= kSpecialWorldMapIdStart) { world_type = 2; } return overworld_maps_[map_index].BuildMap(size, game_state_, world_type, tiles16_, GetMapTiles(world_type)); } void Overworld::LoadTileTypes() { for (int i = 0; i < kNumTileTypes; ++i) { all_tiles_types_[i] = rom()->data()[rom()->version_constants().kOverworldTilesType + i]; } } absl::Status Overworld::LoadSprites() { std::vector> futures; // Determine sprite table locations based on actual ASM version in ROM uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; if (asm_version >= 3 && asm_version != 0xFF) { // v3: Use expanded sprite tables futures.emplace_back(std::async(std::launch::async, [this]() { return LoadSpritesFromMap(overworldSpritesBeginingExpanded, 64, 0); })); futures.emplace_back(std::async(std::launch::async, [this]() { return LoadSpritesFromMap(overworldSpritesZeldaExpanded, 144, 1); })); futures.emplace_back(std::async(std::launch::async, [this]() { return LoadSpritesFromMap(overworldSpritesAgahnimExpanded, 144, 2); })); } else { // Vanilla/v2: Use original sprite tables futures.emplace_back(std::async(std::launch::async, [this]() { return LoadSpritesFromMap(kOverworldSpritesBeginning, 64, 0); })); futures.emplace_back(std::async(std::launch::async, [this]() { return LoadSpritesFromMap(kOverworldSpritesZelda, 144, 1); })); futures.emplace_back(std::async(std::launch::async, [this]() { return LoadSpritesFromMap(kOverworldSpritesAgahnim, 144, 2); })); } for (auto& future : futures) { future.wait(); RETURN_IF_ERROR(future.get()); } return absl::OkStatus(); } absl::Status Overworld::LoadSpritesFromMap(int sprites_per_gamestate_ptr, int num_maps_per_gamestate, int game_state) { for (int i = 0; i < num_maps_per_gamestate; i++) { if (map_parent_[i] != i) continue; int current_spr_ptr = sprites_per_gamestate_ptr + (i * 2); ASSIGN_OR_RETURN(auto word_addr, rom()->ReadWord(current_spr_ptr)); int sprite_address = SnesToPc((0x09 << 0x10) | word_addr); while (true) { ASSIGN_OR_RETURN(uint8_t b1, rom()->ReadByte(sprite_address)); ASSIGN_OR_RETURN(uint8_t b2, rom()->ReadByte(sprite_address + 1)); ASSIGN_OR_RETURN(uint8_t b3, rom()->ReadByte(sprite_address + 2)); if (b1 == 0xFF) break; int editor_map_index = i; if (game_state != 0) { if (editor_map_index >= 128) editor_map_index -= 128; else if (editor_map_index >= 64) editor_map_index -= 64; } int mapY = (editor_map_index / 8); int mapX = (editor_map_index % 8); int realX = ((b2 & 0x3F) * 16) + mapX * 512; int realY = ((b1 & 0x3F) * 16) + mapY * 512; all_sprites_[game_state].emplace_back( *overworld_maps_[i].mutable_current_graphics(), (uint8_t)i, b3, (uint8_t)(b2 & 0x3F), (uint8_t)(b1 & 0x3F), realX, realY); all_sprites_[game_state].back().Draw(); sprite_address += 3; } } return absl::OkStatus(); } absl::Status Overworld::Save(Rom* rom) { rom_ = rom; if (expanded_tile16_) { RETURN_IF_ERROR(SaveMap16Expanded()) } else { RETURN_IF_ERROR(SaveMap16Tiles()) } if (expanded_tile32_) { RETURN_IF_ERROR(SaveMap32Expanded()) } else { RETURN_IF_ERROR(SaveMap32Tiles()) } RETURN_IF_ERROR(SaveOverworldMaps()) RETURN_IF_ERROR(SaveEntrances()) RETURN_IF_ERROR(SaveExits()) RETURN_IF_ERROR(SaveItems()) RETURN_IF_ERROR(SaveMapOverlays()) RETURN_IF_ERROR(SaveOverworldTilesType()) RETURN_IF_ERROR(SaveAreaSpecificBGColors()) RETURN_IF_ERROR(SaveMusic()) RETURN_IF_ERROR(SaveAreaSizes()) return absl::OkStatus(); } absl::Status Overworld::SaveOverworldMaps() { util::logf("Saving Overworld Maps"); // Initialize map pointers std::fill(map_pointers1_id.begin(), map_pointers1_id.end(), -1); std::fill(map_pointers2_id.begin(), map_pointers2_id.end(), -1); // Compress and save each map int pos = kOverworldCompressedMapPos; for (int i = 0; i < kNumOverworldMaps; i++) { std::vector single_map_1(512); std::vector single_map_2(512); // Copy tiles32 data to single_map_1 and single_map_2 int npos = 0; for (int y = 0; y < 16; y++) { for (int x = 0; x < 16; x++) { auto packed = tiles32_list_[npos + (i * 256)]; single_map_1[npos] = packed & 0xFF; // Lower 8 bits single_map_2[npos] = (packed >> 8) & 0xFF; // Next 8 bits npos++; } } int size_a, size_b; // Compress single_map_1 and single_map_2 auto a = gfx::HyruleMagicCompress(single_map_1.data(), 256, &size_a, 1); auto b = gfx::HyruleMagicCompress(single_map_2.data(), 256, &size_b, 1); if (a.empty() || b.empty()) { return absl::AbortedError("Error compressing map gfx."); } // Save compressed data and pointers map_data_p1[i] = std::vector(size_a); map_data_p2[i] = std::vector(size_b); if ((pos + size_a) >= 0x5FE70 && (pos + size_a) <= 0x60000) { pos = 0x60000; } if ((pos + size_a) >= 0x6411F && (pos + size_a) <= 0x70000) { util::logf("Pos set to overflow region for map %s at %s", std::to_string(i), util::HexLong(pos)); pos = kOverworldMapDataOverflow; // 0x0F8780; } const auto compare_array = [](const std::vector& array1, const std::vector& array2) -> bool { if (array1.size() != array2.size()) { return false; } for (size_t i = 0; i < array1.size(); i++) { if (array1[i] != array2[i]) { return false; } } return true; }; for (int j = 0; j < i; j++) { if (compare_array(a, map_data_p1[j])) { // Reuse pointer id j for P1 (a) map_pointers1_id[i] = j; } if (compare_array(b, map_data_p2[j])) { map_pointers2_id[i] = j; // Reuse pointer id j for P2 (b) } } if (map_pointers1_id[i] == -1) { // Save compressed data and pointer for map1 std::copy(a.begin(), a.end(), map_data_p1[i].begin()); int snes_pos = PcToSnes(pos); map_pointers1[i] = snes_pos; util::logf("Saving map pointers1 and compressed data for map %s at %s", util::HexByte(i), util::HexLong(snes_pos)); RETURN_IF_ERROR(rom()->WriteLong( rom()->version_constants().kCompressedAllMap32PointersLow + (3 * i), snes_pos)); RETURN_IF_ERROR(rom()->WriteVector(pos, a)); pos += size_a; } else { // Save pointer for map1 int snes_pos = map_pointers1[map_pointers1_id[i]]; util::logf("Saving map pointers1 for map %s at %s", util::HexByte(i), util::HexLong(snes_pos)); RETURN_IF_ERROR(rom()->WriteLong( rom()->version_constants().kCompressedAllMap32PointersLow + (3 * i), snes_pos)); } if ((pos + b.size()) >= 0x5FE70 && (pos + b.size()) <= 0x60000) { pos = 0x60000; } if ((pos + b.size()) >= 0x6411F && (pos + b.size()) <= 0x70000) { util::logf("Pos set to overflow region for map %s at %s", util::HexByte(i), util::HexLong(pos)); pos = kOverworldMapDataOverflow; } if (map_pointers2_id[i] == -1) { // Save compressed data and pointer for map2 std::copy(b.begin(), b.end(), map_data_p2[i].begin()); int snes_pos = PcToSnes(pos); map_pointers2[i] = snes_pos; util::logf("Saving map pointers2 and compressed data for map %s at %s", util::HexByte(i), util::HexLong(snes_pos)); RETURN_IF_ERROR(rom()->WriteLong( rom()->version_constants().kCompressedAllMap32PointersHigh + (3 * i), snes_pos)); RETURN_IF_ERROR(rom()->WriteVector(pos, b)); pos += size_b; } else { // Save pointer for map2 int snes_pos = map_pointers2[map_pointers2_id[i]]; util::logf("Saving map pointers2 for map %s at %s", util::HexByte(i), util::HexLong(snes_pos)); RETURN_IF_ERROR(rom()->WriteLong( rom()->version_constants().kCompressedAllMap32PointersHigh + (3 * i), snes_pos)); } } // Check if too many maps data if (pos > kOverworldCompressedOverflowPos) { util::logf("Too many maps data %s", util::HexLong(pos)); return absl::AbortedError("Too many maps data " + std::to_string(pos)); } RETURN_IF_ERROR(SaveLargeMaps()) return absl::OkStatus(); } absl::Status Overworld::SaveLargeMaps() { util::logf("Saving Large Maps"); // Check if this is a v3+ ROM to use expanded transition system uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; bool use_expanded_transitions = (asm_version >= 3 && asm_version != 0xFF); if (use_expanded_transitions) { // Use new v3+ complex transition system with neighbor awareness return SaveLargeMapsExpanded(); } // Original vanilla/v2 logic preserved std::vector checked_map; for (int i = 0; i < kNumMapsPerWorld; ++i) { int y_pos = i / 8; int x_pos = i % 8; int parent_y_pos = overworld_maps_[i].parent() / 8; int parent_x_pos = overworld_maps_[i].parent() % 8; // Always write the map parent since it should not matter RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + i, overworld_maps_[i].parent())) if (std::find(checked_map.begin(), checked_map.end(), i) != checked_map.end()) { continue; } // If it's large then save parent pos * // 0x200 otherwise pos * 0x200 if (overworld_maps_[i].is_large_map()) { const uint8_t large_map_offsets[] = {0, 1, 8, 9}; for (const auto& offset : large_map_offsets) { // Check 1 RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i + offset, 0x20)); // Check 2 RETURN_IF_ERROR( rom()->WriteByte(kOverworldMapSizeHighByte + i + offset, 0x03)); // Check 3 RETURN_IF_ERROR( rom()->WriteByte(kOverworldScreenSize + i + offset, 0x00)); RETURN_IF_ERROR( rom()->WriteByte(kOverworldScreenSize + i + offset + 64, 0x00)); // Check 4 RETURN_IF_ERROR(rom()->WriteByte( kOverworldScreenSizeForLoading + i + offset, 0x04)); RETURN_IF_ERROR(rom()->WriteByte( kOverworldScreenSizeForLoading + i + offset + kDarkWorldMapIdStart, 0x04)); RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSizeForLoading + i + offset + kSpecialWorldMapIdStart, 0x04)); } // Check 5 and 6 - transition targets RETURN_IF_ERROR( rom()->WriteShort(kTransitionTargetNorth + (i * 2), (uint16_t)((parent_y_pos * 0x200) - 0xE0))); RETURN_IF_ERROR( rom()->WriteShort(kTransitionTargetWest + (i * 2), (uint16_t)((parent_x_pos * 0x200) - 0x100))); RETURN_IF_ERROR( rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 2, (uint16_t)((parent_y_pos * 0x200) - 0xE0))); RETURN_IF_ERROR( rom()->WriteShort(kTransitionTargetWest + (i * 2) + 2, (uint16_t)((parent_x_pos * 0x200) - 0x100))); RETURN_IF_ERROR( rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 16, (uint16_t)((parent_y_pos * 0x200) - 0xE0))); RETURN_IF_ERROR( rom()->WriteShort(kTransitionTargetWest + (i * 2) + 16, (uint16_t)((parent_x_pos * 0x200) - 0x100))); RETURN_IF_ERROR( rom()->WriteShort(kTransitionTargetNorth + (i * 2) + 18, (uint16_t)((parent_y_pos * 0x200) - 0xE0))); RETURN_IF_ERROR( rom()->WriteShort(kTransitionTargetWest + (i * 2) + 18, (uint16_t)((parent_x_pos * 0x200) - 0x100))); // Check 7 and 8 - transition positions RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionX + (i * 2), (parent_x_pos * 0x200))); RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionY + (i * 2), (parent_y_pos * 0x200))); RETURN_IF_ERROR( rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 02, (parent_x_pos * 0x200))); RETURN_IF_ERROR( rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 02, (parent_y_pos * 0x200))); RETURN_IF_ERROR( rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 16, (parent_x_pos * 0x200))); RETURN_IF_ERROR( rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 16, (parent_y_pos * 0x200))); RETURN_IF_ERROR( rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 18, (parent_x_pos * 0x200))); RETURN_IF_ERROR( rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 18, (parent_y_pos * 0x200))); // Check 9 - simple vanilla large area transitions RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 00, 0x0060)); RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 02, 0x0060)); // If parentX == 0 then lower submaps == 0x0060 too if (parent_x_pos == 0) { RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 16, 0x0060)); RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 18, 0x0060)); } else { // Otherwise lower submaps == 0x1060 RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 16, 0x1060)); RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 18, 0x1060)); // If the area to the left is a large map, we don't need to add an // offset to it. otherwise leave it the same. Just to make sure where // don't try to read outside of the array. if ((i - 1) >= 0) { // If the area to the left is a large area. if (overworld_maps_[i - 1].is_large_map()) { // If the area to the left is the bottom right of a large area. if (overworld_maps_[i - 1].large_index() == 1) { RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 16, 0x0060)); } } } } // Always 0x0080 RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 00, 0x0080)); RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 2, 0x0080)); // Lower always 0x1080 RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 16, 0x1080)); RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 18, 0x1080)); // If the area to the right is a large map, we don't need to add an offset // to it. otherwise leave it the same. Just to make sure where don't try // to read outside of the array. if ((i + 2) < 64) { // If the area to the right is a large area. if (overworld_maps_[i + 2].is_large_map()) { // If the area to the right is the top left of a large area. if (overworld_maps_[i + 2].large_index() == 0) { RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen2 + (i * 2) + 18, 0x0080)); } } } // Always 0x1800 RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x1800)); RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 16, 0x1800)); // Right side is always 0x1840 RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 2, 0x1840)); RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 18, 0x1840)); // If the area above is a large map, we don't need to add an offset to it. // otherwise leave it the same. // Just to make sure where don't try to read outside of the array. if (i - 8 >= 0) { // If the area just above us is a large area. if (overworld_maps_[i - 8].is_large_map()) { // If the area just above us is the bottom left of a large area. if (overworld_maps_[i - 8].large_index() == 2) { RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen3 + (i * 2) + 02, 0x1800)); } } } // Always 0x2000 RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 00, 0x2000)); RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 16, 0x2000)); // Right side always 0x2040 RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 2, 0x2040)); RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 18, 0x2040)); // If the area below is a large map, we don't need to add an offset to it. // otherwise leave it the same. // Just to make sure where don't try to read outside of the array. if (i + 16 < 64) { // If the area just below us is a large area. if (overworld_maps_[i + 16].is_large_map()) { // If the area just below us is the top left of a large area. if (overworld_maps_[i + 16].large_index() == 0) { RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen4 + (i * 2) + 18, 0x2000)); } } } checked_map.emplace_back(i); checked_map.emplace_back((i + 1)); checked_map.emplace_back((i + 8)); checked_map.emplace_back((i + 9)); } else { RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i, 0x00)); RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSizeHighByte + i, 0x01)); RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, 0x01)); RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i + 64, 0x01)); RETURN_IF_ERROR( rom()->WriteByte(kOverworldScreenSizeForLoading + i, 0x02)); RETURN_IF_ERROR(rom()->WriteByte( kOverworldScreenSizeForLoading + i + kDarkWorldMapIdStart, 0x02)); RETURN_IF_ERROR(rom()->WriteByte( kOverworldScreenSizeForLoading + i + kSpecialWorldMapIdStart, 0x02)); RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen1 + (i * 2), 0x0060)); // If the area to the left is a large map, we don't need to add an offset // to it. otherwise leave it the same. // Just to make sure where don't try to read outside of the array. if (i - 1 >= 0 && parent_x_pos != 0) { if (overworld_maps_[i - 1].is_large_map()) { if (overworld_maps_[i - 1].large_index() == 3) { RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen1 + (i * 2), 0xF060)); } } } RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen2 + (i * 2), 0x0040)); if (i + 1 < 64 && parent_x_pos != 7) { if (overworld_maps_[i + 1].is_large_map()) { if (overworld_maps_[i + 1].large_index() == 2) { RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen2 + (i * 2), 0xF040)); } } } RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x1800)); // If the area above is a large map, we don't need to add an offset to it. // otherwise leave it the same. // Just to make sure where don't try to read outside of the array. if (i - 8 >= 0) { // If the area just above us is a large area. if (overworld_maps_[i - 8].is_large_map()) { // If we are under the bottom right of the large area. if (overworld_maps_[i - 8].large_index() == 3) { RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen3 + (i * 2), 0x17C0)); } } } RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen4 + (i * 2), 0x1000)); // If the area below is a large map, we don't need to add an offset to it. // otherwise leave it the same. // Just to make sure where don't try to read outside of the array. if (i + 8 < 64) { // If the area just below us is a large area. if (overworld_maps_[i + 8].is_large_map()) { // If we are on top of the top right of the large area. if (overworld_maps_[i + 8].large_index() == 1) { RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen4 + (i * 2), 0x0FC0)); } } } RETURN_IF_ERROR(rom()->WriteShort(kTransitionTargetNorth + (i * 2), (uint16_t)((y_pos * 0x200) - 0xE0))); RETURN_IF_ERROR(rom()->WriteShort(kTransitionTargetWest + (i * 2), (uint16_t)((x_pos * 0x200) - 0x100))); RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionX + (i * 2), (x_pos * 0x200))); RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionY + (i * 2), (y_pos * 0x200))); checked_map.emplace_back(i); } } constexpr int OverworldScreenTileMapChangeMask = 0x1262C; RETURN_IF_ERROR( rom()->WriteShort(OverworldScreenTileMapChangeMask + 0, 0x1F80)); RETURN_IF_ERROR( rom()->WriteShort(OverworldScreenTileMapChangeMask + 2, 0x1F80)); RETURN_IF_ERROR( rom()->WriteShort(OverworldScreenTileMapChangeMask + 4, 0x007F)); RETURN_IF_ERROR( rom()->WriteShort(OverworldScreenTileMapChangeMask + 6, 0x007F)); return absl::OkStatus(); } absl::Status Overworld::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) { // Set basic transition targets RETURN_IF_ERROR( rom()->WriteShort(transition_target_north + (i * 2), (uint16_t)((parent_y_pos * 0x0200) - 0x00E0))); RETURN_IF_ERROR( rom()->WriteShort(transition_target_west + (i * 2), (uint16_t)((parent_x_pos * 0x0200) - 0x0100))); RETURN_IF_ERROR( rom()->WriteShort(transition_pos_x + (i * 2), parent_x_pos * 0x0200)); RETURN_IF_ERROR( rom()->WriteShort(transition_pos_y + (i * 2), parent_y_pos * 0x0200)); // byScreen1 = Transitioning right uint16_t by_screen1_small = 0x0060; // Check west neighbor for transition adjustments if ((i % 0x40) - 1 >= 0) { auto& west_neighbor = overworld_maps_[i - 1]; // Transition from bottom right quadrant of large area to small area if (west_neighbor.area_size() == AreaSizeEnum::LargeArea && west_neighbor.large_index() == 3) { by_screen1_small = 0xF060; } // Transition from bottom quadrant of tall area to small area else if (west_neighbor.area_size() == AreaSizeEnum::TallArea && west_neighbor.large_index() == 2) { by_screen1_small = 0xF060; } } RETURN_IF_ERROR( rom()->WriteShort(screen_change_1 + (i * 2), by_screen1_small)); // byScreen2 = Transitioning left uint16_t by_screen2_small = 0x0040; // Check east neighbor for transition adjustments if ((i % 0x40) + 1 < 0x40 && i + 1 < kNumOverworldMaps) { auto& east_neighbor = overworld_maps_[i + 1]; // Transition from bottom left quadrant of large area to small area if (east_neighbor.area_size() == AreaSizeEnum::LargeArea && east_neighbor.large_index() == 2) { by_screen2_small = 0xF040; } // Transition from bottom quadrant of tall area to small area else if (east_neighbor.area_size() == AreaSizeEnum::TallArea && east_neighbor.large_index() == 2) { by_screen2_small = 0xF040; } } RETURN_IF_ERROR( rom()->WriteShort(screen_change_2 + (i * 2), by_screen2_small)); // byScreen3 = Transitioning down uint16_t by_screen3_small = 0x1800; // Check north neighbor for transition adjustments if ((i % 0x40) - 8 >= 0) { auto& north_neighbor = overworld_maps_[i - 8]; // Transition from bottom right quadrant of large area to small area if (north_neighbor.area_size() == AreaSizeEnum::LargeArea && north_neighbor.large_index() == 3) { by_screen3_small = 0x17C0; } // Transition from right quadrant of wide area to small area else if (north_neighbor.area_size() == AreaSizeEnum::WideArea && north_neighbor.large_index() == 1) { by_screen3_small = 0x17C0; } } RETURN_IF_ERROR( rom()->WriteShort(screen_change_3 + (i * 2), by_screen3_small)); // byScreen4 = Transitioning up uint16_t by_screen4_small = 0x1000; // Check south neighbor for transition adjustments if ((i % 0x40) + 8 < 0x40 && i + 8 < kNumOverworldMaps) { auto& south_neighbor = overworld_maps_[i + 8]; // Transition from top right quadrant of large area to small area if (south_neighbor.area_size() == AreaSizeEnum::LargeArea && south_neighbor.large_index() == 1) { by_screen4_small = 0x0FC0; } // Transition from right quadrant of wide area to small area else if (south_neighbor.area_size() == AreaSizeEnum::WideArea && south_neighbor.large_index() == 1) { by_screen4_small = 0x0FC0; } } RETURN_IF_ERROR( rom()->WriteShort(screen_change_4 + (i * 2), by_screen4_small)); return absl::OkStatus(); } absl::Status Overworld::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) { // Set transition targets for all 4 quadrants const uint16_t offsets[] = {0, 2, 16, 18}; for (auto offset : offsets) { RETURN_IF_ERROR( rom()->WriteShort(transition_target_north + (i * 2) + offset, (uint16_t)((parent_y_pos * 0x0200) - 0x00E0))); RETURN_IF_ERROR( rom()->WriteShort(transition_target_west + (i * 2) + offset, (uint16_t)((parent_x_pos * 0x0200) - 0x0100))); RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset, parent_x_pos * 0x0200)); RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset, parent_y_pos * 0x0200)); } // Complex neighbor-aware transition calculations for large areas // byScreen1 = Transitioning right std::array by_screen1_large = {0x0060, 0x0060, 0x1060, 0x1060}; // Check west neighbor if ((i % 0x40) - 1 >= 0) { auto& west_neighbor = overworld_maps_[i - 1]; if (west_neighbor.area_size() == AreaSizeEnum::LargeArea) { switch (west_neighbor.large_index()) { case 1: // From bottom right to bottom left of large area by_screen1_large[2] = 0x0060; break; case 3: // From bottom right to top left of large area by_screen1_large[0] = 0xF060; break; } } else if (west_neighbor.area_size() == AreaSizeEnum::TallArea) { switch (west_neighbor.large_index()) { case 0: // From bottom of tall to bottom left of large by_screen1_large[2] = 0x0060; break; case 2: // From bottom of tall to top left of large by_screen1_large[0] = 0xF060; break; } } } for (int j = 0; j < 4; j++) { RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j], by_screen1_large[j])); } // byScreen2 = Transitioning left std::array by_screen2_large = {0x0080, 0x0080, 0x1080, 0x1080}; // Check east neighbor if ((i % 0x40) + 2 < 0x40 && i + 2 < kNumOverworldMaps) { auto& east_neighbor = overworld_maps_[i + 2]; if (east_neighbor.area_size() == AreaSizeEnum::LargeArea) { switch (east_neighbor.large_index()) { case 0: // From bottom left to bottom right of large area by_screen2_large[3] = 0x0080; break; case 2: // From bottom left to top right of large area by_screen2_large[1] = 0xF080; break; } } else if (east_neighbor.area_size() == AreaSizeEnum::TallArea) { switch (east_neighbor.large_index()) { case 0: // From bottom of tall to bottom right of large by_screen2_large[3] = 0x0080; break; case 2: // From bottom of tall to top right of large by_screen2_large[1] = 0xF080; break; } } } for (int j = 0; j < 4; j++) { RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j], by_screen2_large[j])); } // byScreen3 = Transitioning down std::array by_screen3_large = {0x1800, 0x1840, 0x1800, 0x1840}; // Check north neighbor if ((i % 0x40) - 8 >= 0) { auto& north_neighbor = overworld_maps_[i - 8]; if (north_neighbor.area_size() == AreaSizeEnum::LargeArea) { switch (north_neighbor.large_index()) { case 2: // From bottom right to top right of large area by_screen3_large[1] = 0x1800; break; case 3: // From bottom right to top left of large area by_screen3_large[0] = 0x17C0; break; } } else if (north_neighbor.area_size() == AreaSizeEnum::WideArea) { switch (north_neighbor.large_index()) { case 0: // From right of wide to top right of large by_screen3_large[1] = 0x1800; break; case 1: // From right of wide to top left of large by_screen3_large[0] = 0x17C0; break; } } } for (int j = 0; j < 4; j++) { RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j], by_screen3_large[j])); } // byScreen4 = Transitioning up std::array by_screen4_large = {0x2000, 0x2040, 0x2000, 0x2040}; // Check south neighbor if ((i % 0x40) + 16 < 0x40 && i + 16 < kNumOverworldMaps) { auto& south_neighbor = overworld_maps_[i + 16]; if (south_neighbor.area_size() == AreaSizeEnum::LargeArea) { switch (south_neighbor.large_index()) { case 0: // From top right to bottom right of large area by_screen4_large[3] = 0x2000; break; case 1: // From top right to bottom left of large area by_screen4_large[2] = 0x1FC0; break; } } else if (south_neighbor.area_size() == AreaSizeEnum::WideArea) { switch (south_neighbor.large_index()) { case 0: // From right of wide to bottom right of large by_screen4_large[3] = 0x2000; break; case 1: // From right of wide to bottom left of large by_screen4_large[2] = 0x1FC0; break; } } } for (int j = 0; j < 4; j++) { RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j], by_screen4_large[j])); } return absl::OkStatus(); } absl::Status Overworld::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) { // Set transition targets for both quadrants const uint16_t offsets[] = {0, 2}; for (auto offset : offsets) { RETURN_IF_ERROR( rom()->WriteShort(transition_target_north + (i * 2) + offset, (uint16_t)((parent_y_pos * 0x0200) - 0x00E0))); RETURN_IF_ERROR( rom()->WriteShort(transition_target_west + (i * 2) + offset, (uint16_t)((parent_x_pos * 0x0200) - 0x0100))); RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset, parent_x_pos * 0x0200)); RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset, parent_y_pos * 0x0200)); } // byScreen1 = Transitioning right std::array by_screen1_wide = {0x0060, 0x0060}; // Check west neighbor if ((i % 0x40) - 1 >= 0) { auto& west_neighbor = overworld_maps_[i - 1]; // From bottom right of large to left of wide if (west_neighbor.area_size() == AreaSizeEnum::LargeArea && west_neighbor.large_index() == 3) { by_screen1_wide[0] = 0xF060; } // From bottom of tall to left of wide else if (west_neighbor.area_size() == AreaSizeEnum::TallArea && west_neighbor.large_index() == 2) { by_screen1_wide[0] = 0xF060; } } for (int j = 0; j < 2; j++) { RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j], by_screen1_wide[j])); } // byScreen2 = Transitioning left std::array by_screen2_wide = {0x0080, 0x0080}; // Check east neighbor if ((i % 0x40) + 2 < 0x40 && i + 2 < kNumOverworldMaps) { auto& east_neighbor = overworld_maps_[i + 2]; // From bottom left of large to right of wide if (east_neighbor.area_size() == AreaSizeEnum::LargeArea && east_neighbor.large_index() == 2) { by_screen2_wide[1] = 0xF080; } // From bottom of tall to right of wide else if (east_neighbor.area_size() == AreaSizeEnum::TallArea && east_neighbor.large_index() == 2) { by_screen2_wide[1] = 0xF080; } } for (int j = 0; j < 2; j++) { RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j], by_screen2_wide[j])); } // byScreen3 = Transitioning down std::array by_screen3_wide = {0x1800, 0x1840}; // Check north neighbor if ((i % 0x40) - 8 >= 0) { auto& north_neighbor = overworld_maps_[i - 8]; if (north_neighbor.area_size() == AreaSizeEnum::LargeArea) { switch (north_neighbor.large_index()) { case 2: // From bottom right of large to right of wide by_screen3_wide[1] = 0x1800; break; case 3: // From bottom right of large to left of wide by_screen3_wide[0] = 0x17C0; break; } } else if (north_neighbor.area_size() == AreaSizeEnum::WideArea) { switch (north_neighbor.large_index()) { case 0: // From right of wide to right of wide by_screen3_wide[1] = 0x1800; break; case 1: // From right of wide to left of wide by_screen3_wide[0] = 0x07C0; break; } } } for (int j = 0; j < 2; j++) { RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j], by_screen3_wide[j])); } // byScreen4 = Transitioning up std::array by_screen4_wide = {0x1000, 0x1040}; // Check south neighbor if ((i % 0x40) + 8 < 0x40 && i + 8 < kNumOverworldMaps) { auto& south_neighbor = overworld_maps_[i + 8]; if (south_neighbor.area_size() == AreaSizeEnum::LargeArea) { switch (south_neighbor.large_index()) { case 0: // From top right of large to right of wide by_screen4_wide[1] = 0x1000; break; case 1: // From top right of large to left of wide by_screen4_wide[0] = 0x0FC0; break; } } else if (south_neighbor.area_size() == AreaSizeEnum::WideArea) { if (south_neighbor.large_index() == 1) { by_screen4_wide[0] = 0x0FC0; } switch (south_neighbor.large_index()) { case 0: // From right of wide to right of wide by_screen4_wide[1] = 0x1000; break; case 1: // From right of wide to left of wide by_screen4_wide[0] = 0x0FC0; break; } } } for (int j = 0; j < 2; j++) { RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j], by_screen4_wide[j])); } return absl::OkStatus(); } absl::Status Overworld::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) { // Set transition targets for both quadrants const uint16_t offsets[] = {0, 16}; for (auto offset : offsets) { RETURN_IF_ERROR( rom()->WriteShort(transition_target_north + (i * 2) + offset, (uint16_t)((parent_y_pos * 0x0200) - 0x00E0))); RETURN_IF_ERROR( rom()->WriteShort(transition_target_west + (i * 2) + offset, (uint16_t)((parent_x_pos * 0x0200) - 0x0100))); RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset, parent_x_pos * 0x0200)); RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset, parent_y_pos * 0x0200)); } // byScreen1 = Transitioning right std::array by_screen1_tall = {0x0060, 0x1060}; // Check west neighbor if ((i % 0x40) - 1 >= 0) { auto& west_neighbor = overworld_maps_[i - 1]; if (west_neighbor.area_size() == AreaSizeEnum::LargeArea) { switch (west_neighbor.large_index()) { case 1: // From bottom right of large to bottom of tall by_screen1_tall[1] = 0x0060; break; case 3: // From bottom right of large to top of tall by_screen1_tall[0] = 0xF060; break; } } else if (west_neighbor.area_size() == AreaSizeEnum::TallArea) { switch (west_neighbor.large_index()) { case 0: // From bottom of tall to bottom of tall by_screen1_tall[1] = 0x0060; break; case 2: // From bottom of tall to top of tall by_screen1_tall[0] = 0xF060; break; } } } for (int j = 0; j < 2; j++) { RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j], by_screen1_tall[j])); } // byScreen2 = Transitioning left std::array by_screen2_tall = {0x0040, 0x1040}; // Check east neighbor if ((i % 0x40) + 1 < 0x40 && i + 1 < kNumOverworldMaps) { auto& east_neighbor = overworld_maps_[i + 1]; if (east_neighbor.area_size() == AreaSizeEnum::LargeArea) { switch (east_neighbor.large_index()) { case 0: // From bottom left of large to bottom of tall by_screen2_tall[1] = 0x0040; break; case 2: // From bottom left of large to top of tall by_screen2_tall[0] = 0xF040; break; } } else if (east_neighbor.area_size() == AreaSizeEnum::TallArea) { switch (east_neighbor.large_index()) { case 0: // From bottom of tall to bottom of tall by_screen2_tall[1] = 0x0040; break; case 2: // From bottom of tall to top of tall by_screen2_tall[0] = 0xF040; break; } } } for (int j = 0; j < 2; j++) { RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j], by_screen2_tall[j])); } // byScreen3 = Transitioning down std::array by_screen3_tall = {0x1800, 0x1800}; // Check north neighbor if ((i % 0x40) - 8 >= 0) { auto& north_neighbor = overworld_maps_[i - 8]; // From bottom right of large to top of tall if (north_neighbor.area_size() == AreaSizeEnum::LargeArea && north_neighbor.large_index() == 3) { by_screen3_tall[0] = 0x17C0; } // From right of wide to top of tall else if (north_neighbor.area_size() == AreaSizeEnum::WideArea && north_neighbor.large_index() == 1) { by_screen3_tall[0] = 0x17C0; } } for (int j = 0; j < 2; j++) { RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j], by_screen3_tall[j])); } // byScreen4 = Transitioning up std::array by_screen4_tall = {0x2000, 0x2000}; // Check south neighbor if ((i % 0x40) + 16 < 0x40 && i + 16 < kNumOverworldMaps) { auto& south_neighbor = overworld_maps_[i + 16]; // From top right of large to bottom of tall if (south_neighbor.area_size() == AreaSizeEnum::LargeArea && south_neighbor.large_index() == 1) { by_screen4_tall[1] = 0x1FC0; } // From right of wide to bottom of tall else if (south_neighbor.area_size() == AreaSizeEnum::WideArea && south_neighbor.large_index() == 1) { by_screen4_tall[1] = 0x1FC0; } } for (int j = 0; j < 2; j++) { RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j], by_screen4_tall[j])); } return absl::OkStatus(); } absl::Status Overworld::SaveLargeMapsExpanded() { util::logf("Saving Large Maps (v3+ Expanded)"); // Use expanded memory locations for v3+ int transition_target_north = zelda3::transition_target_northExpanded; int transition_target_west = zelda3::transition_target_westExpanded; int transition_pos_x = zelda3::kOverworldTransitionPositionXExpanded; int transition_pos_y = zelda3::kOverworldTransitionPositionYExpanded; int screen_change_1 = zelda3::kOverworldScreenTileMapChangeByScreen1Expanded; int screen_change_2 = zelda3::kOverworldScreenTileMapChangeByScreen2Expanded; int screen_change_3 = zelda3::kOverworldScreenTileMapChangeByScreen3Expanded; int screen_change_4 = zelda3::kOverworldScreenTileMapChangeByScreen4Expanded; std::vector checked_map; // Process all overworld maps (0xA0 for v3) for (int i = 0; i < kNumOverworldMaps; ++i) { // Skip if this map was already processed as part of a multi-area structure if (std::find(checked_map.begin(), checked_map.end(), i) != checked_map.end()) { continue; } int parent_y_pos = (overworld_maps_[i].parent() % 0x40) / 8; int parent_x_pos = (overworld_maps_[i].parent() % 0x40) % 8; // Write the map parent ID to expanded parent table RETURN_IF_ERROR(rom()->WriteByte(zelda3::kOverworldMapParentIdExpanded + i, overworld_maps_[i].parent())); // Handle transitions based on area size switch (overworld_maps_[i].area_size()) { case AreaSizeEnum::SmallArea: RETURN_IF_ERROR(SaveSmallAreaTransitions( i, parent_x_pos, parent_y_pos, transition_target_north, transition_target_west, transition_pos_x, transition_pos_y, screen_change_1, screen_change_2, screen_change_3, screen_change_4)); checked_map.emplace_back(i); break; case AreaSizeEnum::LargeArea: RETURN_IF_ERROR(SaveLargeAreaTransitions( i, parent_x_pos, parent_y_pos, transition_target_north, transition_target_west, transition_pos_x, transition_pos_y, screen_change_1, screen_change_2, screen_change_3, screen_change_4)); // Mark all 4 quadrants as processed checked_map.emplace_back(i); checked_map.emplace_back(i + 1); checked_map.emplace_back(i + 8); checked_map.emplace_back(i + 9); break; case AreaSizeEnum::WideArea: RETURN_IF_ERROR(SaveWideAreaTransitions( i, parent_x_pos, parent_y_pos, transition_target_north, transition_target_west, transition_pos_x, transition_pos_y, screen_change_1, screen_change_2, screen_change_3, screen_change_4)); // Mark both horizontal quadrants as processed checked_map.emplace_back(i); checked_map.emplace_back(i + 1); break; case AreaSizeEnum::TallArea: RETURN_IF_ERROR(SaveTallAreaTransitions( i, parent_x_pos, parent_y_pos, transition_target_north, transition_target_west, transition_pos_x, transition_pos_y, screen_change_1, screen_change_2, screen_change_3, screen_change_4)); // Mark both vertical quadrants as processed checked_map.emplace_back(i); checked_map.emplace_back(i + 8); break; } } return absl::OkStatus(); } namespace { std::vector GetAllTile16(OverworldMapTiles& map_tiles_) { std::vector all_tile_16; // Ensure it's 64 bits int sx = 0; int sy = 0; int c = 0; OverworldBlockset tiles_used; for (int i = 0; i < kNumOverworldMaps; i++) { if (i < kDarkWorldMapIdStart) { tiles_used = map_tiles_.light_world; } else if (i < kSpecialWorldMapIdStart && i >= kDarkWorldMapIdStart) { tiles_used = map_tiles_.dark_world; } else { tiles_used = map_tiles_.special_world; } for (int y = 0; y < 32; y += 2) { for (int x = 0; x < 32; x += 2) { gfx::Tile32 current_tile( tiles_used[x + (sx * 32)][y + (sy * 32)], tiles_used[x + 1 + (sx * 32)][y + (sy * 32)], tiles_used[x + (sx * 32)][y + 1 + (sy * 32)], tiles_used[x + 1 + (sx * 32)][y + 1 + (sy * 32)]); all_tile_16.emplace_back(current_tile.GetPackedValue()); } } sx++; if (sx >= 8) { sy++; sx = 0; } c++; if (c >= 64) { sx = 0; sy = 0; c = 0; } } return all_tile_16; } } // namespace absl::Status Overworld::CreateTile32Tilemap() { tiles32_unique_.clear(); tiles32_list_.clear(); // Get all tiles16 and packs them into tiles32 std::vector all_tile_16 = GetAllTile16(map_tiles_); // Convert to set then back to vector std::set unique_tiles_set(all_tile_16.begin(), all_tile_16.end()); std::vector unique_tiles(all_tile_16); unique_tiles.assign(unique_tiles_set.begin(), unique_tiles_set.end()); // Create the indexed tiles list std::unordered_map all_tiles_indexed; for (size_t tile32_id = 0; tile32_id < unique_tiles.size(); tile32_id++) { all_tiles_indexed.insert( {unique_tiles[tile32_id], static_cast(tile32_id)}); } // Add all tiles32 from all maps. // Convert all tiles32 non-unique IDs into unique array of IDs. for (int j = 0; j < NumberOfMap32; j++) { tiles32_list_.emplace_back(all_tiles_indexed[all_tile_16[j]]); } // Create the unique tiles list for (size_t i = 0; i < unique_tiles.size(); ++i) { tiles32_unique_.emplace_back(gfx::Tile32(unique_tiles[i])); } while (tiles32_unique_.size() % 4 != 0) { gfx::Tile32 padding_tile(0, 0, 0, 0); tiles32_unique_.emplace_back(padding_tile.GetPackedValue()); } if (tiles32_unique_.size() > LimitOfMap32) { return absl::InternalError(absl::StrFormat( "Number of unique Tiles32: %d Out of: %d\nUnique Tile32 count exceed " "the limit\nThe ROM Has not been saved\nYou can fill maps with grass " "tiles to free some space\nOr use the option Clear DW Tiles in the " "Overworld Menu", unique_tiles.size(), LimitOfMap32)); } if (core::FeatureFlags::get().kLogToConsole) { std::cout << "Number of unique Tiles32: " << tiles32_unique_.size() << " Saved:" << tiles32_unique_.size() << " Out of: " << LimitOfMap32 << std::endl; } int v = tiles32_unique_.size(); for (int i = v; i < LimitOfMap32; i++) { gfx::Tile32 padding_tile(420, 420, 420, 420); tiles32_unique_.emplace_back(padding_tile.GetPackedValue()); } return absl::OkStatus(); } absl::Status Overworld::SaveMap32Expanded() { int bottomLeft = kMap32TileBLExpanded; int bottomRight = kMap32TileBRExpanded; int topRight = kMap32TileTRExpanded; int limit = 0x8A80; // Updates the pointers too for the tile32 // Top Right RETURN_IF_ERROR(rom()->WriteLong(0x0176EC, PcToSnes(kMap32TileTRExpanded))); RETURN_IF_ERROR( rom()->WriteLong(0x0176F3, PcToSnes(kMap32TileTRExpanded + 1))); RETURN_IF_ERROR( rom()->WriteLong(0x0176FA, PcToSnes(kMap32TileTRExpanded + 2))); RETURN_IF_ERROR( rom()->WriteLong(0x017701, PcToSnes(kMap32TileTRExpanded + 3))); RETURN_IF_ERROR( rom()->WriteLong(0x017708, PcToSnes(kMap32TileTRExpanded + 4))); RETURN_IF_ERROR( rom()->WriteLong(0x01771A, PcToSnes(kMap32TileTRExpanded + 5))); // BottomLeft RETURN_IF_ERROR(rom()->WriteLong(0x01772C, PcToSnes(kMap32TileBLExpanded))); RETURN_IF_ERROR( rom()->WriteLong(0x017733, PcToSnes(kMap32TileBLExpanded + 1))); RETURN_IF_ERROR( rom()->WriteLong(0x01773A, PcToSnes(kMap32TileBLExpanded + 2))); RETURN_IF_ERROR( rom()->WriteLong(0x017741, PcToSnes(kMap32TileBLExpanded + 3))); RETURN_IF_ERROR( rom()->WriteLong(0x017748, PcToSnes(kMap32TileBLExpanded + 4))); RETURN_IF_ERROR( rom()->WriteLong(0x01775A, PcToSnes(kMap32TileBLExpanded + 5))); // BottomRight RETURN_IF_ERROR(rom()->WriteLong(0x01776C, PcToSnes(kMap32TileBRExpanded))); RETURN_IF_ERROR( rom()->WriteLong(0x017773, PcToSnes(kMap32TileBRExpanded + 1))); RETURN_IF_ERROR( rom()->WriteLong(0x01777A, PcToSnes(kMap32TileBRExpanded + 2))); RETURN_IF_ERROR( rom()->WriteLong(0x017781, PcToSnes(kMap32TileBRExpanded + 3))); RETURN_IF_ERROR( rom()->WriteLong(0x017788, PcToSnes(kMap32TileBRExpanded + 4))); RETURN_IF_ERROR( rom()->WriteLong(0x01779A, PcToSnes(kMap32TileBRExpanded + 5))); constexpr int kTilesPer32x32Tile = 6; int unique_tile_index = 0; int num_unique_tiles = tiles32_unique_.size(); for (int i = 0; i < num_unique_tiles; i += kTilesPer32x32Tile) { if (unique_tile_index >= limit) { return absl::AbortedError("Too many unique tile32 definitions."); } // Top Left. auto top_left = rom()->version_constants().kMap32TileTL; RETURN_IF_ERROR(rom()->WriteByte( top_left + i, (uint8_t)(tiles32_unique_[unique_tile_index].tile0_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_left + (i + 1), (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile0_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_left + (i + 2), (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile0_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_left + (i + 3), (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile0_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_left + (i + 4), (uint8_t)(((tiles32_unique_[unique_tile_index].tile0_ >> 4) & 0xF0) + ((tiles32_unique_[unique_tile_index + 1].tile0_ >> 8) & 0x0F)))); RETURN_IF_ERROR(rom()->WriteByte( top_left + (i + 5), (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile0_ >> 4) & 0xF0) + ((tiles32_unique_[unique_tile_index + 3].tile0_ >> 8) & 0x0F)))); // Top Right. auto top_right = topRight; RETURN_IF_ERROR(rom()->WriteByte( top_right + i, (uint8_t)(tiles32_unique_[unique_tile_index].tile1_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_right + (i + 1), (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile1_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_right + (i + 2), (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile1_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_right + (i + 3), (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile1_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_right + (i + 4), (uint8_t)(((tiles32_unique_[unique_tile_index].tile1_ >> 4) & 0xF0) | ((tiles32_unique_[unique_tile_index + 1].tile1_ >> 8) & 0x0F)))); RETURN_IF_ERROR(rom()->WriteByte( top_right + (i + 5), (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile1_ >> 4) & 0xF0) | ((tiles32_unique_[unique_tile_index + 3].tile1_ >> 8) & 0x0F)))); // Bottom Left. auto bottom_left = bottomLeft; RETURN_IF_ERROR(rom()->WriteByte( bottom_left + i, (uint8_t)(tiles32_unique_[unique_tile_index].tile2_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( bottom_left + (i + 1), (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile2_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( bottom_left + (i + 2), (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile2_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( bottom_left + (i + 3), (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile2_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( bottom_left + (i + 4), (uint8_t)(((tiles32_unique_[unique_tile_index].tile2_ >> 4) & 0xF0) | ((tiles32_unique_[unique_tile_index + 1].tile2_ >> 8) & 0x0F)))); RETURN_IF_ERROR(rom()->WriteByte( bottom_left + (i + 5), (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile2_ >> 4) & 0xF0) | ((tiles32_unique_[unique_tile_index + 3].tile2_ >> 8) & 0x0F)))); // Bottom Right. auto bottom_right = bottomRight; RETURN_IF_ERROR(rom()->WriteByte( bottom_right + i, (uint8_t)(tiles32_unique_[unique_tile_index].tile3_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( bottom_right + (i + 1), (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile3_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( bottom_right + (i + 2), (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile3_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( bottom_right + (i + 3), (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile3_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( bottom_right + (i + 4), (uint8_t)(((tiles32_unique_[unique_tile_index].tile3_ >> 4) & 0xF0) | ((tiles32_unique_[unique_tile_index + 1].tile3_ >> 8) & 0x0F)))); RETURN_IF_ERROR(rom()->WriteByte( bottom_right + (i + 5), (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile3_ >> 4) & 0xF0) | ((tiles32_unique_[unique_tile_index + 3].tile3_ >> 8) & 0x0F)))); unique_tile_index += 4; } return absl::OkStatus(); } absl::Status Overworld::SaveMap32Tiles() { util::logf("Saving Map32 Tiles"); constexpr int kMaxUniqueTiles = 0x4540; constexpr int kTilesPer32x32Tile = 6; int unique_tile_index = 0; int num_unique_tiles = tiles32_unique_.size(); for (int i = 0; i < num_unique_tiles; i += kTilesPer32x32Tile) { if (unique_tile_index >= kMaxUniqueTiles) { return absl::AbortedError("Too many unique tile32 definitions."); } // Top Left. auto top_left = rom()->version_constants().kMap32TileTL; RETURN_IF_ERROR(rom()->WriteByte( top_left + i, (uint8_t)(tiles32_unique_[unique_tile_index].tile0_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_left + (i + 1), (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile0_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_left + (i + 2), (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile0_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_left + (i + 3), (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile0_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_left + (i + 4), (uint8_t)(((tiles32_unique_[unique_tile_index].tile0_ >> 4) & 0xF0) + ((tiles32_unique_[unique_tile_index + 1].tile0_ >> 8) & 0x0F)))); RETURN_IF_ERROR(rom()->WriteByte( top_left + (i + 5), (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile0_ >> 4) & 0xF0) + ((tiles32_unique_[unique_tile_index + 3].tile0_ >> 8) & 0x0F)))); // Top Right. auto top_right = rom()->version_constants().kMap32TileTR; RETURN_IF_ERROR(rom()->WriteByte( top_right + i, (uint8_t)(tiles32_unique_[unique_tile_index].tile1_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_right + (i + 1), (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile1_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_right + (i + 2), (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile1_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_right + (i + 3), (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile1_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( top_right + (i + 4), (uint8_t)(((tiles32_unique_[unique_tile_index].tile1_ >> 4) & 0xF0) | ((tiles32_unique_[unique_tile_index + 1].tile1_ >> 8) & 0x0F)))); RETURN_IF_ERROR(rom()->WriteByte( top_right + (i + 5), (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile1_ >> 4) & 0xF0) | ((tiles32_unique_[unique_tile_index + 3].tile1_ >> 8) & 0x0F)))); // Bottom Left. const auto map32TilesBL = rom()->version_constants().kMap32TileBL; RETURN_IF_ERROR(rom()->WriteByte( map32TilesBL + i, (uint8_t)(tiles32_unique_[unique_tile_index].tile2_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( map32TilesBL + (i + 1), (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile2_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( map32TilesBL + (i + 2), (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile2_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( map32TilesBL + (i + 3), (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile2_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( map32TilesBL + (i + 4), (uint8_t)(((tiles32_unique_[unique_tile_index].tile2_ >> 4) & 0xF0) | ((tiles32_unique_[unique_tile_index + 1].tile2_ >> 8) & 0x0F)))); RETURN_IF_ERROR(rom()->WriteByte( map32TilesBL + (i + 5), (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile2_ >> 4) & 0xF0) | ((tiles32_unique_[unique_tile_index + 3].tile2_ >> 8) & 0x0F)))); // Bottom Right. const auto map32TilesBR = rom()->version_constants().kMap32TileBR; RETURN_IF_ERROR(rom()->WriteByte( map32TilesBR + i, (uint8_t)(tiles32_unique_[unique_tile_index].tile3_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( map32TilesBR + (i + 1), (uint8_t)(tiles32_unique_[unique_tile_index + 1].tile3_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( map32TilesBR + (i + 2), (uint8_t)(tiles32_unique_[unique_tile_index + 2].tile3_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( map32TilesBR + (i + 3), (uint8_t)(tiles32_unique_[unique_tile_index + 3].tile3_ & 0xFF))); RETURN_IF_ERROR(rom()->WriteByte( map32TilesBR + (i + 4), (uint8_t)(((tiles32_unique_[unique_tile_index].tile3_ >> 4) & 0xF0) | ((tiles32_unique_[unique_tile_index + 1].tile3_ >> 8) & 0x0F)))); RETURN_IF_ERROR(rom()->WriteByte( map32TilesBR + (i + 5), (uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile3_ >> 4) & 0xF0) | ((tiles32_unique_[unique_tile_index + 3].tile3_ >> 8) & 0x0F)))); unique_tile_index += 4; num_unique_tiles += 2; } return absl::OkStatus(); } absl::Status Overworld::SaveMap16Expanded() { RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x008865), PcToSnes(kMap16TilesExpanded))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x0EDE4F), PcToSnes(kMap16TilesExpanded))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x0EDEE9), PcToSnes(kMap16TilesExpanded))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BBC2D), PcToSnes(kMap16TilesExpanded + 2))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BBC4C), PcToSnes(kMap16TilesExpanded))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BBCC2), PcToSnes(kMap16TilesExpanded + 4))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BBCCB), PcToSnes(kMap16TilesExpanded + 6))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BBEF6), PcToSnes(kMap16TilesExpanded))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BBF23), PcToSnes(kMap16TilesExpanded))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BC041), PcToSnes(kMap16TilesExpanded))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BC9B3), PcToSnes(kMap16TilesExpanded))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BC9BA), PcToSnes(kMap16TilesExpanded + 2))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BC9C1), PcToSnes(kMap16TilesExpanded + 4))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BC9C8), PcToSnes(kMap16TilesExpanded + 6))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BCA40), PcToSnes(kMap16TilesExpanded))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BCA47), PcToSnes(kMap16TilesExpanded + 2))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BCA4E), PcToSnes(kMap16TilesExpanded + 4))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x1BCA55), PcToSnes(kMap16TilesExpanded + 6))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x02F457), PcToSnes(kMap16TilesExpanded))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x02F45E), PcToSnes(kMap16TilesExpanded + 2))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x02F467), PcToSnes(kMap16TilesExpanded + 4))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x02F46E), PcToSnes(kMap16TilesExpanded + 6))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x02F51F), PcToSnes(kMap16TilesExpanded))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x02F526), PcToSnes(kMap16TilesExpanded + 4))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x02F52F), PcToSnes(kMap16TilesExpanded + 2))); RETURN_IF_ERROR( rom()->WriteLong(SnesToPc(0x02F536), PcToSnes(kMap16TilesExpanded + 6))); RETURN_IF_ERROR( rom()->WriteShort(SnesToPc(0x02FE1C), PcToSnes(kMap16TilesExpanded))); RETURN_IF_ERROR( rom()->WriteShort(SnesToPc(0x02FE23), PcToSnes(kMap16TilesExpanded + 4))); RETURN_IF_ERROR( rom()->WriteShort(SnesToPc(0x02FE2C), PcToSnes(kMap16TilesExpanded + 2))); RETURN_IF_ERROR( rom()->WriteShort(SnesToPc(0x02FE33), PcToSnes(kMap16TilesExpanded + 6))); RETURN_IF_ERROR(rom()->WriteByte( SnesToPc(0x02FD28), static_cast(PcToSnes(kMap16TilesExpanded) >> 16))); RETURN_IF_ERROR(rom()->WriteByte( SnesToPc(0x02FD39), static_cast(PcToSnes(kMap16TilesExpanded) >> 16))); int tpos = kMap16TilesExpanded; for (int i = 0; i < NumberOfMap16Ex; i += 1) // 4096 { RETURN_IF_ERROR( rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_))); tpos += 2; RETURN_IF_ERROR( rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_))); tpos += 2; RETURN_IF_ERROR( rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_))); tpos += 2; RETURN_IF_ERROR( rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_))); tpos += 2; } return absl::OkStatus(); } absl::Status Overworld::SaveMap16Tiles() { util::logf("Saving Map16 Tiles"); int tpos = kMap16Tiles; // 3760 for (int i = 0; i < NumberOfMap16; i += 1) { RETURN_IF_ERROR( rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_))) tpos += 2; RETURN_IF_ERROR( rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_))) tpos += 2; RETURN_IF_ERROR( rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_))) tpos += 2; RETURN_IF_ERROR( rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_))) tpos += 2; } return absl::OkStatus(); } absl::Status Overworld::SaveEntrances() { RETURN_IF_ERROR( ::yaze::zelda3::SaveEntrances(rom_, all_entrances_, expanded_entrances_)); RETURN_IF_ERROR(SaveHoles(rom_, all_holes_)); return absl::OkStatus(); } absl::Status Overworld::SaveExits() { RETURN_IF_ERROR(::yaze::zelda3::SaveExits(rom_, all_exits_)); return absl::OkStatus(); } absl::Status Overworld::SaveItems() { RETURN_IF_ERROR(::yaze::zelda3::SaveItems(rom_, all_items_)); return absl::OkStatus(); } absl::Status Overworld::SaveMapOverlays() { util::logf("Saving Map Overlays"); // Generate the new overlay code that handles interactive overlays std::vector new_overlay_code = { 0xC2, 0x30, // REP #$30 0xA5, 0x8A, // LDA $8A 0x0A, 0x18, // ASL : CLC 0x65, 0x8A, // ADC $8A 0xAA, // TAX 0xBF, 0x00, 0x00, 0x00, // LDA, X 0x85, 0x00, // STA $00 0xBF, 0x00, 0x00, 0x00, // LDA, X +2 0x85, 0x02, // STA $02 0x4B, // PHK 0xF4, 0x00, 0x00, // This position +3 ? 0xDC, 0x00, 0x00, // JML [$00 00] 0xE2, 0x30, // SEP #$30 0xAB, // PLB 0x6B, // RTL }; // Write overlay code to ROM constexpr int kOverlayCodeStart = 0x077657; RETURN_IF_ERROR(rom()->WriteVector(kOverlayCodeStart, new_overlay_code)); // Set up overlay pointers int ptr_start = kOverlayCodeStart + 0x20; int snes_ptr_start = PcToSnes(ptr_start); // Write overlay pointer addresses in the code RETURN_IF_ERROR(rom()->WriteLong(kOverlayCodeStart + 10, snes_ptr_start)); RETURN_IF_ERROR(rom()->WriteLong(kOverlayCodeStart + 16, snes_ptr_start + 2)); int pea_addr = PcToSnes(kOverlayCodeStart + 27); RETURN_IF_ERROR(rom()->WriteShort(kOverlayCodeStart + 23, pea_addr)); // Write overlay data to expanded space constexpr int kExpandedOverlaySpace = 0x120000; int pos = kExpandedOverlaySpace; int ptr_pos = kOverlayCodeStart + 32; for (int i = 0; i < kNumOverworldMaps; i++) { int snes_addr = PcToSnes(pos); RETURN_IF_ERROR(rom()->WriteLong(ptr_pos, snes_addr & 0xFFFFFF)); ptr_pos += 3; // Write overlay data for each map that has overlays if (overworld_maps_[i].has_overlay()) { const auto& overlay_data = overworld_maps_[i].overlay_data(); for (size_t t = 0; t < overlay_data.size(); t += 3) { if (t + 2 < overlay_data.size()) { // Generate LDA/STA sequence for each overlay tile RETURN_IF_ERROR(rom()->WriteByte(pos, 0xA9)); // LDA #$ RETURN_IF_ERROR(rom()->WriteShort( pos + 1, overlay_data[t] | (overlay_data[t + 1] << 8))); pos += 3; RETURN_IF_ERROR(rom()->WriteByte(pos, 0x8D)); // STA $xxxx RETURN_IF_ERROR(rom()->WriteShort(pos + 1, overlay_data[t + 2])); pos += 3; } } } RETURN_IF_ERROR(rom()->WriteByte(pos, 0x6B)); // RTL pos++; } return absl::OkStatus(); } absl::Status Overworld::SaveOverworldTilesType() { util::logf("Saving Overworld Tiles Types"); for (int i = 0; i < kNumTileTypes; i++) { RETURN_IF_ERROR( rom()->WriteByte(overworldTilesType + i, all_tiles_types_[i])); } return absl::OkStatus(); } absl::Status Overworld::SaveCustomOverworldASM(bool enable_bg_color, bool enable_main_palette, bool enable_mosaic, bool enable_gfx_groups, bool enable_subscreen_overlay, bool enable_animated) { util::logf("Applying Custom Overworld ASM"); // Set the enable/disable settings uint8_t enable_value = enable_bg_color ? 0xFF : 0x00; RETURN_IF_ERROR( rom()->WriteByte(OverworldCustomAreaSpecificBGEnabled, enable_value)); enable_value = enable_main_palette ? 0xFF : 0x00; RETURN_IF_ERROR( rom()->WriteByte(OverworldCustomMainPaletteEnabled, enable_value)); enable_value = enable_mosaic ? 0xFF : 0x00; RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomMosaicEnabled, enable_value)); enable_value = enable_gfx_groups ? 0xFF : 0x00; RETURN_IF_ERROR( rom()->WriteByte(OverworldCustomTileGFXGroupEnabled, enable_value)); enable_value = enable_animated ? 0xFF : 0x00; RETURN_IF_ERROR( rom()->WriteByte(OverworldCustomAnimatedGFXEnabled, enable_value)); enable_value = enable_subscreen_overlay ? 0xFF : 0x00; RETURN_IF_ERROR( rom()->WriteByte(OverworldCustomSubscreenOverlayEnabled, enable_value)); // Write the main palette table for (int i = 0; i < kNumOverworldMaps; i++) { RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomMainPaletteArray + i, overworld_maps_[i].main_palette())); } // Write the mosaic table for (int i = 0; i < kNumOverworldMaps; i++) { const auto& mosaic = overworld_maps_[i].mosaic_expanded(); // .... udlr bit format uint8_t mosaic_byte = (mosaic[0] ? 0x08 : 0x00) | // up (mosaic[1] ? 0x04 : 0x00) | // down (mosaic[2] ? 0x02 : 0x00) | // left (mosaic[3] ? 0x01 : 0x00); // right RETURN_IF_ERROR( rom()->WriteByte(OverworldCustomMosaicArray + i, mosaic_byte)); } // Write the main and animated gfx tiles table for (int i = 0; i < kNumOverworldMaps; i++) { for (int j = 0; j < 8; j++) { RETURN_IF_ERROR( rom()->WriteByte(OverworldCustomTileGFXGroupArray + (i * 8) + j, overworld_maps_[i].custom_tileset(j))); } RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomAnimatedGFXArray + i, overworld_maps_[i].animated_gfx())); } // Write the subscreen overlay table for (int i = 0; i < kNumOverworldMaps; i++) { RETURN_IF_ERROR( rom()->WriteShort(OverworldCustomSubscreenOverlayArray + (i * 2), overworld_maps_[i].subscreen_overlay())); } return absl::OkStatus(); } absl::Status Overworld::SaveAreaSpecificBGColors() { util::logf("Saving Area Specific Background Colors"); // Write area-specific background colors if enabled for (int i = 0; i < kNumOverworldMaps; i++) { uint16_t bg_color = overworld_maps_[i].area_specific_bg_color(); RETURN_IF_ERROR(rom()->WriteShort( OverworldCustomAreaSpecificBGPalette + (i * 2), bg_color)); } return absl::OkStatus(); } absl::Status Overworld::SaveMapProperties() { util::logf("Saving Map Properties"); for (int i = 0; i < kDarkWorldMapIdStart; i++) { RETURN_IF_ERROR(rom()->WriteByte(kAreaGfxIdPtr + i, overworld_maps_[i].area_graphics())); RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapPaletteIds + i, overworld_maps_[i].area_palette())); RETURN_IF_ERROR(rom()->WriteByte(kOverworldSpriteset + i, overworld_maps_[i].sprite_graphics(0))); RETURN_IF_ERROR( rom()->WriteByte(kOverworldSpriteset + kDarkWorldMapIdStart + i, overworld_maps_[i].sprite_graphics(1))); RETURN_IF_ERROR( rom()->WriteByte(kOverworldSpriteset + kSpecialWorldMapIdStart + i, overworld_maps_[i].sprite_graphics(2))); RETURN_IF_ERROR(rom()->WriteByte(kOverworldSpritePaletteIds + i, overworld_maps_[i].sprite_palette(0))); RETURN_IF_ERROR( rom()->WriteByte(kOverworldSpritePaletteIds + kDarkWorldMapIdStart + i, overworld_maps_[i].sprite_palette(1))); RETURN_IF_ERROR(rom()->WriteByte( kOverworldSpritePaletteIds + kSpecialWorldMapIdStart + i, overworld_maps_[i].sprite_palette(2))); } for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) { RETURN_IF_ERROR(rom()->WriteByte(kAreaGfxIdPtr + i, overworld_maps_[i].area_graphics())); RETURN_IF_ERROR(rom()->WriteByte(kOverworldSpriteset + i, overworld_maps_[i].sprite_graphics(0))); RETURN_IF_ERROR( rom()->WriteByte(kOverworldSpriteset + kDarkWorldMapIdStart + i, overworld_maps_[i].sprite_graphics(1))); RETURN_IF_ERROR( rom()->WriteByte(kOverworldSpriteset + kSpecialWorldMapIdStart + i, overworld_maps_[i].sprite_graphics(2))); RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapPaletteIds + i, overworld_maps_[i].area_palette())); RETURN_IF_ERROR( rom()->WriteByte(kOverworldSpritePaletteIds + kDarkWorldMapIdStart + i, overworld_maps_[i].sprite_palette(0))); RETURN_IF_ERROR(rom()->WriteByte( kOverworldSpritePaletteIds + kSpecialWorldMapIdStart + i, overworld_maps_[i].sprite_palette(1))); RETURN_IF_ERROR(rom()->WriteByte(kOverworldSpritePaletteIds + 192 + i, overworld_maps_[i].sprite_palette(2))); } return absl::OkStatus(); } absl::Status Overworld::SaveMusic() { util::logf("Saving Music Data"); // Save music data for Light World maps for (int i = 0; i < kDarkWorldMapIdStart; i++) { RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicBeginning + i, overworld_maps_[i].area_music(0))); RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicZelda + i, overworld_maps_[i].area_music(1))); RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicMasterSword + i, overworld_maps_[i].area_music(2))); RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicAgahnim + i, overworld_maps_[i].area_music(3))); } // Save music data for Dark World maps for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) { RETURN_IF_ERROR( rom()->WriteByte(kOverworldMusicDarkWorld + (i - kDarkWorldMapIdStart), overworld_maps_[i].area_music(0))); } return absl::OkStatus(); } absl::Status Overworld::SaveAreaSizes() { util::logf("Saving V3 Area Sizes"); // Check if this is a v3 ROM uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; if (asm_version < 3 || asm_version == 0xFF) { return absl::OkStatus(); // Not a v3 ROM, nothing to do } // Save area sizes to the expanded table for (int i = 0; i < kNumOverworldMaps; i++) { uint8_t area_size_byte = static_cast(overworld_maps_[i].area_size()); RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, area_size_byte)); } // Save message IDs to expanded table for (int i = 0; i < kNumOverworldMaps; i++) { uint16_t message_id = overworld_maps_[i].message_id(); RETURN_IF_ERROR( rom()->WriteShort(kOverworldMessagesExpanded + (i * 2), message_id)); } return absl::OkStatus(); } } // namespace yaze::zelda3