14#include "absl/status/status.h"
15#include "absl/strings/str_format.h"
16#include "absl/strings/string_view.h"
17#include "absl/types/span.h"
62#include "imgui/imgui.h"
83 return absl::FailedPreconditionError(
"ROM not loaded");
86 bool any_dirty =
false;
93 return absl::OkStatus();
96 std::vector<zelda3::WaterFillZoneEntry> zones;
98 for (
int room_id = 0; room_id < static_cast<int>(rooms.
size()); ++room_id) {
100 if (room ==
nullptr) {
108 if (tile_count <= 0) {
111 if (tile_count > 255) {
112 return absl::InvalidArgumentError(absl::StrFormat(
113 "Water fill zone in room 0x%02X has %d tiles (max 255)", room_id,
120 z.
fill_offsets.reserve(
static_cast<size_t>(tile_count));
123 for (
size_t i = 0; i < map.size(); ++i) {
128 zones.push_back(std::move(z));
131 if (zones.size() > 8) {
132 return absl::InvalidArgumentError(absl::StrFormat(
133 "Too many water fill zones: %zu (max 8 fits in $7EF411 bitfield)",
140 for (
const auto& z : zones) {
150 return absl::OkStatus();
156 std::vector<std::pair<uint32_t, uint32_t>> ranges;
157 if (rom ==
nullptr || !rom->
is_loaded()) {
161 bool any_dirty =
false;
174 if (layout !=
nullptr &&
176 for (
const auto& range : layout->allocation_regions) {
180 const uint32_t pointer_width =
184 const uint32_t pointer_table_pc =
yaze::SnesToPc(layout->pointer_table);
187 pointer_table_pc + layout->pointer_count * pointer_width);
192 const auto& rom_data = rom->
vector();
198 if (pointer_slot < 0 ||
199 pointer_slot + 1 >=
static_cast<int>(rom_data.size())) {
202 const uint16_t pointer =
203 (
static_cast<uint16_t
>(rom_data[pointer_slot + 1]) << 8) |
204 rom_data[pointer_slot];
205 if (pointer < 0x8000) {
209 const uint32_t encoded_size =
210 static_cast<uint32_t
>(room.
GetPotItems().size() * 3 + 2);
211 ranges.emplace_back(stream_pc, stream_pc + encoded_size);
216absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
221 rom,
static_cast<int>(rooms.
size()),
222 [&rooms](
int room_id) { return rooms.GetIfMaterialized(room_id); }));
228 std::vector<std::pair<uint32_t, uint32_t>> ranges;
229 if (rom ==
nullptr || !rom->
is_loaded() ||
233 bool any_dirty =
false;
246std::vector<std::pair<uint32_t, uint32_t>>
249 std::vector<std::pair<uint32_t, uint32_t>> ranges;
251 if (rom ==
nullptr || !rom->
is_loaded() ||
253 static_cast<int>(rom->
size()) ||
257 bool any_dirty =
false;
275 std::optional<zelda3::DungeonStreamLayout> repack_layout;
276 bool any_dirty =
false;
285 if (manifest_layout !=
nullptr &&
291 repack_layout = std::move(allocator_layout);
296 rom,
static_cast<int>(rooms.
size()),
297 [&rooms](
int room_id) { return rooms.GetIfMaterialized(room_id); },
298 repack_layout.has_value() ? &*repack_layout :
nullptr);
306 return absl::OkStatus();
309 if (ranges.empty()) {
310 return absl::OkStatus();
314 save_scope,
"DungeonEditorV2", toast_manager);
319 const std::array<zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots>&
321 const std::array<zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints>&
323 absl::string_view save_scope,
ToastManager* toast_manager) {
324 if (rom ==
nullptr || !rom->
is_loaded()) {
325 return absl::FailedPreconditionError(
"ROM not loaded");
337 return absl::OkStatus();
343 ranges.insert(ranges.end(), spawn_ranges.begin(), spawn_ranges.end());
344 if (ranges.empty()) {
345 return absl::OkStatus();
349 save_scope,
"DungeonEditorV2", toast_manager);
354 absl::string_view save_scope,
357 if (ranges.empty() || project ==
nullptr ||
359 return absl::OkStatus();
363 save_scope,
"DungeonEditorV2", toast_manager);
371 return absl::OkStatus();
374 if (ranges.empty()) {
375 return absl::OkStatus();
379 save_scope,
"DungeonEditorV2", toast_manager);
387 return absl::OkStatus();
390 if (ranges.empty()) {
391 return absl::OkStatus();
395 save_scope,
"DungeonEditorV2", toast_manager);
402 return absl::FailedPreconditionError(
403 "Dungeon save transaction is already active");
409 palette_manager.HasUnsavedChanges(
game_data_)) {
410 if (!palette_manager.IsManaging(
game_data_)) {
411 return absl::FailedPreconditionError(
412 "Cannot save dungeon palettes from a different ROM session");
421 for (
size_t i = 0; i <
entrances_.size(); ++i) {
432 return absl::OkStatus();
442 room->RestoreSaveDirtySnapshot(state);
445 for (
size_t i = 0; i <
entrances_.size(); ++i) {
482 const std::function<absl::Status()>& operation) {
484 return absl::FailedPreconditionError(
"ROM not loaded");
496 const absl::Status status = operation();
504 return absl::OkStatus();
509 return absl::FailedPreconditionError(
"ROM not loaded");
513 std::optional<zelda3::ChestSavePlan> chest_save_plan;
515 if (flags.kSaveCollision) {
520 if (flags.kSaveWaterFillZones) {
525 if (flags.kSaveEntrances) {
531 if (flags.kSaveChests) {
534 [
this](
int room_id) {
535 return rooms_.GetIfMaterialized(room_id);
540 chest_save_plan = std::move(plan);
542 if (flags.kSavePotItems) {
549 if (flags.kSavePalettes && palette_manager.HasUnsavedChanges(
game_data_)) {
550 if (!palette_manager.IsManaging(
game_data_)) {
551 return absl::FailedPreconditionError(
552 "Cannot save dungeon palettes from a different ROM session");
554 const size_t modified_color_count =
555 palette_manager.GetModifiedColorCount(
game_data_);
556 auto status = palette_manager.SaveAllToRom();
558 LOG_ERROR(
"DungeonEditorV2",
"Failed to save palette changes: %s",
559 status.message().data());
562 LOG_INFO(
"DungeonEditorV2",
"Saved %zu modified colors to ROM",
563 modified_color_count);
566 if (flags.kSaveObjects || flags.kSaveSprites || flags.kSaveRoomHeaders) {
567 absl::Status save_status = absl::OkStatus();
569 if (!save_status.ok()) {
574 save_status = status;
577 if (!save_status.ok()) {
582 if (flags.kSaveTorches) {
585 [
this](
int room_id) { return rooms_.GetIfMaterialized(room_id); });
587 LOG_ERROR(
"DungeonEditorV2",
"Failed to save torches: %s",
588 status.message().data());
593 if (flags.kSavePits) {
597 LOG_ERROR(
"DungeonEditorV2",
"Failed to save pits: %s",
598 status.message().data());
603 if (flags.kSaveBlocks) {
606 [
this](
int room_id) { return rooms_.GetIfMaterialized(room_id); });
608 LOG_ERROR(
"DungeonEditorV2",
"Failed to save blocks: %s",
609 status.message().data());
614 if (flags.kSaveCollision) {
617 [
this](
int room_id) { return rooms_.GetIfMaterialized(room_id); });
619 LOG_ERROR(
"DungeonEditorV2",
"Failed to save collision: %s",
620 status.message().data());
625 if (flags.kSaveWaterFillZones) {
626 auto status = SaveWaterFillZones(
rom_,
rooms_);
628 LOG_ERROR(
"DungeonEditorV2",
"Failed to save water fill zones: %s",
629 status.message().data());
634 if (flags.kSaveChests) {
636 rom_, *chest_save_plan,
639 LOG_ERROR(
"DungeonEditorV2",
"Failed to save chests: %s",
640 status.message().data());
645 if (flags.kSavePotItems) {
649 LOG_ERROR(
"DungeonEditorV2",
"Failed to save pot items: %s",
650 status.message().data());
655 if (flags.kSaveEntrances) {
658 LOG_ERROR(
"DungeonEditorV2",
"Failed to save dungeon entrances: %s",
659 status.message().data());
664 LOG_ERROR(
"DungeonEditorV2",
"Failed to save dungeon spawn points: %s",
665 status.message().data());
670 return absl::OkStatus();
675 std::vector<std::pair<uint32_t, uint32_t>> ranges;
685 if (flags.kSavePalettes) {
686 auto palette_ranges =
687 palette_manager.GetModifiedColorWriteRanges(
game_data_);
688 ranges.insert(ranges.end(), palette_ranges.begin(), palette_ranges.end());
691 if (flags.kSaveEntrances) {
692 auto entrance_ranges =
694 ranges.insert(ranges.end(), entrance_ranges.begin(), entrance_ranges.end());
697 ranges.insert(ranges.end(), spawn_ranges.begin(), spawn_ranges.end());
701 bool stream_dirty,
int room_id) {
709 if (layout ==
nullptr ||
712 static_cast<uint32_t
>(room_id) >= layout->pointer_count) {
716 for (
const auto& range : layout->allocation_regions) {
720 const uint32_t pointer_width =
723 const uint32_t pointer_slot =
725 static_cast<uint32_t
>(room_id) * pointer_width;
726 ranges.emplace_back(pointer_slot, pointer_slot + pointer_width);
728 const uint32_t door_slot =
730 ranges.emplace_back(door_slot, door_slot + 3u);
738 if (flags.kSaveWaterFillZones) {
739 auto water_fill_ranges = CollectDirtyWaterFillWriteRanges(
rom_,
rooms_);
740 ranges.insert(ranges.end(), water_fill_ranges.begin(),
741 water_fill_ranges.end());
747 if (flags.kSaveCollision) {
748 auto collision_ranges =
749 CollectDirtyCustomCollisionWriteRanges(
rom_,
rooms_);
750 ranges.insert(ranges.end(), collision_ranges.begin(),
751 collision_ranges.end());
757 if (flags.kSaveChests) {
758 auto chest_ranges = CollectDirtyChestWriteRanges(
rom_,
rooms_);
759 if (chest_ranges.ok()) {
760 ranges.insert(ranges.end(), chest_ranges->begin(), chest_ranges->end());
762 LOG_WARN(
"DungeonEditorV2",
"Could not predict chest write ranges: %s",
763 chest_ranges.status().message().data());
772 if (zelda3::kRoomHeaderPointer + 2 < static_cast<int>(rom_data.size())) {
773 int header_ptr_table =
774 (rom_data[zelda3::kRoomHeaderPointer + 2] << 16) |
775 (rom_data[zelda3::kRoomHeaderPointer + 1] << 8) |
776 rom_data[zelda3::kRoomHeaderPointer];
777 header_ptr_table = yaze::SnesToPc(header_ptr_table);
778 int table_offset = header_ptr_table + (room_id * 2);
780 if (table_offset + 1 < static_cast<int>(rom_data.size())) {
781 int address = (rom_data[zelda3::kRoomHeaderPointerBank] << 16) |
782 (rom_data[table_offset + 1] << 8) |
783 rom_data[table_offset];
784 int header_location = yaze::SnesToPc(address);
785 ranges.emplace_back(header_location, header_location + 14);
789 if (message_address >= 0 &&
790 message_address + 1 <
static_cast<int>(rom_data.size())) {
791 ranges.emplace_back(message_address, message_address + 2);
797 const uint32_t door_slot =
799 ranges.emplace_back(door_slot, door_slot + 3u);
805 int entry_offset = obj_ptr_table + (room_id * 3);
807 if (entry_offset + 2 <
static_cast<int>(rom_data.size())) {
808 int tile_addr = (rom_data[entry_offset + 2] << 16) |
809 (rom_data[entry_offset + 1] << 8) |
810 rom_data[entry_offset];
814 ranges.emplace_back(objects_location,
815 objects_location + encoded.size() + 2);
822 const uint32_t table_snes =
828 if ((table_snes & 0xFFFFu) >= 0x8000u) {
830 const int pointer_slot = table_pc + room_id * 3;
831 if (pointer_slot >= 0 &&
832 pointer_slot + 2 <
static_cast<int>(rom_data.size())) {
833 const uint32_t stream_snes =
834 (
static_cast<uint32_t
>(rom_data[pointer_slot + 2]) << 16) |
835 (
static_cast<uint32_t
>(rom_data[pointer_slot + 1]) << 8) |
836 rom_data[pointer_slot];
837 if ((stream_snes & 0xFFFFu) >= 0x8000u) {
839 if (
static_cast<uint64_t
>(stream_pc) + 2u <= rom_data.size()) {
840 ranges.emplace_back(stream_pc, stream_pc + 2u);
850 const uint32_t table_snes =
856 const int pointer_slot = table_pc + room_id * 2;
857 if (pointer_slot >= 0 &&
858 pointer_slot + 1 <
static_cast<int>(rom_data.size())) {
859 const uint16_t pointer =
860 (
static_cast<uint16_t
>(rom_data[pointer_slot + 1]) << 8) |
861 rom_data[pointer_slot];
862 if (pointer >= 0x8000) {
864 ranges.emplace_back(stream_pc,
870 append_declared_cow_ranges(
872 flags.kSaveObjects &&
882 if (flags.kSavePotItems) {
884 CollectDirtyPotItemWriteRanges(dependencies_.project, rom_, rooms_);
885 ranges.insert(ranges.end(), pot_ranges.begin(), pot_ranges.end());
891absl::Status DungeonEditorV2::SaveRoom(
int room_id) {
892 return RunWithSaveTransaction([
this, room_id]() -> absl::Status {
893 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size())) {
894 return absl::InvalidArgumentError(
"Invalid room ID");
898 std::optional<zelda3::ChestSavePlan> chest_save_plan;
899 if (flags.kSaveCollision) {
901 dependencies_.project, rom_, rooms_,
902 absl::StrFormat(
"custom collision for room 0x%03X", room_id),
903 dependencies_.toast_manager));
905 if (flags.kSaveWaterFillZones) {
907 dependencies_.project, rom_, rooms_,
908 absl::StrFormat(
"water fill zones for room 0x%03X", room_id),
909 dependencies_.toast_manager));
911 if (flags.kSaveEntrances) {
913 dependencies_.project, rom_, entrances_, spawn_points_,
915 "dungeon entrances and spawn points (Apply Room 0x%03X)",
917 dependencies_.toast_manager));
919 if (flags.kSaveChests) {
922 rom_,
static_cast<int>(rooms_.size()), [
this](
int id) {
923 return rooms_.GetIfMaterialized(id);
926 dependencies_.project, plan,
927 absl::StrFormat(
"chest table for room 0x%03X", room_id),
928 dependencies_.toast_manager));
929 chest_save_plan = std::move(plan);
931 if (flags.kSavePotItems) {
933 dependencies_.project, rom_, rooms_,
934 absl::StrFormat(
"pot items for room 0x%03X", room_id),
935 dependencies_.toast_manager));
938 if (flags.kSavePalettes && palette_manager.HasUnsavedChanges(game_data_)) {
939 if (!palette_manager.IsManaging(game_data_)) {
940 return absl::FailedPreconditionError(
941 "Cannot save dungeon palettes from a different ROM session");
943 auto status = palette_manager.SaveAllToRom();
945 LOG_ERROR(
"DungeonEditorV2",
"Failed to save palette changes: %s",
946 status.message().data());
950 if (flags.kSaveObjects || flags.kSaveSprites || flags.kSaveRoomHeaders) {
954 if (flags.kSaveTorches) {
956 rom_,
static_cast<int>(rooms_.size()),
957 [
this](
int id) { return rooms_.GetIfMaterialized(id); }));
959 if (flags.kSavePits) {
961 rom_, game_data_ ? &game_data_->pit_damage_table :
nullptr));
963 if (flags.kSaveBlocks) {
965 rom_,
static_cast<int>(rooms_.size()),
966 [
this](
int id) { return rooms_.GetIfMaterialized(id); }));
968 if (flags.kSaveCollision) {
970 rom_,
static_cast<int>(rooms_.size()),
971 [
this](
int id) { return rooms_.GetIfMaterialized(id); }));
973 if (flags.kSaveWaterFillZones) {
976 if (flags.kSaveChests) {
978 rom_, *chest_save_plan,
979 [
this](
int id) {
return rooms_.GetIfMaterialized(
id); }));
981 if (flags.kSavePotItems) {
983 SaveAllPotItemsForProject(dependencies_.project, rom_, rooms_));
985 if (flags.kSaveEntrances) {
990 return absl::OkStatus();
994absl::Status DungeonEditorV2::SaveRoomData(
int room_id) {
995 if (!rom_ || !rom_->is_loaded()) {
996 return absl::FailedPreconditionError(
"ROM not loaded");
998 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size())) {
999 return absl::InvalidArgumentError(
"Invalid room ID");
1002 auto& room = rooms_[room_id];
1003 if (!room.IsLoaded()) {
1004 return absl::OkStatus();
1011 for (
const auto& w : result.warnings) {
1012 LOG_WARN(
"DungeonEditorV2",
"Room 0x%03X validation warning: %s", room_id,
1015 if (!result.is_valid) {
1016 for (
const auto& e : result.errors) {
1017 LOG_ERROR(
"DungeonEditorV2",
"Room 0x%03X validation error: %s",
1018 room_id, e.c_str());
1020 if (dependencies_.toast_manager) {
1021 dependencies_.toast_manager->Show(
1023 "Save blocked: room 0x%03X failed validation (%zu error(s))",
1024 room_id, result.errors.size()),
1027 return absl::FailedPreconditionError(
1028 absl::StrFormat(
"Room 0x%03X failed validation", room_id));
1037 std::optional<zelda3::DungeonStreamLayout> object_cow_layout;
1038 std::optional<zelda3::DungeonStreamLayout> sprite_cow_layout;
1039 if (dependencies_.project && dependencies_.project->hack_manifest.loaded()) {
1040 const auto& manifest = dependencies_.project->hack_manifest;
1041 auto load_cow_layout =
1043 std::optional<zelda3::DungeonStreamLayout>* out) -> absl::Status {
1044 if (!stream_dirty) {
1045 return absl::OkStatus();
1047 const auto* manifest_layout =
1048 manifest.GetDungeonStreamLayout(stream_type);
1049 if (manifest_layout ==
nullptr ||
1050 manifest_layout->strategy !=
1052 return absl::OkStatus();
1057 *out = std::move(allocator_layout);
1058 return absl::OkStatus();
1063 flags.kSaveObjects &&
1064 (room.object_stream_dirty() || room.object_stream_header_dirty()),
1065 &object_cow_layout));
1067 flags.kSaveSprites && room.sprites_dirty(),
1068 &sprite_cow_layout));
1072 if (dependencies_.project && dependencies_.project->hack_manifest.loaded()) {
1073 std::vector<std::pair<uint32_t, uint32_t>> ranges;
1074 const auto& manifest = dependencies_.project->hack_manifest;
1075 const auto& rom_data = rom_->vector();
1078 if (flags.kSaveRoomHeaders && room.header_dirty()) {
1080 int header_ptr_table =
1085 int table_offset = header_ptr_table + (room_id * 2);
1087 if (table_offset + 1 <
static_cast<int>(rom_data.size())) {
1089 (rom_data[table_offset + 1] << 8) |
1090 rom_data[table_offset];
1092 ranges.emplace_back(header_location, header_location + 14);
1096 if (message_address >= 0 &&
1097 message_address + 1 <
static_cast<int>(rom_data.size())) {
1098 ranges.emplace_back(message_address, message_address + 2);
1103 if (flags.kSaveObjects && room.object_stream_dirty()) {
1104 const uint32_t door_slot =
1106 ranges.emplace_back(door_slot, door_slot + 3u);
1112 int entry_offset = obj_ptr_table + (room_id * 3);
1114 if (entry_offset + 2 <
static_cast<int>(rom_data.size())) {
1115 int tile_addr = (rom_data[entry_offset + 2] << 16) |
1116 (rom_data[entry_offset + 1] << 8) |
1117 rom_data[entry_offset];
1124 auto encoded = room.EncodeObjects();
1125 ranges.emplace_back(objects_location,
1126 objects_location + encoded.size() + 2);
1131 if (flags.kSaveObjects && room.object_stream_header_dirty() &&
1133 const uint32_t table_snes =
1139 if ((table_snes & 0xFFFFu) >= 0x8000u) {
1141 const int pointer_slot = table_pc + room_id * 3;
1142 if (pointer_slot >= 0 &&
1143 pointer_slot + 2 <
static_cast<int>(rom_data.size())) {
1144 const uint32_t stream_snes =
1145 (
static_cast<uint32_t
>(rom_data[pointer_slot + 2]) << 16) |
1146 (
static_cast<uint32_t
>(rom_data[pointer_slot + 1]) << 8) |
1147 rom_data[pointer_slot];
1148 if ((stream_snes & 0xFFFFu) >= 0x8000u) {
1150 if (
static_cast<uint64_t
>(stream_pc) + 2u <= rom_data.size()) {
1151 ranges.emplace_back(stream_pc, stream_pc + 2u);
1159 if (flags.kSaveSprites && room.sprites_dirty() &&
1161 const uint32_t table_snes =
1167 const int pointer_slot = table_pc + room_id * 2;
1168 if (pointer_slot >= 0 &&
1169 pointer_slot + 1 <
static_cast<int>(rom_data.size())) {
1170 const uint16_t pointer =
1171 (
static_cast<uint16_t
>(rom_data[pointer_slot + 1]) << 8) |
1172 rom_data[pointer_slot];
1173 if (pointer >= 0x8000) {
1175 ranges.emplace_back(stream_pc,
1176 stream_pc + room.EncodeSprites().size() + 1);
1186 for (
const auto& range : layout.allocation_ranges) {
1187 ranges.emplace_back(range.begin, range.end);
1189 const uint32_t pointer_width =
1193 const uint32_t pointer_slot =
1194 layout.pointer_table_pc +
1195 static_cast<uint32_t
>(room_id) * pointer_width;
1196 ranges.emplace_back(pointer_slot, pointer_slot + pointer_width);
1198 const uint32_t door_slot =
1200 ranges.emplace_back(door_slot, door_slot + 3u);
1203 if (object_cow_layout.has_value()) {
1204 append_cow_ranges(*object_cow_layout);
1206 if (sprite_cow_layout.has_value()) {
1207 append_cow_ranges(*sprite_cow_layout);
1212 const auto write_policy = dependencies_.project->rom_metadata.write_policy;
1214 manifest, write_policy, ranges, absl::StrFormat(
"room 0x%03X", room_id),
1215 "DungeonEditorV2", dependencies_.toast_manager));
1218 if (flags.kSaveObjects && room.object_stream_dirty()) {
1219 auto status = room.SaveObjects(
1220 object_cow_layout.has_value() ? &*object_cow_layout :
nullptr);
1222 LOG_ERROR(
"DungeonEditorV2",
"Failed to save room objects: %s",
1223 status.message().data());
1228 if (flags.kSaveObjects && room.object_stream_header_dirty()) {
1229 auto status = room.SaveObjectStreamHeader(
1230 object_cow_layout.has_value() ? &*object_cow_layout :
nullptr);
1233 "Failed to save room object-stream header: %s",
1234 status.message().data());
1239 if (flags.kSaveSprites && room.sprites_dirty()) {
1240 auto status = room.SaveSprites(
1241 sprite_cow_layout.has_value() ? &*sprite_cow_layout :
nullptr);
1243 LOG_ERROR(
"DungeonEditorV2",
"Failed to save room sprites: %s",
1244 status.message().data());
1249 if (flags.kSaveRoomHeaders && room.header_dirty()) {
1250 auto status = room.SaveRoomHeader();
1252 LOG_ERROR(
"DungeonEditorV2",
"Failed to save room header: %s",
1253 status.message().data());
1258 return absl::OkStatus();
1261void DungeonEditorV2::SaveAllRooms() {
1262 auto status = RunWithSaveTransaction([
this]() {
return Save(); });
1264 if (dependencies_.toast_manager) {
1265 dependencies_.toast_manager->Show(
"Applied loaded rooms to ROM buffer",
1266 ToastType::kSuccess);
1269 LOG_ERROR(
"DungeonEditorV2",
"SaveAllRooms failed: %s",
1270 status.message().data());
1271 if (dependencies_.toast_manager) {
1272 dependencies_.toast_manager->Show(
1273 absl::StrFormat(
"Save all failed: %s", status.message()),
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
const auto & vector() const
const DungeonStreamLayout * GetDungeonStreamLayout(DungeonStreamType stream) const
Get an explicitly declared dungeon stream layout.
bool loaded() const
Check if the manifest has been loaded.
void CommitSaveTransaction() override
absl::Status SaveRoomData(int room_id)
absl::Status Save() override
std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > entrances_
void RollbackSaveTransaction() override
absl::Status BeginSaveTransaction() override
absl::Status RunWithSaveTransaction(const std::function< absl::Status()> &operation)
std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > spawn_points_
std::optional< SaveTransactionSnapshot > save_transaction_snapshot_
zelda3::GameData * game_data_
std::vector< std::pair< uint32_t, uint32_t > > CollectWriteRanges() const
zelda3::Room * GetIfMaterialized(int room_id)
void ForEachMaterialized(Fn &&fn)
void ForEachLoaded(Fn &&fn)
EditorDependencies dependencies_
void RollbackSaveTransaction()
void CommitSaveTransaction()
static PaletteManager & Get()
Get the singleton instance.
ValidationResult ValidateRoom(const Room &room)
void set_water_fill_sram_bit_mask(uint8_t mask)
bool custom_collision_dirty() const
const WaterFillZoneMap & water_fill_zone() const
int WaterFillTileCount() const
bool pot_items_dirty() const
bool object_stream_dirty() const
bool sprites_dirty() const
bool header_dirty() const
bool object_stream_header_dirty() const
bool has_water_fill_zone() const
std::vector< uint8_t > EncodeObjects() const
void ClearWaterFillDirty()
std::vector< uint8_t > EncodeSprites() const
SaveDirtySnapshot CaptureSaveDirtySnapshot() const
bool water_fill_dirty() const
const std::vector< PotItem > & GetPotItems() const
uint8_t water_fill_sram_bit_mask() const
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
DungeonStreamType
Dungeon stream kinds with allocator layouts in the hack manifest.
absl::StatusOr< zelda3::DungeonStreamLayout > ToDungeonStreamAllocatorLayout(DungeonStreamType stream_type, const DungeonStreamLayout &manifest_layout)
absl::Status ValidatePotItemManifestConflicts(const project::YazeProject *project, Rom *rom, const DungeonRoomStore &rooms, absl::string_view save_scope, ToastManager *toast_manager)
std::vector< std::pair< uint32_t, uint32_t > > CollectDirtyWaterFillWriteRanges(const Rom *rom, const DungeonRoomStore &rooms)
absl::Status ValidateCustomCollisionManifestConflicts(const project::YazeProject *project, const Rom *rom, const DungeonRoomStore &rooms, absl::string_view save_scope, ToastManager *toast_manager)
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > CollectDirtyChestWriteRanges(const Rom *rom, const DungeonRoomStore &rooms)
std::vector< std::pair< uint32_t, uint32_t > > CollectDirtyPotItemWriteRanges(const project::YazeProject *project, const Rom *rom, const DungeonRoomStore &rooms)
absl::Status SaveAllPotItemsForProject(const project::YazeProject *project, Rom *rom, DungeonRoomStore &rooms)
absl::Status ValidateDungeonEntranceSavePreflight(const project::YazeProject *project, Rom *rom, const std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > &entrances, const std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > &spawn_points, absl::string_view save_scope, ToastManager *toast_manager)
std::vector< std::pair< uint32_t, uint32_t > > CollectDirtyCustomCollisionWriteRanges(const Rom *rom, const DungeonRoomStore &rooms)
absl::Status ValidateChestManifestConflicts(const project::YazeProject *project, const zelda3::ChestSavePlan &plan, absl::string_view save_scope, ToastManager *toast_manager)
absl::Status SaveWaterFillZones(Rom *rom, DungeonRoomStore &rooms)
absl::Status ValidateWaterFillManifestConflicts(const project::YazeProject *project, const Rom *rom, const DungeonRoomStore &rooms, absl::string_view save_scope, ToastManager *toast_manager)
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)
constexpr int kDoorPointers
constexpr int kWaterFillTableEnd
constexpr int kCustomCollisionDataSoftEnd
absl::StatusOr< ChestSavePlan > BuildChestSavePlan(const Rom *rom, int room_count, const std::function< const Room *(int)> &room_lookup)
constexpr int kWaterFillTableStart
constexpr int kMessagesIdDungeon
absl::Status NormalizeWaterFillZoneMasks(std::vector< WaterFillZoneEntry > *zones)
absl::Status SaveAllPotItems(Rom *rom, absl::Span< const Room > rooms)
std::vector< DungeonEntranceWriteRange > CollectDirtyRegularDungeonEntranceWriteRanges(const std::array< RoomEntrance, kNumDungeonEntranceSlots > &entrances)
absl::Status SaveAllDungeonSpawnPoints(Rom *rom, std::array< DungeonSpawnPoint, kNumDungeonSpawnPoints > &spawn_points, bool dirty_only=true)
absl::Status SaveAllTorches(Rom *rom, absl::Span< const Room > rooms)
constexpr int kRoomsSpritePointer
absl::Status SaveAllPits(Rom *rom)
absl::Status SaveAllBlocks(Rom *rom)
constexpr int kRoomItemsPointers
constexpr int kCustomCollisionDataPosition
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)
absl::Status ValidateRegularDungeonEntrancesForSave(const Rom &rom, const std::array< RoomEntrance, kNumDungeonEntranceSlots > &entrances, bool dirty_only=true)
constexpr int kNumberOfRooms
absl::Status ApplyChestSavePlan(Rom *rom, const ChestSavePlan &plan, const std::function< const Room *(int)> &room_lookup)
constexpr int kRoomHeaderPointer
constexpr int kRoomHeaderPointerBank
constexpr int kCustomCollisionRoomPointers
absl::Status SaveAllCollision(Rom *rom, absl::Span< Room > rooms)
absl::Status ValidateDungeonSpawnPointsForSave(const Rom &rom, const std::array< DungeonSpawnPoint, kNumDungeonSpawnPoints > &spawn_points, bool dirty_only=true)
constexpr int kRoomObjectPointer
std::vector< DungeonSpawnPointWriteRange > CollectDirtyDungeonSpawnPointWriteRanges(const std::array< DungeonSpawnPoint, kNumDungeonSpawnPoints > &spawn_points)
absl::Status WriteWaterFillTable(Rom *rom, const std::vector< WaterFillZoneEntry > &zones)
uint32_t SnesToPc(uint32_t addr) noexcept
#define RETURN_IF_ERROR(expr)
DungeonPointerEncoding pointer_encoding
struct yaze::core::FeatureFlags::Flags::Dungeon dungeon
bool has_palette_transaction
std::vector< std::pair< int, zelda3::Room::SaveDirtySnapshot > > room_states
std::array< bool, zelda3::kNumDungeonSpawnPoints > spawn_dirty_states
bool has_pit_damage_table
std::array< bool, zelda3::kNumDungeonEntranceSlots > entrance_dirty_states
project::YazeProject * project
ToastManager * toast_manager
Modern project structure with comprehensive settings consolidation.
core::HackManifest hack_manifest
std::vector< std::pair< uint32_t, uint32_t > > write_ranges() const
PitDamageTable pit_damage_table
std::vector< uint16_t > fill_offsets
std::array< uint8_t, 64 *64 > tiles