yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
room_entrance.h
Go to the documentation of this file.
1#ifndef YAZE_APP_ZELDA3_DUNGEON_ROOM_ENTRANCE_H
2#define YAZE_APP_ZELDA3_DUNGEON_ROOM_ENTRANCE_H
3
4#include <array>
5#include <cstddef>
6#include <cstdint>
7#include <utility>
8#include <vector>
9
10#include "absl/status/status.h"
11#include "absl/strings/str_format.h"
12#include "rom/rom.h"
13#include "util/macro.h"
16
17namespace yaze {
18namespace zelda3 {
19
20// 0x14577 word value for each room
21constexpr int kEntranceRoom = 0x14813;
22
23// 8 bytes per room, HU, FU, HD, FD, HL, FL, HR, FR
24constexpr int kEntranceScrollEdge = 0x1491D; // 0x14681
25constexpr int kEntranceYScroll = 0x14D45; // 0x14AA9 2 bytes each room
26constexpr int kEntranceXScroll = 0x14E4F; // 0x14BB3 2 bytes
27constexpr int kEntranceYPosition = 0x14F59; // 0x14CBD 2bytes
28constexpr int kEntranceXPosition = 0x15063; // 0x14DC7 2bytes
29constexpr int kEntranceCameraYTrigger = 0x1516D; // 0x14ED1 2bytes
30constexpr int kEntranceCameraXTrigger = 0x15277; // 0x14FDB 2bytes
31
32constexpr int kEntranceBlockset = 0x15381; // 0x150E5 1byte
33constexpr int kEntranceFloor = 0x15406; // 0x1516A 1byte
34constexpr int kEntranceDungeon = 0x1548B; // 0x151EF 1byte (dungeon id)
35constexpr int kEntranceDoor = 0x15510; // 0x15274 1byte
36
37// 1 byte, ---b ---a b = bg2, a = need to check
38constexpr int kEntranceLadderBG = 0x15595; // 0x152F9
39constexpr int kEntrancescrolling = 0x1561A; // 0x1537E 1byte --h- --v-
40constexpr int kEntranceScrollQuadrant = 0x1569F; // 0x15403 1byte
41constexpr int kEntranceExit = 0x15724; // 0x15488 2byte word
42constexpr int kEntranceMusic = 0x1582E; // 0x15592
43
44using DungeonEntranceWriteRange = std::pair<uint32_t, uint32_t>;
45
46// One regular entrance is stored as 17 discontiguous records: eight words,
47// eight scalar bytes, and one eight-byte camera-boundary record. Keep this
48// layout shared by save validation, manifest preflight, and write prediction so
49// those safety surfaces cannot drift apart.
50constexpr std::array<std::pair<uint32_t, uint32_t>, 17>
70
71inline std::vector<DungeonEntranceWriteRange> RegularDungeonEntranceWriteRanges(
72 int entrance_id) {
73 if (entrance_id < 0 || entrance_id >= kNumRegularDungeonEntrances) {
74 return {};
75 }
76
77 std::vector<DungeonEntranceWriteRange> ranges;
78 ranges.reserve(kRegularDungeonEntranceTableLayout.size());
79 for (const auto& [table_pc, record_width] :
81 const uint32_t begin =
82 table_pc + static_cast<uint32_t>(entrance_id) * record_width;
83 ranges.emplace_back(begin, begin + record_width);
84 }
85 return ranges;
86}
87
88class RoomEntrance;
89
90absl::Status ValidateRegularDungeonEntranceForSave(const Rom& rom,
91 const RoomEntrance& entrance,
92 int entrance_id);
93
94constexpr int items_data_start = 0xDDE9; // save purpose
95constexpr int items_data_end = 0xE6B2; // save purpose
96constexpr int initial_equipement = 0x271A6;
97
98// item id you get instead if you already have that item
99constexpr int chests_backupitems = 0x3B528;
100constexpr int chests_yoffset = 0x4836C;
101constexpr int chests_xoffset = 0x4836C + (76 * 1);
102constexpr int chests_itemsgfx = 0x4836C + (76 * 2);
103constexpr int chests_itemswide = 0x4836C + (76 * 3);
104constexpr int chests_itemsproperties = 0x4836C + (76 * 4);
105constexpr int chests_sramaddress = 0x4836C + (76 * 5);
106constexpr int chests_sramvalue = 0x4836C + (76 * 7);
107constexpr int chests_msgid = 0x442DD;
108
109constexpr int dungeons_startrooms = 0x7939;
110constexpr int dungeons_endrooms = 0x792D;
111constexpr int dungeons_bossrooms = 0x10954; // short value
112
113// Bed Related Values (Starting location)
114constexpr int bedPositionX = 0x039A37; // short value
115constexpr int bedPositionY = 0x039A32; // short value
116
117// short value (on 2 different bytes)
118constexpr int bedPositionResetXLow = 0x02DE53;
119constexpr int bedPositionResetXHigh = 0x02DE58;
120
121// short value (on 2 different bytes)
122constexpr int bedPositionResetYLow = 0x02DE5D;
123constexpr int bedPositionResetYHigh = 0x02DE62;
124
125constexpr int bedSheetPositionX = 0x0480BD; // short value
126constexpr int bedSheetPositionY = 0x0480B8; // short value
127
132 public:
133 RoomEntrance() = default;
134 RoomEntrance(Rom* rom, uint8_t entrance_id, bool is_spawn_point = false)
136 if (is_spawn_point) {
137 // This combined array remains a read-only navigation view for existing
138 // UI code. Authoritative spawn persistence uses DungeonSpawnPoint, which
139 // has distinct door-tilemap and entrance-ID fields.
140 auto loaded_spawn = DungeonSpawnPoint::Load(*rom, entrance_id);
141 if (!loaded_spawn.ok()) {
142 return;
143 }
144 const DungeonSpawnPoint& spawn = *loaded_spawn;
145 room_ = static_cast<int16_t>(spawn.room_id);
152 blockset_ = spawn.main_gfx;
153 music_ = spawn.song;
154 dungeon_id_ = spawn.dungeon_id;
155 floor_ = spawn.floor;
156 door_ = 0;
157 ladder_bg_ = spawn.layer;
160 exit_ = static_cast<int16_t>(spawn.overworld_door_tilemap);
169 return;
170 }
171
172 room_ = static_cast<short>(
173 (rom->data()[kEntranceRoom + (entrance_id * 2) + 1] << 8) +
174 rom->data()[kEntranceRoom + (entrance_id * 2)]);
175 y_position_ = static_cast<uint16_t>(
176 (rom->data()[kEntranceYPosition + (entrance_id * 2) + 1] << 8) +
177 rom->data()[kEntranceYPosition + (entrance_id * 2)]);
178 x_position_ = static_cast<uint16_t>(
179 (rom->data()[kEntranceXPosition + (entrance_id * 2) + 1] << 8) +
180 rom->data()[kEntranceXPosition + (entrance_id * 2)]);
181 camera_x_ = static_cast<uint16_t>(
182 (rom->data()[kEntranceXScroll + (entrance_id * 2) + 1] << 8) +
183 rom->data()[kEntranceXScroll + (entrance_id * 2)]);
184 camera_y_ = static_cast<uint16_t>(
185 (rom->data()[kEntranceYScroll + (entrance_id * 2) + 1] << 8) +
186 rom->data()[kEntranceYScroll + (entrance_id * 2)]);
187 camera_trigger_y_ = static_cast<uint16_t>(
188 (rom->data()[(kEntranceCameraYTrigger + (entrance_id * 2)) + 1] << 8) +
189 rom->data()[kEntranceCameraYTrigger + (entrance_id * 2)]);
190 camera_trigger_x_ = static_cast<uint16_t>(
191 (rom->data()[(kEntranceCameraXTrigger + (entrance_id * 2)) + 1] << 8) +
192 rom->data()[kEntranceCameraXTrigger + (entrance_id * 2)]);
193 blockset_ = rom->data()[kEntranceBlockset + entrance_id];
194 music_ = rom->data()[kEntranceMusic + entrance_id];
195 dungeon_id_ = rom->data()[kEntranceDungeon + entrance_id];
196 floor_ = rom->data()[kEntranceFloor + entrance_id];
197 door_ = rom->data()[kEntranceDoor + entrance_id];
198 ladder_bg_ = rom->data()[kEntranceLadderBG + entrance_id];
199 scrolling_ = rom->data()[kEntrancescrolling + entrance_id];
200 scroll_quadrant_ = rom->data()[kEntranceScrollQuadrant + entrance_id];
201 exit_ = static_cast<short>(
202 (rom->data()[kEntranceExit + (entrance_id * 2) + 1] << 8) +
203 rom->data()[kEntranceExit + (entrance_id * 2)]);
204
206 rom->data()[kEntranceScrollEdge + 0 + (entrance_id * 8)];
208 rom->data()[kEntranceScrollEdge + 1 + (entrance_id * 8)];
210 rom->data()[kEntranceScrollEdge + 2 + (entrance_id * 8)];
212 rom->data()[kEntranceScrollEdge + 3 + (entrance_id * 8)];
214 rom->data()[kEntranceScrollEdge + 4 + (entrance_id * 8)];
216 rom->data()[kEntranceScrollEdge + 5 + (entrance_id * 8)];
218 rom->data()[kEntranceScrollEdge + 6 + (entrance_id * 8)];
220 rom->data()[kEntranceScrollEdge + 7 + (entrance_id * 8)];
221 }
222
223 bool dirty() const { return dirty_; }
224 void MarkDirty() { dirty_ = true; }
225 void ClearDirty() { dirty_ = false; }
226 bool is_spawn_point() const { return is_spawn_point_; }
227
228 absl::Status Save(Rom* rom, int entrance_id, bool is_spawn_point = false) {
229 if (!rom || !rom->is_loaded()) {
230 return absl::FailedPreconditionError("ROM not loaded");
231 }
232 if (is_spawn_point) {
233 return absl::FailedPreconditionError(absl::StrFormat(
234 "Dungeon spawn point 0x%02X cannot be saved: spawn writes are "
235 "disabled through the legacy RoomEntrance model; use the dedicated "
236 "spawn ROM schema in DungeonSpawnPoint",
237 entrance_id));
238 }
239
240 // Deliberate defense in depth for direct callers outside the bulk editor
241 // preflight: reject model/ID mismatches before the first regular write.
243 ValidateRegularDungeonEntranceForSave(*rom, *this, entrance_id));
244 RETURN_IF_ERROR(rom->WriteShort(kEntranceRoom + (entrance_id * 2), room_));
246 rom->WriteShort(kEntranceYPosition + (entrance_id * 2), y_position_));
248 rom->WriteShort(kEntranceXPosition + (entrance_id * 2), x_position_));
250 rom->WriteShort(kEntranceYScroll + (entrance_id * 2), camera_y_));
252 rom->WriteShort(kEntranceXScroll + (entrance_id * 2), camera_x_));
253 RETURN_IF_ERROR(rom->WriteShort(kEntranceCameraXTrigger + (entrance_id * 2),
255 RETURN_IF_ERROR(rom->WriteShort(kEntranceCameraYTrigger + (entrance_id * 2),
257 RETURN_IF_ERROR(rom->WriteShort(kEntranceExit + (entrance_id * 2), exit_));
259 (uint8_t)(blockset_ & 0xFF)));
261 rom->WriteByte(kEntranceMusic + entrance_id, (uint8_t)(music_ & 0xFF)));
262 RETURN_IF_ERROR(rom->WriteByte(kEntranceDungeon + entrance_id,
263 (uint8_t)(dungeon_id_ & 0xFF)));
265 rom->WriteByte(kEntranceDoor + entrance_id, (uint8_t)(door_ & 0xFF)));
267 rom->WriteByte(kEntranceFloor + entrance_id, (uint8_t)(floor_ & 0xFF)));
269 (uint8_t)(ladder_bg_ & 0xFF)));
271 (uint8_t)(scrolling_ & 0xFF)));
273 (uint8_t)(scroll_quadrant_ & 0xFF)));
274 RETURN_IF_ERROR(rom->WriteByte(kEntranceScrollEdge + 0 + (entrance_id * 8),
276 RETURN_IF_ERROR(rom->WriteByte(kEntranceScrollEdge + 1 + (entrance_id * 8),
278 RETURN_IF_ERROR(rom->WriteByte(kEntranceScrollEdge + 2 + (entrance_id * 8),
280 RETURN_IF_ERROR(rom->WriteByte(kEntranceScrollEdge + 3 + (entrance_id * 8),
282 RETURN_IF_ERROR(rom->WriteByte(kEntranceScrollEdge + 4 + (entrance_id * 8),
284 RETURN_IF_ERROR(rom->WriteByte(kEntranceScrollEdge + 5 + (entrance_id * 8),
286 RETURN_IF_ERROR(rom->WriteByte(kEntranceScrollEdge + 6 + (entrance_id * 8),
288 RETURN_IF_ERROR(rom->WriteByte(kEntranceScrollEdge + 7 + (entrance_id * 8),
290 ClearDirty();
291 return absl::OkStatus();
292 }
293
294 uint16_t entrance_id_ = 0;
295 uint16_t x_position_ = 0;
296 uint16_t y_position_ = 0;
297 uint16_t camera_x_ = 0;
298 uint16_t camera_y_ = 0;
299 uint16_t camera_trigger_x_ = 0;
300 uint16_t camera_trigger_y_ = 0;
301
302 int16_t room_ = 0;
303 uint8_t blockset_ = 0;
304 uint8_t floor_ = 0;
305 uint8_t dungeon_id_ = 0;
306 uint8_t ladder_bg_ = 0;
307 uint8_t scrolling_ = 0;
308 uint8_t scroll_quadrant_ = 0;
309 int16_t exit_ = 0;
310 uint8_t music_ = 0;
311 uint8_t door_ = 0;
312
321
322 private:
323 bool dirty_ = false;
324 bool is_spawn_point_ = false;
325};
326
327inline std::vector<DungeonEntranceWriteRange>
329 const std::array<RoomEntrance, kNumDungeonEntranceSlots>& entrances) {
330 std::vector<DungeonEntranceWriteRange> ranges;
331 for (int entrance_id = 0; entrance_id < kNumRegularDungeonEntrances;
332 ++entrance_id) {
333 const auto& entrance = entrances[kNumDungeonSpawnPoints + entrance_id];
334 if (!entrance.dirty()) {
335 continue;
336 }
337 auto entrance_ranges = RegularDungeonEntranceWriteRanges(entrance_id);
338 ranges.insert(ranges.end(), entrance_ranges.begin(), entrance_ranges.end());
339 }
340 return ranges;
341}
342
344 const Rom& rom, const RoomEntrance& entrance, int entrance_id) {
345 if (entrance_id < 0 || entrance_id >= kNumRegularDungeonEntrances) {
346 return absl::OutOfRangeError(
347 absl::StrFormat("Regular dungeon entrance ID %d is outside [0, %d)",
348 entrance_id, kNumRegularDungeonEntrances));
349 }
350 if (entrance.is_spawn_point()) {
351 return absl::InvalidArgumentError(absl::StrFormat(
352 "Regular dungeon entrance 0x%02X is marked as a spawn point",
353 entrance_id));
354 }
355 if (entrance.entrance_id_ != static_cast<uint16_t>(entrance_id)) {
356 return absl::InvalidArgumentError(absl::StrFormat(
357 "Regular dungeon entrance model ID 0x%02X does not match requested "
358 "save ID 0x%02X",
359 static_cast<int>(entrance.entrance_id_), entrance_id));
360 }
361 if (entrance.room_ < 0 || entrance.room_ >= kNumberOfRooms) {
362 return absl::OutOfRangeError(absl::StrFormat(
363 "Regular dungeon entrance 0x%02X room ID %d is outside [0, %d)",
364 entrance_id, entrance.room_, kNumberOfRooms));
365 }
366
367 for (const auto& [begin, end] :
369 if (end > rom.size()) {
370 return absl::FailedPreconditionError(absl::StrFormat(
371 "Regular dungeon entrance 0x%02X write range "
372 "0x%06X-0x%06X exceeds ROM size 0x%X",
373 entrance_id, begin, end, static_cast<uint32_t>(rom.size())));
374 }
375 }
376 return absl::OkStatus();
377}
378
380 const Rom& rom,
381 const std::array<RoomEntrance, kNumDungeonEntranceSlots>& entrances,
382 bool dirty_only = true) {
383 for (int entrance_id = 0; entrance_id < kNumRegularDungeonEntrances;
384 ++entrance_id) {
385 const auto& entrance = entrances[kNumDungeonSpawnPoints + entrance_id];
386 if (dirty_only && !entrance.dirty()) {
387 continue;
388 }
390 ValidateRegularDungeonEntranceForSave(rom, entrance, entrance_id));
391 }
392 return absl::OkStatus();
393}
394
396 const std::array<RoomEntrance, kNumDungeonEntranceSlots>& entrances,
397 bool dirty_only = true) {
398 for (int spawn_id = 0; spawn_id < kNumDungeonSpawnPoints; ++spawn_id) {
399 if (dirty_only && !entrances[spawn_id].dirty()) {
400 continue;
401 }
402 return absl::FailedPreconditionError(absl::StrFormat(
403 "Dungeon spawn point 0x%02X is scheduled for save through the legacy "
404 "RoomEntrance model; use the dedicated DungeonSpawnPoint model",
405 spawn_id));
406 }
407 return absl::OkStatus();
408}
409
410inline absl::Status SaveAllDungeonEntrances(
411 Rom* rom, std::array<RoomEntrance, kNumDungeonEntranceSlots>& entrances,
412 bool dirty_only = true) {
413 if (!rom || !rom->is_loaded()) {
414 return absl::FailedPreconditionError("ROM not loaded");
415 }
416
417 // The combined RoomEntrance array remains a read-only navigation view for
418 // spawn slots. Reject dirty legacy views before writing any regular record;
419 // authoritative spawn saves use SaveAllDungeonSpawnPoints separately.
421 RejectUnsupportedDungeonSpawnPointSaves(entrances, dirty_only));
422
423 // Validate every participating regular record before the first write.
425 ValidateRegularDungeonEntrancesForSave(*rom, entrances, dirty_only));
426
427 for (int entrance_id = 0; entrance_id < kNumRegularDungeonEntrances;
428 ++entrance_id) {
429 auto& entrance = entrances[kNumDungeonSpawnPoints + entrance_id];
430 if (dirty_only && !entrance.dirty()) {
431 continue;
432 }
433 RETURN_IF_ERROR(entrance.Save(rom, entrance_id, false));
434 }
435
436 return absl::OkStatus();
437}
438
439} // namespace zelda3
440} // namespace yaze
441
442#endif // YAZE_APP_ZELDA3_DUNGEON_ROOM_ENTRANCE_H
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:586
auto data() const
Definition rom.h:151
auto size() const
Definition rom.h:150
absl::Status WriteShort(int addr, uint16_t value)
Definition rom.cc:628
bool is_loaded() const
Definition rom.h:144
std::array< uint8_t, 8 > camera_scroll_boundaries
static absl::StatusOr< DungeonSpawnPoint > Load(const Rom &rom, int spawn_id)
Dungeon Room Entrance or Spawn Point.
absl::Status Save(Rom *rom, int entrance_id, bool is_spawn_point=false)
RoomEntrance(Rom *rom, uint8_t entrance_id, bool is_spawn_point=false)
std::vector< DungeonEntranceWriteRange > RegularDungeonEntranceWriteRanges(int entrance_id)
constexpr int bedPositionResetXLow
constexpr int bedSheetPositionX
constexpr int kEntranceCameraXTrigger
constexpr int kEntranceRoom
constexpr int dungeons_startrooms
constexpr std::array< std::pair< uint32_t, uint32_t >, 17 > kRegularDungeonEntranceTableLayout
constexpr int kEntranceDoor
constexpr int kEntranceXScroll
constexpr int bedPositionResetXHigh
constexpr int items_data_start
constexpr int bedPositionResetYHigh
absl::Status ValidateRegularDungeonEntranceForSave(const Rom &rom, const RoomEntrance &entrance, int entrance_id)
constexpr int bedPositionResetYLow
std::vector< DungeonEntranceWriteRange > CollectDirtyRegularDungeonEntranceWriteRanges(const std::array< RoomEntrance, kNumDungeonEntranceSlots > &entrances)
constexpr int bedPositionY
constexpr int initial_equipement
constexpr int kEntranceExit
constexpr int kNumRegularDungeonEntrances
constexpr int chests_msgid
constexpr int chests_itemsgfx
constexpr int chests_sramvalue
constexpr int chests_backupitems
constexpr int chests_xoffset
constexpr int kEntranceFloor
constexpr int bedPositionX
constexpr int kEntranceCameraYTrigger
constexpr int kEntranceBlockset
constexpr int kEntranceXPosition
constexpr int kEntranceLadderBG
constexpr int dungeons_endrooms
constexpr int kEntranceMusic
constexpr int chests_itemsproperties
absl::Status RejectUnsupportedDungeonSpawnPointSaves(const std::array< RoomEntrance, kNumDungeonEntranceSlots > &entrances, bool dirty_only=true)
absl::Status SaveAllDungeonEntrances(Rom *rom, std::array< RoomEntrance, kNumDungeonEntranceSlots > &entrances, bool dirty_only=true)
constexpr int kNumDungeonSpawnPoints
absl::Status ValidateRegularDungeonEntrancesForSave(const Rom &rom, const std::array< RoomEntrance, kNumDungeonEntranceSlots > &entrances, bool dirty_only=true)
constexpr int chests_sramaddress
constexpr int kNumberOfRooms
std::pair< uint32_t, uint32_t > DungeonEntranceWriteRange
constexpr int kEntranceScrollEdge
constexpr int kEntranceDungeon
constexpr int dungeons_bossrooms
constexpr int kEntranceScrollQuadrant
constexpr int kEntrancescrolling
constexpr int chests_yoffset
constexpr int kEntranceYScroll
constexpr int chests_itemswide
constexpr int items_data_end
constexpr int bedSheetPositionY
constexpr int kEntranceYPosition
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22