yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_entrance.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstddef>
5#include <utility>
6#include <vector>
7
8#include "absl/status/status.h"
9#include "absl/status/statusor.h"
10#include "rom/rom.h"
11#include "util/macro.h"
13
14namespace yaze::zelda3 {
15namespace {
16
18 const core::RomAddressOverrides& addresses) {
19 if (rom.vector().size() <= OverworldCustomASMHasBeenApplied) {
20 return false;
21 }
22 const auto version = OverworldVersionHelper::GetVersion(rom);
23 if (version == OverworldVersion::kVanilla) {
24 return false;
25 }
26 const auto flag_addr =
29 if (flag_addr >= rom.vector().size()) {
30 return false;
31 }
32 return rom.data()[flag_addr] != 0xB8;
33}
34
35} // namespace
36
37absl::StatusOr<std::vector<OverworldEntrance>> LoadEntrances(Rom* rom) {
38 return LoadEntrances(rom, core::RomSettings::Get().address_overrides());
39}
40
41absl::StatusOr<std::vector<OverworldEntrance>> LoadEntrances(
42 Rom* rom, const core::RomAddressOverrides& addresses) {
43 if (!rom)
44 return absl::InvalidArgumentError("Cannot load entrances without a ROM");
45 std::vector<OverworldEntrance> entrances;
46 int ow_entrance_map_ptr = kOverworldEntranceMap;
47 int ow_entrance_pos_ptr = kOverworldEntrancePos;
48 int ow_entrance_id_ptr = kOverworldEntranceEntranceId;
49 int num_entrances = 129;
50
51 // Check if expanded entrance data is actually present in ROM
52 // The flag position should contain 0xB8 for vanilla, something else for
53 // expanded
54 const bool expanded = HasExpandedEntranceTables(*rom, addresses);
55 if (expanded) {
56 // ROM has expanded entrance data - use expanded addresses
57 ow_entrance_map_ptr =
60 ow_entrance_pos_ptr =
63 ow_entrance_id_ptr =
66 num_entrances = 256; // Expanded entrance count
67 }
68 // Otherwise use vanilla addresses (already set above)
69
70 for (int i = 0; i < num_entrances; i++) {
71 ASSIGN_OR_RETURN(auto map_id, rom->ReadWord(ow_entrance_map_ptr + (i * 2)));
72 ASSIGN_OR_RETURN(auto map_pos,
73 rom->ReadWord(ow_entrance_pos_ptr + (i * 2)));
74 ASSIGN_OR_RETURN(auto entrance_id, rom->ReadByte(ow_entrance_id_ptr + i));
75 int p = map_pos >> 1;
76 int x = (p % 64);
77 int y = (p >> 6);
78 const bool deleted = map_pos == 0xFFFF;
79 const bool hole = expanded && !deleted && (map_id & 0x8000) != 0;
80 // Preserve unused rows' raw map word; live expanded rows encode the
81 // entrance kind separately from the area number.
82 const uint16_t area = expanded && !deleted ? map_id & 0x7FFF : map_id;
83 entrances.emplace_back((x * 16) + ((area % 8) * 512),
84 (y * 16) + (((area % 64) / 8) * 512), entrance_id,
85 area, map_pos, hole);
86 entrances.back().deleted = deleted;
87 entrances.back().entity_id_ = i;
88 }
89
90 return entrances;
91}
92
93absl::StatusOr<std::vector<OverworldEntrance>> LoadHoles(Rom* rom) {
94 if (!rom)
95 return absl::InvalidArgumentError("Cannot load holes without a ROM");
96 constexpr int kNumHoles = 0x13;
97 std::vector<OverworldEntrance> holes;
98 for (int i = 0; i < kNumHoles; i++) {
99 ASSIGN_OR_RETURN(auto map_id, rom->ReadWord(kOverworldHoleArea + (i * 2)));
100 ASSIGN_OR_RETURN(auto map_pos, rom->ReadWord(kOverworldHolePos + (i * 2)));
101 ASSIGN_OR_RETURN(auto entrance_id,
103 int p = (map_pos + 0x400) >> 1;
104 int x = (p % 64);
105 int y = (p >> 6);
106 holes.emplace_back(
107 (x * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512),
108 (y * 16) + (((map_id % 64) / 8) * 512), entrance_id, map_id,
109 (uint16_t)(map_pos + 0x400), true);
110 holes.back().deleted = map_pos == 0xFFFF;
111 holes.back().entity_id_ = i;
112 holes.back().RememberHoleSourceState(i);
113 }
114 return holes;
115}
116
117absl::Status SaveEntrances(Rom* rom,
118 const std::vector<OverworldEntrance>& entrances,
119 bool expanded_entrances) {
120 if (!rom)
121 return absl::InvalidArgumentError("Cannot save entrances without a ROM");
122 if (entrances.size() > (expanded_entrances
123 ? 256u
124 : static_cast<size_t>(kNumOverworldEntrances))) {
125 return absl::InvalidArgumentError(
126 "Too many records for the selected entrance table");
127 }
128 const size_t count = std::min<size_t>(
129 expanded_entrances ? 256 : kNumOverworldEntrances, entrances.size());
130 const size_t map_base = expanded_entrances ? GetOverworldEntranceMapExpanded()
132 const size_t pos_base = expanded_entrances ? GetOverworldEntrancePosExpanded()
134 const size_t id_base = expanded_entrances ? GetOverworldEntranceIdExpanded()
136 const size_t size = rom->vector().size();
137 for (const auto& range : {std::pair<size_t, size_t>{map_base, count * 2},
138 {pos_base, count * 2},
139 {id_base, count}}) {
140 if (range.first > size || range.second > size - range.first) {
141 return absl::OutOfRangeError(
142 "ROM does not contain complete entrance tables");
143 }
144 }
145 auto write_entrance = [&](int index, uint32_t map_addr, uint32_t pos_addr,
146 uint32_t id_addr) -> absl::Status {
147 // Mirrors ZeldaFullEditor/Save.cs::SaveOWEntrances (see lines ~1081-1085)
148 // where MapID and MapPos are written as 16-bit words and EntranceID as a
149 // byte.
150 const auto& record = entrances[index];
151 const uint16_t map_word =
152 expanded_entrances && !record.deleted
153 ? (record.map_id_ & 0x7FFF) | (record.is_hole_ ? 0x8000 : 0)
154 : record.map_id_;
155 RETURN_IF_ERROR(rom->WriteShort(map_addr, map_word));
157 rom->WriteShort(pos_addr, record.deleted ? 0xFFFF : record.map_pos_));
158 RETURN_IF_ERROR(rom->WriteByte(id_addr, entrances[index].entrance_id_));
159 return absl::OkStatus();
160 };
161
162 // Write only the selected layout; inactive tables are not independent
163 // storage and may have been repurposed by an expansion.
164 for (size_t i = 0; i < count; ++i) {
166 write_entrance(i, map_base + i * 2, pos_base + i * 2, id_base + i));
167 }
168
169 return absl::OkStatus();
170}
171
172absl::Status SaveHoles(Rom* rom, const std::vector<OverworldEntrance>& holes) {
173 if (!rom || holes.size() < kNumOverworldHoles) {
174 return absl::InvalidArgumentError(
175 "Saving holes requires a ROM and all 19 records");
176 }
177 const size_t size = rom->vector().size();
178 if (size < kOverworldHolePos + kNumOverworldHoles * 2 ||
181 return absl::OutOfRangeError("ROM does not contain complete hole tables");
182 }
184 HasExpandedEntranceTables(*rom,
185 core::RomSettings::Get().address_overrides())) {
186 return absl::FailedPreconditionError(
187 "Expanded holes must be saved through their entrance table rows");
188 }
189 for (int i = 0; i < kNumOverworldHoles; ++i) {
191 rom->WriteShort(kOverworldHoleArea + (i * 2), holes[i].map_id_));
192
193 // ZeldaFullEditor/Data/Overworld.cs::LoadHoles() adds 0x400 when loading
194 // (see lines ~1006-1014). SaveOWEntrances subtracts it before writing
195 // (Save.cs lines ~1088-1092). We replicate that here so vanilla ROMs
196 // receive the expected values.
197 uint16_t rom_map_pos =
198 holes[i].deleted ? 0xFFFF
199 : static_cast<uint16_t>(holes[i].map_pos_ >= 0x400
200 ? holes[i].map_pos_ - 0x400
201 : holes[i].map_pos_);
202 RETURN_IF_ERROR(rom->WriteShort(kOverworldHolePos + (i * 2), rom_map_pos));
204 rom->WriteByte(kOverworldHoleEntrance + i, holes[i].entrance_id_));
205 }
206
207 return absl::OkStatus();
208}
209
210} // namespace yaze::zelda3
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:52
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:1104
absl::StatusOr< uint8_t > ReadByte(int offset) const
Definition rom.cc:1036
const auto & vector() const
Definition rom.h:217
absl::StatusOr< uint16_t > ReadWord(int offset) const
Definition rom.cc:1044
auto data() const
Definition rom.h:213
auto size() const
Definition rom.h:212
absl::Status WriteShort(int addr, uint16_t value)
Definition rom.cc:1153
static RomSettings & Get()
static OverworldVersion GetVersion(const Rom &rom)
Detect ROM version from ASM marker byte.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
constexpr char kOverworldEntrancePosExpanded[]
constexpr char kOverworldEntranceMapExpanded[]
constexpr char kOverworldEntranceFlagExpanded[]
constexpr char kOverworldEntranceIdExpanded[]
bool HasExpandedEntranceTables(const Rom &rom, const core::RomAddressOverrides &addresses)
Zelda 3 specific classes and functions.
absl::Status SaveEntrances(Rom *rom, const std::vector< OverworldEntrance > &entrances, bool expanded_entrances)
constexpr int kOverworldHoleArea
constexpr int kOverworldEntrancePos
constexpr int kOverworldHoleEntrance
absl::StatusOr< std::vector< OverworldEntrance > > LoadEntrances(Rom *rom)
constexpr int kOverworldEntranceEntranceIdExpanded
constexpr int kNumOverworldHoles
constexpr int kOverworldEntranceEntranceId
constexpr int kOverworldHolePos
constexpr int kOverworldEntranceMap
constexpr int OverworldCustomASMHasBeenApplied
Definition common.h:90
@ kVanilla
0xFF in ROM, no ZScream ASM applied
constexpr int kOverworldEntrancePosExpanded
int GetOverworldEntrancePosExpanded()
int GetOverworldEntranceIdExpanded()
constexpr int kOverworldEntranceMapExpanded
absl::Status SaveHoles(Rom *rom, const std::vector< OverworldEntrance > &holes)
absl::StatusOr< std::vector< OverworldEntrance > > LoadHoles(Rom *rom)
constexpr int kOverworldEntranceExpandedFlagPos
int GetOverworldEntranceMapExpanded()
constexpr int kNumOverworldEntrances
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::optional< uint32_t > GetAddress(const std::string &key) const