yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_editor_v2_room_transfer.cc
Go to the documentation of this file.
2
3#include <map>
4#include <memory>
5#include <utility>
6
7#include "absl/strings/cord.h"
9#include "core/features.h"
10#include "rom/snes.h"
11#include "util/macro.h"
15
16namespace yaze::editor {
17namespace {
18
19class RoomTransferAction final : public UndoAction {
20 public:
21 using Restore = std::function<absl::Status(const DungeonRoomDocument&)>;
23 : plan_(std::move(plan)), restore_(std::move(restore)) {}
24 absl::Status Undo() override { return restore_(plan_.before); }
25 absl::Status Redo() override { return restore_(plan_.after); }
26 std::string Description() const override {
27 return absl::StrFormat("Replace room %03X contents", plan_.target_room_id);
28 }
29 size_t MemoryUsage() const override {
30 size_t bytes = sizeof(plan_);
31 for (const auto* document : {&plan_.before, &plan_.after}) {
32 const auto& c = document->contents;
33 bytes += c.objects.size() * sizeof(zelda3::RoomObject) +
34 c.chests.size() * sizeof(chest_data) +
35 c.doors.size() * sizeof(zelda3::Room::Door) +
36 c.sprites.size() * sizeof(DungeonSpriteSnapshot) +
37 c.items.size() * sizeof(zelda3::PotItem);
38 }
39 return bytes;
40 }
41
42 private:
45};
46
47absl::Status CheckTransferSaveFlags(const zelda3::Room& changed) {
48 const auto& f = core::FeatureFlags::get().dungeon;
49 if ((!f.kSaveObjects && (changed.object_stream_dirty() ||
50 changed.object_stream_header_dirty())) ||
51 (!f.kSaveRoomHeaders && changed.header_dirty()) ||
52 (!f.kSaveSprites && changed.sprites_dirty()) ||
53 (!f.kSaveChests && changed.chests_dirty()) ||
54 (!f.kSavePotItems && changed.pot_items_dirty()) ||
55 (!f.kSaveTorches && changed.torches_dirty()) ||
56 (!f.kSaveBlocks && changed.blocks_dirty()) ||
57 (!f.kSaveCollision && changed.custom_collision_dirty()) ||
58 (!f.kSaveWaterFillZones && changed.water_fill_dirty())) {
59 return absl::FailedPreconditionError(
60 "A changed room domain has saving disabled; enable it before "
61 "replacement");
62 }
63 return absl::OkStatus();
64}
65
66// The legacy torch loader has no status result and accepts a segment ending
67// at the declared table length. Verify its framing before trusting a new load.
68absl::Status ValidateTransferTorchSource(const Rom& rom, int room_id) {
69 ASSIGN_OR_RETURN(const auto length,
71 constexpr uint16_t kMaxTorchTableBytes = 0x120;
72 if (length > kMaxTorchTableBytes || (length & 1) != 0 ||
73 static_cast<uint64_t>(zelda3::kTorchData) + length > rom.size()) {
74 return absl::FailedPreconditionError(
75 "Torch table has an invalid length or lies outside the ROM");
76 }
77 size_t offset = 0;
78 bool found_room = false;
79 while (offset < length) {
80 ASSIGN_OR_RETURN(const auto segment_room,
81 rom.ReadWord(zelda3::kTorchData + offset));
82 offset += 2;
83 // Standalone end markers are tolerated by the existing table reader.
84 if (segment_room == 0xFFFF)
85 continue;
86 if (segment_room >= zelda3::kNumberOfRooms) {
87 return absl::FailedPreconditionError(
88 "Torch table has an invalid room ID");
89 }
90 if (segment_room == room_id) {
91 if (found_room) {
92 return absl::FailedPreconditionError(absl::StrFormat(
93 "Torch table contains duplicate segments for room %03X", room_id));
94 }
95 found_room = true;
96 }
97 bool terminated = false;
98 while (offset < length) {
99 ASSIGN_OR_RETURN(const auto entry,
100 rom.ReadWord(zelda3::kTorchData + offset));
101 offset += 2;
102 if (entry == 0xFFFF) {
103 terminated = true;
104 break;
105 }
106 }
107 if (!terminated) {
108 return absl::FailedPreconditionError(absl::StrFormat(
109 "Torch segment for room %03X has no end marker", segment_room));
110 }
111 }
112 return absl::OkStatus();
113}
114
115// Fixed room headers have no copy-on-write allocator. Refuse a replacement
116// when its in-place header write would also change another room's header.
117absl::Status ValidateTransferHeaderOwnership(const Rom& rom, int room_id) {
118 ASSIGN_OR_RETURN(const auto table_snes,
120 ASSIGN_OR_RETURN(const auto bank,
122 const auto table_bank = (table_snes >> 16) & 0xFF;
123 if ((table_snes & 0xFFFF) < 0x8000 || table_bank == 0x7E ||
124 table_bank == 0x7F || table_bank == 0xFE || table_bank == 0xFF ||
125 bank == 0x7E || bank == 0x7F || bank == 0xFE || bank == 0xFF) {
126 return absl::FailedPreconditionError("Invalid room header pointer table");
127 }
128 const auto table_pc = SnesToPc(table_snes);
129 if (static_cast<uint64_t>(table_pc) + zelda3::kNumberOfRooms * 2 >
130 rom.size()) {
131 return absl::FailedPreconditionError(
132 "Room header pointer table is truncated");
133 }
134 ASSIGN_OR_RETURN(const auto header, rom.ReadWord(table_pc + room_id * 2));
135 const auto header_pc = SnesToPc((bank << 16) | header);
136 if (header < 0x8000 || static_cast<uint64_t>(header_pc) + 14 > rom.size()) {
137 return absl::FailedPreconditionError("Replacement room header is invalid");
138 }
139 for (int other_id = 0; other_id < zelda3::kNumberOfRooms; ++other_id) {
140 if (other_id == room_id)
141 continue;
142 ASSIGN_OR_RETURN(const auto other_header,
143 rom.ReadWord(table_pc + other_id * 2));
144 const auto other_pc = SnesToPc((bank << 16) | other_header);
145 if (other_header < 0x8000 ||
146 static_cast<uint64_t>(other_pc) + 14 > rom.size())
147 continue;
148 if (header_pc < other_pc + 14 && other_pc < header_pc + 14) {
149 auto status = absl::FailedPreconditionError(absl::StrFormat(
150 "Room %03X header overlaps room %03X; exclude room properties or "
151 "give the destination independent header storage before replacing it",
152 room_id, other_id));
153 status.SetPayload(kRoomTransferSharedHeaderPayload, absl::Cord("1"));
154 return status;
155 }
156 }
157 return absl::OkStatus();
158}
159
160absl::Status AssignTransferredWaterMask(Rom& rom, const DungeonRoomStore& rooms,
162 if (!(plan.options.domains & kTransferWater) || !plan.after.water.has_data)
163 return absl::OkStatus();
164 ASSIGN_OR_RETURN(const auto saved, zelda3::LoadWaterFillTable(&rom));
165 std::map<int, uint8_t> masks;
166 for (const auto& zone : saved)
167 masks[zone.room_id] = zone.sram_bit_mask;
168 rooms.ForEachMaterialized([&](int id, const zelda3::Room& room) {
169 if (room.has_water_fill_zone())
170 masks[id] = room.water_fill_sram_bit_mask();
171 else if (room.water_fill_dirty())
172 masks.erase(id);
173 });
174 uint8_t used = 0;
175 for (const auto& [id, mask] : masks) {
176 if (id != plan.target_room_id)
177 used |= mask;
178 }
179 const auto prior = plan.before.water.sram_bit_mask;
180 if (prior && !(prior & (prior - 1)) && !(used & prior)) {
181 plan.after.water.sram_bit_mask = prior;
182 return absl::OkStatus();
183 }
184 for (unsigned bit = 1; bit <= 128; bit <<= 1) {
185 if (!(used & bit)) {
186 plan.after.water.sram_bit_mask = static_cast<uint8_t>(bit);
187 return absl::OkStatus();
188 }
189 }
190 return absl::ResourceExhaustedError(
191 "No free water-fill save-state bit remains");
192}
193
194} // namespace
195
197 if (!rom_ || !rom_->is_loaded() || !IsValidRoomId(room_id)) {
198 return absl::InvalidArgumentError("Choose a room in the current ROM");
199 }
200 const auto* existing = rooms_.GetIfMaterialized(room_id);
201 if (existing && existing->rom() != rom_) {
202 return absl::FailedPreconditionError("Room belongs to another ROM");
203 }
204 if (existing &&
205 ((!existing->AreTorchesLoaded() && existing->torches_dirty()) ||
206 (!existing->AreBlocksLoaded() && existing->blocks_dirty()))) {
207 return absl::FailedPreconditionError(
208 "Finish loading the room's torch/block edits before copying it");
209 }
210 // Validate before invoking the legacy void loaders. Empty/truncated streams
211 // must not become apparently valid empty source collections.
212 if (!existing || !existing->AreTorchesLoaded()) {
213 RETURN_IF_ERROR(ValidateTransferTorchSource(*rom_, room_id));
214 }
215 if (!existing || !existing->AreSpritesLoaded()) {
216 if (existing && existing->sprites_dirty()) {
217 return absl::FailedPreconditionError(
218 "Finish loading the room's sprite edits first");
219 }
221 }
222 if (!existing || !existing->ArePotItemsLoaded()) {
223 if (existing && existing->pot_items_dirty()) {
224 return absl::FailedPreconditionError(
225 "Finish loading the room's pot-item edits first");
226 }
228 }
229 if ((!existing || !existing->AreObjectsLoaded()) &&
230 rom_->size() >=
233 }
235 auto& room = *rooms_.GetIfLoaded(room_id);
236 room.EnsureSpritesLoaded();
237 if (!room.AreSpritesLoaded()) {
238 return absl::FailedPreconditionError("Room sprites did not load");
239 }
240 if (!room.AreChestsLoaded()) {
241 if (room.chests_dirty()) {
242 return absl::FailedPreconditionError(
243 "Finish loading the room's chest edits first");
244 }
245 room.LoadChests();
246 }
247 room.EnsurePotItemsLoaded();
248 if (!room.water_fill_dirty() && !room.has_water_fill_zone()) {
250 for (const auto& zone : zones) {
251 if (zone.room_id != room_id)
252 continue;
253 room.set_water_fill_sram_bit_mask(zone.sram_bit_mask);
254 for (const auto offset : zone.fill_offsets)
255 room.SetWaterFillTile(offset % 64, offset / 64, true);
256 room.ClearWaterFillDirty();
257 break;
258 }
259 }
260 if (!room.AreTorchesLoaded())
261 room.LoadTorches();
262 if (!room.AreBlocksLoaded())
263 room.LoadBlocks();
264 if (!room.AreChestsLoaded() || !room.ArePotItemsLoaded() ||
265 !room.AreTorchesLoaded() || !room.AreBlocksLoaded()) {
266 return absl::FailedPreconditionError(
267 "Room data did not fully load; repair the source before copying it");
268 }
269 return absl::OkStatus();
270}
271
277
278absl::StatusOr<DungeonRoomTransferPlan> DungeonEditorV2::PreviewRoomTransfer(
279 int target_room_id, int source_room_id, const std::string& json,
280 const DungeonRoomTransferOptions& options) {
281 if (source_room_id < -1) {
282 return absl::InvalidArgumentError("Invalid source room");
283 }
284 if (source_room_id == target_room_id) {
285 return absl::InvalidArgumentError("Choose a different source room");
286 }
287 DungeonRoomDocument source;
288 if (source_room_id >= 0) {
290 source = CaptureDungeonRoomDocument(*rooms_.GetIfLoaded(source_room_id));
291 } else {
293 }
295 ASSIGN_OR_RETURN(auto plan,
297 source, options));
298 if (source_room_id >= 0) {
299 plan.clone_source_room_id = source_room_id;
300 plan.clone_source_before = std::move(source);
301 }
302 auto capture_selection = [&](DungeonCanvasViewer* viewer) {
303 if (!viewer || viewer->current_room_id() != target_room_id)
304 return;
305 plan.before.contents.selected_objects =
306 viewer->object_interaction().GetSelectedObjectIndices();
307 plan.before.contents.selected_entities = viewer->object_interaction()
308 .entity_coordinator()
309 .SelectedEntitiesForEdit();
310 };
311 room_viewers_.ForEach([&](int, std::unique_ptr<DungeonCanvasViewer>& viewer) {
312 capture_selection(viewer.get());
313 });
314 capture_selection(workbench_compare_viewer_.get());
315 capture_selection(workbench_viewer_.get());
316 RETURN_IF_ERROR(AssignTransferredWaterMask(*rom_, rooms_, plan));
317 if (plan.changed())
319 return plan;
320}
321
323 const DungeonRoomTransferPlan& plan) {
324 Rom scratch(*rom_);
325 // Only a detached model is visible to serializers: no live renderer, palette
326 // state, history, source files or borrowed room buffers can be mutated here.
327 // GameData is roughly 1.8 MB; keep it off small worker/WebAssembly stacks.
328 // Declare it before the editor so borrowed state outlives editor teardown.
329 auto scratch_game = std::make_unique<zelda3::GameData>();
330 auto shadow = std::make_unique<DungeonEditorV2>(&scratch);
331 if (game_data_)
332 scratch_game->version = game_data_->version;
333 shadow->SetGameData(scratch_game.get());
334 shadow->dependencies_.project = dependencies_.project;
335 rooms_.ForEachMaterialized([&](int id, const zelda3::Room& original) {
336 auto& copy = shadow->rooms_[id];
337 copy = zelda3::Room(id, &scratch, scratch_game.get());
338 if (original.AreTorchesLoaded())
339 copy.LoadTorches();
340 if (original.AreBlocksLoaded())
341 copy.LoadBlocks();
343 for (auto& object : copy.GetTileObjects())
344 object.SetRom(&scratch);
345 copy.SetLoaded(original.IsLoaded());
346 copy.RestoreSaveDirtySnapshot(original.CaptureSaveDirtySnapshot());
347 });
348 // The water table is project-owned and may precede room materialization.
349 // Include saved zones absent from the live room store in global preflight.
350 ASSIGN_OR_RETURN(const auto saved_zones,
352 for (const auto& zone : saved_zones) {
353 if (shadow->rooms_.GetIfMaterialized(zone.room_id))
354 continue;
355 auto& copy = shadow->rooms_[zone.room_id];
356 copy.set_water_fill_sram_bit_mask(zone.sram_bit_mask);
357 for (const auto offset : zone.fill_offsets)
358 copy.SetWaterFillTile(offset % 64, offset / 64, true);
359 copy.ClearWaterFillDirty();
360 }
361 auto& target = *shadow->rooms_.GetIfLoaded(plan.target_room_id);
362 // Check only newly changed domains; older unrelated unsaved edits retain
363 // their normal Save behavior in the subsequent shared-table preflight.
364 const auto prior_dirty = target.CaptureSaveDirtySnapshot();
365 target.ClearSaveDirtyState();
366 target.ClearCustomCollisionDirty();
367 target.ClearWaterFillDirty();
368 ApplyDungeonRoomDocument(target, plan.after);
369 RETURN_IF_ERROR(CheckTransferSaveFlags(target));
370 const auto changed_dirty = target.CaptureSaveDirtySnapshot();
371 target.RestoreSaveDirtySnapshot(prior_dirty);
372 auto combined = prior_dirty;
373 combined.header |= changed_dirty.header;
374 combined.object_stream |= changed_dirty.object_stream;
375 combined.object_stream_header |= changed_dirty.object_stream_header;
376 combined.sprites |= changed_dirty.sprites;
377 combined.chests |= changed_dirty.chests;
378 combined.pot_items |= changed_dirty.pot_items;
379 combined.torches |= changed_dirty.torches;
380 combined.blocks |= changed_dirty.blocks;
381 combined.custom_collision |= changed_dirty.custom_collision;
382 combined.water_fill |= changed_dirty.water_fill;
383 combined.water_fill_sram_bit_mask = plan.after.water.sram_bit_mask;
384 combined.block_load_orders = changed_dirty.block_load_orders;
385 target.RestoreSaveDirtySnapshot(combined);
386 if (core::FeatureFlags::get().dungeon.kSaveRoomHeaders &&
387 target.header_dirty()) {
389 ValidateTransferHeaderOwnership(scratch, plan.target_room_id));
390 }
391 RETURN_IF_ERROR(shadow->SaveRoomImpl(plan.target_room_id, true));
392 if (changed_dirty.water_fill &&
393 target.water_fill_sram_bit_mask() != plan.after.water.sram_bit_mask) {
394 return absl::FailedPreconditionError(
395 "Water-fill state bits changed; preview again");
396 }
397 bool changed_other_mask = false;
398 rooms_.ForEachMaterialized([&](int id, const zelda3::Room& original) {
399 const auto* copy = shadow->rooms_.GetIfMaterialized(id);
400 if (id != plan.target_room_id && original.has_water_fill_zone() && copy &&
401 original.water_fill_sram_bit_mask() != copy->water_fill_sram_bit_mask())
402 changed_other_mask = true;
403 });
404 if (changed_other_mask) {
405 return absl::FailedPreconditionError(
406 "Replacement would reassign another room's water-fill state bit");
407 }
408 // Check the actual serialized changes as well as the save path's estimated
409 // ranges, covering shared torch/block tables and allocator metadata.
411 std::vector<std::pair<uint32_t, uint32_t>> ranges;
412 const auto& before = rom_->vector();
413 const auto& after = scratch.vector();
414 if (before.size() != after.size()) {
415 return absl::FailedPreconditionError(
416 "Room replacement cannot resize the ROM");
417 }
418 for (size_t i = 0; i < before.size();) {
419 if (before[i] == after[i]) {
420 ++i;
421 continue;
422 }
423 const size_t start = i++;
424 while (i < before.size() && before[i] != after[i])
425 ++i;
426 ranges.emplace_back(start, i);
427 }
431 "room clone/import", "DungeonEditorV2", nullptr));
432 }
433 return absl::OkStatus();
434}
435
437 const DungeonRoomTransferPlan& plan) {
438 auto validate = [&]() -> absl::Status {
439 const auto* target = rooms_.GetIfLoaded(plan.target_room_id);
440 if (plan.rom != rom_ || !target || target->rom() != rom_ ||
441 target->id() != plan.target_room_id ||
443 plan.before)) {
444 return absl::FailedPreconditionError(
445 "Room changed since preview; preview again");
446 }
447 if (plan.clone_source_room_id >= 0) {
448 const auto* source = rooms_.GetIfLoaded(plan.clone_source_room_id);
449 if (!source || source->rom() != rom_ || !plan.clone_source_before ||
451 *plan.clone_source_before)) {
452 return absl::FailedPreconditionError(
453 "Source room changed; preview again");
454 }
455 }
457 const auto checked,
458 PlanDungeonRoomTransfer(*target, plan.after, plan.options));
459 if (!SameDungeonRoomDocument(checked.after, plan.after)) {
460 return absl::InvalidArgumentError(
461 "Room preview does not match its copy options");
462 }
463 return absl::OkStatus();
464 };
465 RETURN_IF_ERROR(validate());
466 if (!plan.changed())
467 return absl::OkStatus();
470 RETURN_IF_ERROR(validate());
471 // Gesture finalization can alter shared-table occupancy in another room.
474 auto history = plan;
475 history.clone_source_before.reset();
476 undo_manager_.Push(std::make_unique<RoomTransferAction>(
477 std::move(history), [this, id = plan.target_room_id,
478 rom = rom_](const DungeonRoomDocument& state) {
479 if (rom_ != rom)
480 return absl::FailedPreconditionError(
481 "Room history belongs to another ROM");
482 return RestoreRoomTransfer(id, state);
483 }));
484 return absl::OkStatus();
485}
486
488 int room_id, const DungeonRoomDocument& document) {
489 auto* room = rooms_.GetIfLoaded(room_id);
490 if (!room || room->rom() != rom_ || room->id() != room_id) {
491 return absl::FailedPreconditionError(
492 "Room replacement target is not loaded");
493 }
494 ApplyDungeonRoomDocument(*room, document);
495 for (auto& object : room->GetTileObjects())
496 object.SetRom(rom_);
498 RefreshSelectionEditViews(room_id, &document.contents);
500 return absl::OkStatus();
501}
502
503} // namespace yaze::editor
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:49
absl::StatusOr< uint8_t > ReadByte(int offset) const
Definition rom.cc:785
const auto & vector() const
Definition rom.h:205
absl::StatusOr< uint16_t > ReadWord(int offset) const
Definition rom.cc:793
auto size() const
Definition rom.h:200
bool is_loaded() const
Definition rom.h:187
absl::StatusOr< uint32_t > ReadLong(int offset) const
Definition rom.cc:800
static Flags & get()
Definition features.h:119
bool loaded() const
Check if the manifest has been loaded.
absl::Status PreflightRoomTransfer(const DungeonRoomTransferPlan &plan)
absl::Status ApplyRoomTransfer(const DungeonRoomTransferPlan &plan)
util::LruCache< int, std::unique_ptr< DungeonCanvasViewer > > room_viewers_
void RefreshSelectionEditViews(int room_id, const DungeonSelectionEditState *selection)
std::unique_ptr< DungeonCanvasViewer > workbench_compare_viewer_
static bool IsValidRoomId(int room_id)
absl::StatusOr< std::string > ExportRoomDocument(int room_id)
absl::StatusOr< DungeonRoomTransferPlan > PreviewRoomTransfer(int target_room_id, int source_room_id, const std::string &json, const DungeonRoomTransferOptions &options)
std::unique_ptr< DungeonCanvasViewer > workbench_viewer_
absl::Status RestoreRoomTransfer(int room_id, const DungeonRoomDocument &document)
zelda3::Room * GetIfMaterialized(int room_id)
zelda3::Room * GetIfLoaded(int room_id)
UndoManager undo_manager_
Definition editor.h:371
EditorDependencies dependencies_
Definition editor.h:370
Abstract base for all undoable actions (Command pattern)
Definition undo_action.h:20
void Push(std::unique_ptr< UndoAction > action)
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
void EnsureSpritesLoaded()
Definition room.cc:1033
bool custom_collision_dirty() const
Definition room.h:596
bool IsLoaded() const
Definition room.h:970
bool pot_items_dirty() const
Definition room.h:431
bool object_stream_dirty() const
Definition room.h:504
bool sprites_dirty() const
Definition room.h:318
bool header_dirty() const
Definition room.h:658
bool object_stream_header_dirty() const
Definition room.h:506
bool has_water_fill_zone() const
Definition room.h:603
bool chests_dirty() const
Definition room.h:325
bool blocks_dirty() const
Definition room.h:438
bool torches_dirty() const
Definition room.h:435
SaveDirtySnapshot CaptureSaveDirtySnapshot() const
Definition room.h:673
bool water_fill_dirty() const
Definition room.h:654
bool AreTorchesLoaded() const
Definition room.h:976
bool AreBlocksLoaded() const
Definition room.h:982
uint8_t water_fill_sram_bit_mask() const
Definition room.h:644
struct chest_data chest_data
Legacy chest data structure.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
absl::Status AssignTransferredWaterMask(Rom &rom, const DungeonRoomStore &rooms, DungeonRoomTransferPlan &plan)
Editors are the view controllers for the application.
absl::Status ValidateHackManifestSaveConflicts(const core::HackManifest &manifest, project::RomWritePolicy write_policy, const std::vector< std::pair< uint32_t, uint32_t > > &ranges, absl::string_view save_scope, const char *log_tag, ToastManager *toast_manager)
absl::StatusOr< DungeonRoomTransferPlan > PlanDungeonRoomTransfer(const zelda3::Room &target, const DungeonRoomDocument &source, DungeonRoomTransferOptions options)
bool SameDungeonRoomDocument(const DungeonRoomDocument &a, const DungeonRoomDocument &b)
constexpr char kRoomTransferSharedHeaderPayload[]
absl::StatusOr< std::string > SerializeDungeonRoomDocument(const DungeonRoomDocument &document)
constexpr uint16_t kTransferWater
void ApplyDungeonRoomDocument(zelda3::Room &room, const DungeonRoomDocument &document)
absl::StatusOr< DungeonRoomDocument > ParseDungeonRoomDocument(const std::string &json)
DungeonRoomDocument CaptureDungeonRoomDocument(const zelda3::Room &room)
constexpr int kTorchesLengthPointer
absl::StatusOr< CustomCollisionMap > LoadCustomCollisionMap(Rom *rom, int room_id)
absl::StatusOr< DungeonStreamRecord > ReadDungeonPotItemStream(const Rom &rom, int room_id)
constexpr int kNumberOfRooms
constexpr int kRoomHeaderPointer
constexpr int kRoomHeaderPointerBank
constexpr int kCustomCollisionRoomPointers
constexpr int kTorchData
absl::StatusOr< std::vector< WaterFillZoneEntry > > LoadWaterFillTable(Rom *rom)
absl::StatusOr< DungeonStreamRecord > ReadDungeonSpriteStream(const Rom &rom, int room_id)
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
struct yaze::core::FeatureFlags::Flags::Dungeon dungeon
std::optional< DungeonRoomDocument > clone_source_before
project::YazeProject * project
Definition editor.h:174
RomWritePolicy write_policy
Definition project.h:225
core::HackManifest hack_manifest
Definition project.h:360
zelda3_version version
Definition game_data.h:84
Represents a door in a dungeon room.
Definition room.h:339