#include "zelda3/overworld/overworld_entrance.h" #include #include "absl/status/status.h" #include "absl/status/statusor.h" #include "app/rom.h" #include "util/macro.h" namespace yaze::zelda3 { absl::StatusOr> LoadEntrances(Rom* rom) { std::vector entrances; int ow_entrance_map_ptr = kOverworldEntranceMap; int ow_entrance_pos_ptr = kOverworldEntrancePos; int ow_entrance_id_ptr = kOverworldEntranceEntranceId; int num_entrances = 129; // Check if expanded entrance data is actually present in ROM // The flag position should contain 0xB8 for vanilla, something else for expanded if (rom->data()[kOverworldEntranceExpandedFlagPos] != 0xB8) { // ROM has expanded entrance data - use expanded addresses ow_entrance_map_ptr = kOverworldEntranceMapExpanded; ow_entrance_pos_ptr = kOverworldEntrancePosExpanded; ow_entrance_id_ptr = kOverworldEntranceEntranceIdExpanded; num_entrances = 256; // Expanded entrance count } // Otherwise use vanilla addresses (already set above) for (int i = 0; i < num_entrances; i++) { ASSIGN_OR_RETURN(auto map_id, rom->ReadWord(ow_entrance_map_ptr + (i * 2))); ASSIGN_OR_RETURN(auto map_pos, rom->ReadWord(ow_entrance_pos_ptr + (i * 2))); ASSIGN_OR_RETURN(auto entrance_id, rom->ReadByte(ow_entrance_id_ptr + i)); int p = map_pos >> 1; int x = (p % 64); int y = (p >> 6); bool deleted = false; if (map_pos == 0xFFFF) { deleted = true; } entrances.emplace_back( (x * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512), (y * 16) + (((map_id % 64) / 8) * 512), entrance_id, map_id, map_pos, deleted); } return entrances; } absl::StatusOr> LoadHoles(Rom* rom) { constexpr int kNumHoles = 0x13; std::vector holes; for (int i = 0; i < kNumHoles; i++) { ASSIGN_OR_RETURN(auto map_id, rom->ReadWord(kOverworldHoleArea + (i * 2))); ASSIGN_OR_RETURN(auto map_pos, rom->ReadWord(kOverworldHolePos + (i * 2))); ASSIGN_OR_RETURN(auto entrance_id, rom->ReadByte(kOverworldHoleEntrance + i)); int p = (map_pos + 0x400) >> 1; int x = (p % 64); int y = (p >> 6); holes.emplace_back( (x * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512), (y * 16) + (((map_id % 64) / 8) * 512), entrance_id, map_id, (uint16_t)(map_pos + 0x400), true); } return holes; } absl::Status SaveEntrances(Rom* rom, const std::vector& entrances, bool expanded_entrances) { auto write_entrance = [&](int index, uint32_t map_addr, uint32_t pos_addr, uint32_t id_addr) -> absl::Status { // Mirrors ZeldaFullEditor/Save.cs::SaveOWEntrances (see lines ~1081-1085) // where MapID and MapPos are written as 16-bit words and EntranceID as a byte. RETURN_IF_ERROR(rom->WriteShort(map_addr, entrances[index].map_id_)); RETURN_IF_ERROR(rom->WriteShort(pos_addr, entrances[index].map_pos_)); RETURN_IF_ERROR(rom->WriteByte(id_addr, entrances[index].entrance_id_)); return absl::OkStatus(); }; // Always keep the legacy tables in sync for pure vanilla ROMs so e.g. Hyrule // Magic expects them. ZScream does the same in SaveOWEntrances. for (int i = 0; i < kNumOverworldEntrances; ++i) { RETURN_IF_ERROR(write_entrance(i, kOverworldEntranceMap + (i * 2), kOverworldEntrancePos + (i * 2), kOverworldEntranceEntranceId + i)); } if (expanded_entrances) { // For ZS v3+ ROMs, mirror writes into the expanded tables the way // ZeldaFullEditor does when the ASM patch is active. for (int i = 0; i < kNumOverworldEntrances; ++i) { RETURN_IF_ERROR(write_entrance(i, kOverworldEntranceMapExpanded + (i * 2), kOverworldEntrancePosExpanded + (i * 2), kOverworldEntranceEntranceIdExpanded + i)); } } return absl::OkStatus(); } absl::Status SaveHoles(Rom* rom, const std::vector& holes) { for (int i = 0; i < kNumOverworldHoles; ++i) { RETURN_IF_ERROR( rom->WriteShort(kOverworldHoleArea + (i * 2), holes[i].map_id_)); // ZeldaFullEditor/Data/Overworld.cs::LoadHoles() adds 0x400 when loading // (see lines ~1006-1014). SaveOWEntrances subtracts it before writing // (Save.cs lines ~1088-1092). We replicate that here so vanilla ROMs // receive the expected values. uint16_t rom_map_pos = static_cast(holes[i].map_pos_ >= 0x400 ? holes[i].map_pos_ - 0x400 : holes[i].map_pos_); RETURN_IF_ERROR(rom->WriteShort(kOverworldHolePos + (i * 2), rom_map_pos)); RETURN_IF_ERROR( rom->WriteByte(kOverworldHoleEntrance + i, holes[i].entrance_id_)); } return absl::OkStatus(); } } // namespace yaze::zelda3