yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
editor_manager_project_io.cc
Go to the documentation of this file.
1// ROM/project I/O and its callback wiring. Method bodies are kept with the
2// qualified editor implementation; editor_manager.cc owns the remaining shell.
4
5#include <algorithm>
6#include <cstdint>
7#include <exception>
8#include <filesystem>
9#include <fstream>
10#include <initializer_list>
11#include <optional>
12#include <string>
13#include <utility>
14#include <vector>
15
16#ifdef __APPLE__
17#include <TargetConditionals.h>
18#endif
19
20#include "absl/status/status.h"
21#include "absl/strings/match.h"
22#include "absl/strings/str_format.h"
23#include "imgui/imgui.h"
24
26#include "app/editor/editor.h"
41#include "app/platform/timing.h"
42#include "core/features.h"
43#include "core/project.h"
44#include "core/rom_settings.h"
46#include "rom/rom.h"
47#include "rom/rom_diff.h"
48#include "rom/transaction.h"
49#include "util/file_util.h"
50#include "util/log.h"
51#include "util/macro.h"
54#include "zelda3/game_data.h"
58
59#ifdef YAZE_ENABLE_TESTING
61#endif
62
63namespace yaze::editor {
64namespace {
66 public:
70 delete;
71
73 if (!committed_) {
74 for (auto it = editors_.rbegin(); it != editors_.rend(); ++it) {
75 (*it)->RollbackSaveTransaction();
76 }
77 }
78 }
79
80 absl::Status Begin(Editor* editor) {
81 if (editor == nullptr) {
82 return absl::OkStatus();
83 }
85 editors_.push_back(editor);
86 return absl::OkStatus();
87 }
88
89 void Commit() {
90 for (Editor* editor : editors_) {
91 editor->CommitSaveTransaction();
92 }
93 committed_ = true;
94 }
95
96 private:
97 std::vector<Editor*> editors_;
98 bool committed_ = false;
99};
100
102 std::initializer_list<const char*> keys) {
103 for (const char* key : keys) {
104 if (overrides.addresses.find(key) != overrides.addresses.end()) {
105 return true;
106 }
107 }
108 return false;
109}
110
112 return !project.custom_objects_folder.empty() ||
113 !project.custom_object_files.empty();
114}
115
116constexpr int kTrackCustomObjectId = 0x31;
117
119 std::string* warning) {
120 if (project == nullptr) {
121 return false;
122 }
123 if (project->custom_objects_folder.empty()) {
124 return false;
125 }
126 if (project->custom_object_files.find(kTrackCustomObjectId) !=
127 project->custom_object_files.end()) {
128 return false;
129 }
130
131 const auto& defaults =
134 if (defaults.empty()) {
135 return false;
136 }
137
138 project->custom_object_files[kTrackCustomObjectId] = defaults;
139 if (warning != nullptr) {
140 *warning = absl::StrFormat(
141 "Project defines custom_objects_folder but is missing "
142 "custom_object_files[0x%02X]. Seeded default mapping (%d entries).",
143 kTrackCustomObjectId, static_cast<int>(defaults.size()));
144 }
145 return true;
146}
147
148std::vector<std::string> ValidateRomAddressOverrides(
149 const core::RomAddressOverrides& overrides, const Rom& rom) {
150 std::vector<std::string> warnings;
151 if (overrides.addresses.empty()) {
152 return warnings;
153 }
154
155 const auto rom_size = rom.size();
156 auto warn = [&](const std::string& message) {
157 warnings.push_back(message);
158 };
159
160 auto check_range = [&](const std::string& label, uint32_t addr, size_t span) {
161 const size_t addr_size = static_cast<size_t>(addr);
162 if (addr_size >= rom_size || addr_size + span > rom_size) {
163 warn(absl::StrFormat("ROM override '%s' out of range: 0x%X (size 0x%X)",
164 label, addr, rom_size));
165 }
166 };
167
168 for (const auto& [key, value] : overrides.addresses) {
169 check_range(key, value, 1);
170 }
171
174 // Defaults match the Oracle expanded message bank ($2F8000-$2FFFFF).
175 // Kept local to avoid pulling message editor constants into core validation.
176 constexpr uint32_t kExpandedMessageStartDefault = 0x178000;
177 constexpr uint32_t kExpandedMessageEndDefault = 0x17FFFF;
178 const uint32_t start =
180 .value_or(kExpandedMessageStartDefault);
181 const uint32_t end =
183 .value_or(kExpandedMessageEndDefault);
184 if (start >= rom_size || end >= rom_size) {
185 warn(absl::StrFormat(
186 "Expanded message range out of ROM bounds: 0x%X-0x%X", start, end));
187 } else if (end < start) {
188 warn(absl::StrFormat("Expanded message range invalid: 0x%X-0x%X", start,
189 end));
190 }
191 }
192
193 if (auto hook =
195 check_range(core::RomAddressKey::kExpandedMusicHook, *hook, 4);
196 if (*hook < rom_size) {
197 auto opcode = rom.ReadByte(*hook);
198 if (opcode.ok() && opcode.value() != 0x22) {
199 warn(absl::StrFormat(
200 "Expanded music hook at 0x%X is not a JSL opcode (0x%02X)", *hook,
201 opcode.value()));
202 }
203 }
204 }
205
206 if (auto main =
209 }
210
211 if (auto aux = overrides.GetAddress(core::RomAddressKey::kExpandedMusicAux)) {
212 check_range(core::RomAddressKey::kExpandedMusicAux, *aux, 4);
213 }
214
215 if (HasAnyOverride(overrides,
220 const uint32_t marker =
223 const uint32_t magic =
226 check_range("overworld_ptr_marker", marker, 1);
227 if (marker < rom_size) {
228 if (rom.data()[marker] != static_cast<uint8_t>(magic)) {
229 warn(absl::StrFormat(
230 "Overworld expanded pointer marker mismatch at 0x%X: expected "
231 "0x%02X, found 0x%02X",
232 marker, static_cast<uint8_t>(magic), rom.data()[marker]));
233 }
234 }
235 }
236
237 if (HasAnyOverride(overrides,
242 const uint32_t flag_addr =
243 overrides
246 check_range("overworld_entrance_flag_expanded", flag_addr, 1);
247 if (flag_addr < rom_size && rom.data()[flag_addr] == 0xB8) {
248 warn(
249 absl::StrFormat("Overworld entrance flag at 0x%X is still 0xB8 "
250 "(vanilla); expanded entrance tables may be ignored",
251 flag_addr));
252 }
253 }
254
255 return warnings;
256}
257
258} // namespace
259
261 // Initialize ROM load options dialog callbacks
263 [this](const RomLoadOptionsDialog::LoadOptions& options) {
264 // Apply feature flags from dialog
265 auto& flags = core::FeatureFlags::get();
266 flags.overworld.kSaveOverworldMaps = options.save_overworld_maps;
267 flags.overworld.kSaveOverworldEntrances =
269 flags.overworld.kSaveOverworldExits = options.save_overworld_exits;
270 flags.overworld.kSaveOverworldItems = options.save_overworld_items;
271 flags.overworld.kLoadCustomOverworld = options.enable_custom_overworld;
272 flags.kSaveDungeonMaps = options.save_dungeon_maps;
273 flags.kSaveAllPalettes = options.save_all_palettes;
274 flags.kSaveGfxGroups = options.save_gfx_groups;
276 if (auto* session = session_coordinator_->GetActiveRomSession()) {
277 session->feature_flags = flags;
278 }
279 }
280
281 // Create project if requested
282 if (options.create_project && !options.project_name.empty()) {
283 auto status =
285 if (!status.ok()) {
286 toast_manager_.Show(absl::StrFormat("Failed to create project: %s",
287 status.message()),
289 } else {
290 toast_manager_.Show("Project created: " + options.project_name,
292 }
293 }
294
295 // Close dialog, then pick the post-load surface again: the options
296 // may have created a project, but an open editor still wins.
299
300 LOG_INFO("EditorManager", "ROM load options applied: preset=%s",
301 options.selected_preset.c_str());
302 });
303}
304
306 // Utility callbacks removed - now handled via EventBus
307
309 [this](bool visible, bool expanded) {
310 user_settings_.prefs().sidebar_visible = visible;
311 user_settings_.prefs().sidebar_panel_expanded = expanded;
312 settings_dirty_ = true;
314 });
317 settings_dirty_ = true;
319 });
321 [this](float width) {
323 settings_dirty_ = true;
325 });
326
328 const std::string& category) {
329 if (category.empty() ||
331 return;
332 }
335
336 const auto& prefs = user_settings_.prefs();
337 auto it = prefs.panel_visibility_state.find(category);
338 if (it != prefs.panel_visibility_state.end()) {
340 window_manager_.GetActiveSessionId(), it->second);
341 } else {
342 // No saved visibility state for this category yet.
343 //
344 // Apply LayoutPresets defaults only when this category has *no*
345 // visible panels (editors may have already shown their own defaults,
346 // e.g. Dungeon Workbench).
347 const size_t session_id = window_manager_.GetActiveSessionId();
348 bool any_visible = false;
349 for (const auto& desc :
350 window_manager_.GetWindowsInCategory(session_id, category)) {
351 if (desc.visibility_flag && *desc.visibility_flag) {
352 any_visible = true;
353 break;
354 }
355 }
356
357 if (!any_visible) {
358 const EditorType type =
360 for (const auto& window_id : LayoutPresets::GetDefaultWindows(type)) {
361 window_manager_.OpenWindow(session_id, window_id);
362 }
363 }
364 }
365
366 settings_dirty_ = true;
368 });
369
371 [this](const std::string& category) -> Editor* {
372 Editor* editor = ResolveEditorForCategory(category);
374 return editor;
375 });
376
378 [this](const std::string& category) {
380 if (type != EditorType::kSettings && type != EditorType::kUnknown) {
381 SwitchToEditor(type, true);
382 }
383 });
384
386 [this](const std::string& category) {
387 if (ui_coordinator_) {
388 ui_coordinator_->SetStartupSurface(StartupSurface::kEditor);
389 }
390
391 if (category == "Agent") {
392#ifdef YAZE_BUILD_AGENT_UI
393 ShowAIAgent();
394#endif
395 return;
396 }
397
399 if (type != EditorType::kSettings) {
400 SwitchToEditor(type, true);
401 }
402 });
403
405
407 [this](const std::string& category, const std::string& path) {
408 if (category == "Assembly") {
409 if (auto* editor_set = GetCurrentEditorSet()) {
410 editor_set->ChangeActiveAssemblyFile(path);
412 }
413 }
414 });
415}
416
418 auto load_from_path = [this](const std::string& file_name) -> absl::Status {
419 if (file_name.empty()) {
420 return absl::OkStatus();
421 }
422
423 // Check if this is a project file - route to project loading
424 if (absl::EndsWith(file_name, ".yaze") ||
425 absl::EndsWith(file_name, ".zsproj") ||
426 absl::EndsWith(file_name, ".yazeproj")) {
427 return OpenRomOrProjectInternal(file_name);
428 }
429
430 auto path_status =
431 session_coordinator_->CheckBackingFileAvailable(file_name);
432 if (!path_status.ok()) {
433 toast_manager_.Show(std::string(path_status.message()),
435 return path_status;
436 }
437
438 // Delegate ROM loading to RomFileManager
439 Rom temp_rom;
440 RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, file_name));
441
442 const std::optional<size_t> previous_session_id =
444 const size_t previous_session_count =
445 session_coordinator_->GetTotalSessionCount();
446 auto session_or = session_coordinator_->CreateSessionFromRom(
447 std::move(temp_rom), file_name);
448 if (!session_or.ok()) {
449 return session_or.status();
450 }
451
455
456 // HandleSessionCreated binds the project context that governed this open.
457 // Reapply it here rather than partially clearing global runtime state.
460
461 // Keep ResourceLabelProvider in sync with the newly-active ROM session
462 // before any editors/assets query room/sprite names.
464
465#ifdef YAZE_ENABLE_TESTING
467#endif
468
470 const auto& recent_files = manager.GetRecentFiles();
471 const bool is_first_time_rom_path =
472 std::find(recent_files.begin(), recent_files.end(), file_name) ==
473 recent_files.end();
474 auto asset_status = LoadAssetsForMode();
475 if (!asset_status.ok()) {
476 auto rollback_status =
477 DiscardProvisionalSessionCreatedSince(previous_session_count);
478 RestoreProjectContextAfterFailedOpen(previous_session_id);
479 if (!rollback_status.ok()) {
480 return absl::InternalError(
481 absl::StrFormat("%s; session rollback failed: %s",
482 asset_status.message(), rollback_status.message()));
483 }
484 return asset_status;
485 }
486
487 manager.AddFile(file_name);
488 manager.Save();
489
490 if (ui_coordinator_) {
491 ui_coordinator_->SetWelcomeScreenVisible(false);
492
493 // Show ROM load options and bias new ROM paths toward project creation.
494 rom_load_options_dialog_.Open(GetCurrentRom(), is_first_time_rom_path);
496 }
497
498 return absl::OkStatus();
499 };
500
501#if defined(__APPLE__) && TARGET_OS_IOS == 1
502 // On iOS, route through the SwiftUI overlay document picker to get proper
503 // security-scoped access to iCloud Drive and Files app locations. This
504 // mirrors how OpenProject() works on iOS and supports cloud ROMs.
505 // The SwiftUI picker calls YazeIOSBridge.loadRomAtPath: after importing the
506 // ROM to a persistent sandbox directory.
508 return absl::OkStatus();
509#else
512 return load_from_path(file_name);
513#endif
514}
515
517 const std::optional<std::string>& save_as_filename) {
518 auto* current_rom = GetCurrentRom();
519 auto* current_editor_set = GetCurrentEditorSet();
520 if (!current_rom || !current_editor_set) {
521 return absl::FailedPreconditionError("No ROM or editor set loaded");
522 }
523 RETURN_IF_ERROR(current_rom->CheckLayoutWritable());
524 if (save_as_filename.has_value() && save_as_filename->empty()) {
525 return absl::InvalidArgumentError("No filename provided for save as");
526 }
527 if (save_as_filename.has_value()) {
528 RETURN_IF_ERROR(session_coordinator_->CheckBackingFileAvailable(
529 *save_as_filename, GetCurrentSessionId()));
530 }
531
532 // Refuse excluded model edits before serializers, project rebinding, or disk
533 // writes. Reuse the same preflight as direct Save / Apply Room.
534 if (auto* dungeon = static_cast<DungeonEditorV2*>(
535 current_editor_set->GetExistingEditor(EditorType::kDungeon))) {
536 RETURN_IF_ERROR(dungeon->ValidateSaveScope());
537 }
538
539 ASSIGN_OR_RETURN(const project::YazeProject* save_project,
541 // Hash/write/safety checks must read the same immutable session snapshot as
542 // the rest of this save, never a process-global project left by another ROM.
543 rom_lifecycle_.SetProjectContext(save_project);
544 struct LifecycleProjectContextGuard {
545 RomLifecycleManager* lifecycle = nullptr;
546 project::YazeProject* restore = nullptr;
547 ~LifecycleProjectContextGuard() {
548 if (lifecycle) {
549 lifecycle->SetProjectContext(restore);
550 }
551 }
552 } lifecycle_project_guard{&rom_lifecycle_, &current_project_};
553
554 // GraphicsEditor tracks pixel edits in its model, not in the ROM buffer.
555 // With Save Graphics Sheets enabled it joins the editor saves below; with
556 // it disabled the edits cannot be written, so block before any serializer
557 // can mutate the ROM rather than report a save that omitted the sheets.
558 // Gfx group tables (blocksets, spritesets, palettesets) are edited in
559 // GameData. Without Save Gfx Groups they cannot be written, so block before
560 // any serializer runs instead of saving a ROM that silently drops them.
561 auto* save_session = session_coordinator_->GetActiveRomSession();
562 auto& palette_manager = gfx::PaletteManager::Get();
563 const bool pending_palettes =
564 save_session != nullptr &&
565 palette_manager.HasUnsavedChanges(&save_session->game_data);
566 // Both existing save settings authorize PaletteManager's modified colors.
567 // Do not make palette-only persistence depend on opening Dungeon first.
568 const bool palette_save_enabled =
571 if (pending_palettes && !palette_save_enabled) {
572 return absl::FailedPreconditionError(
573 "Save blocked: palette edits are pending, but palette saving is "
574 "disabled. Enable Save All Palettes or Save Dungeon Palettes, or undo "
575 "the palette edits before saving the ROM.");
576 }
577 const bool pending_gfx_groups =
578 save_session != nullptr && save_session->HasPendingGfxGroupChanges();
579 if (pending_gfx_groups && !core::FeatureFlags::get().kSaveGfxGroups) {
580 return absl::FailedPreconditionError(
581 "Save blocked: blockset/spriteset (gfx group) edits are pending, but "
582 "Save Gfx Groups is disabled. Enable it, or undo the gfx group edits "
583 "before saving the ROM.");
584 }
585
586 if (current_editor_set->HasPendingGraphicsChanges() &&
588 return absl::FailedPreconditionError(
589 "Save blocked: graphics sheet edits are pending, but Save Graphics "
590 "Sheets is disabled. Enable it, or discard the graphics sheet edits "
591 "before saving the ROM.");
592 }
593
594 // Preflight map-only Screen edits before any serializer can mutate the ROM.
595 // Retain the exact planned extents for manifest protection if disk diffing
596 // is unavailable. Never materialize an unopened Screen editor here.
597 auto* screen_editor = static_cast<ScreenEditor*>(
598 current_editor_set->GetExistingEditor(EditorType::kScreen));
599 std::vector<std::pair<uint32_t, uint32_t>> predicted_screen_write_ranges;
600 if (screen_editor != nullptr && (screen_editor->IsRomBackedStateValid() ||
601 screen_editor->HasPendingScreenChanges())) {
602 ASSIGN_OR_RETURN(auto plan, screen_editor->PrepareDungeonMapSave());
603 for (const auto& write : plan.writes) {
604 predicted_screen_write_ranges.emplace_back(
605 write.pc_address, write.pc_address + write.data.size());
606 }
607 }
608
609 // --- State machine checks (delegated to RomLifecycleManager) ---
611 return absl::CancelledError("Save pending confirmation");
612 }
613
614 RETURN_IF_ERROR(CheckRomWritePolicy(save_as_filename));
615
616 // Pot streams are saved sparsely: unchanged/unloaded rooms are inventoried
617 // from ROM by the serializer. No load-all confirmation is necessary.
618
619 // --- Backup policy setup ---
620 const auto experiment_policy =
621 current_editor_ != nullptr
624 const bool force_experimental_backup =
626 experiment_policy.experimental &&
627 experiment_policy.disabled_save_posture ==
629 if (force_experimental_backup) {
631 "Experimental editor save: backup forced before writing the ROM",
633 }
634 if (save_project->project_opened()) {
636 save_project->workspace_settings.backup_on_save ||
637 force_experimental_backup,
638 save_project->GetAbsolutePath(save_project->rom_backup_folder),
642 } else {
644 user_settings_.prefs().backup_before_save || force_experimental_backup,
645 "", 20, true, 14);
646 }
647
648 // Reject an already-invalid Oracle layout before any editor serializer can
649 // mutate the ROM buffer or clear its own dirty tracking. The post-save check
650 // below remains mandatory because serializers may introduce a violation.
652
653 // Serializers clear their dirty flags after a successful in-memory write.
654 // Capture their conservative write prediction first so conflict protection
655 // still has a fail-closed fallback when the on-disk ROM cannot be read.
656 const auto predicted_dungeon_write_ranges =
657 current_editor_set->CollectDungeonWriteRanges();
658
659 ScopedRomTransaction rom_transaction(*current_rom);
660 ScopedEditorSaveTransactions editor_transactions;
661
662 auto save_editor = [&editor_transactions](Editor* editor) -> absl::Status {
663 if (editor == nullptr) {
664 return absl::OkStatus();
665 }
666 RETURN_IF_ERROR(editor_transactions.Begin(editor));
667 return editor->Save();
668 };
669
670 // --- Save editor-specific data ---
671 // A failed lazy Screen load leaves an invalid, clean editor behind. It has
672 // nothing to serialize and must not poison unrelated ROM saves.
673 if (screen_editor != nullptr && (screen_editor->IsRomBackedStateValid() ||
674 screen_editor->HasPendingScreenChanges())) {
675 RETURN_IF_ERROR(save_editor(screen_editor));
676 }
677 // A coordinated save must not materialize an unrelated lazy editor. Its
678 // serializer can project broad writes (and correctly trip manifest guards)
679 // even though the user never opened or edited that domain.
681 save_editor(current_editor_set->GetExistingEditor(EditorType::kDungeon)));
682 RETURN_IF_ERROR(save_editor(
683 current_editor_set->GetExistingEditor(EditorType::kOverworld)));
684
685 // Dungeon may already have serialized palettes under its own transaction.
686 // Otherwise enlist the Palette editor in the same file-save transaction.
687 if (pending_palettes &&
688 palette_manager.HasUnsavedChanges(&save_session->game_data)) {
690 RETURN_IF_ERROR(save_editor(
691 current_editor_set->GetExistingEditor(EditorType::kPalette)));
692 }
693
694 if (core::FeatureFlags::get().kSaveMessages) {
697 save_editor(current_editor_set->GetEditor(EditorType::kMessage)));
698 }
699
700 // Graphics sheets are written through zelda3::WriteGfxSheet (in place, or
701 // relocated into manifest-registered free space) before the write-conflict
702 // check below diffs the ROM against disk.
703 if (core::FeatureFlags::get().kSaveGraphicsSheet) {
704 RETURN_IF_ERROR(save_editor(
705 current_editor_set->GetExistingEditor(EditorType::kGraphics)));
706 }
707
708 // Gfx group tables are written byte-for-byte where they differ, then read
709 // back. The ROM transaction restores them if a later step fails.
710 if (pending_gfx_groups && core::FeatureFlags::get().kSaveGfxGroups) {
712 zelda3::SaveGfxGroups(*current_rom, save_session->game_data));
713 }
714
715 // Oracle guardrails: refuse to write obviously corrupted ROM layouts.
717
718 // --- Write conflict check (ASM-owned address protection) ---
719 if (save_project->project_opened() && save_project->hack_manifest.loaded()) {
721 std::vector<std::pair<uint32_t, uint32_t>> write_ranges;
722 bool diff_computed = false;
723
724 if (!current_rom->filename().empty()) {
725 std::ifstream file(current_rom->filename(), std::ios::binary);
726 if (file.is_open()) {
727 file.seekg(0, std::ios::end);
728 const std::streampos end = file.tellg();
729 if (end >= 0) {
730 std::vector<uint8_t> disk_data(static_cast<size_t>(end));
731 file.seekg(0, std::ios::beg);
732 file.read(reinterpret_cast<char*>(disk_data.data()),
733 static_cast<std::streamsize>(disk_data.size()));
734 if (file) {
735 diff_computed = true;
736 auto diff = yaze::rom::ComputeDiffRanges(disk_data,
737 current_rom->vector());
738 if (!diff.ranges.empty()) {
739 LOG_DEBUG("EditorManager",
740 "ROM save diff: %zu bytes changed in %zu range(s)",
741 diff.total_bytes_changed, diff.ranges.size());
742 write_ranges = std::move(diff.ranges);
743 }
744 }
745 }
746 }
747 }
748
749 if (write_ranges.empty() && !diff_computed) {
750 write_ranges = predicted_dungeon_write_ranges;
751 write_ranges.insert(write_ranges.end(),
752 predicted_screen_write_ranges.begin(),
753 predicted_screen_write_ranges.end());
754 }
755
756 if (!write_ranges.empty()) {
757 auto conflicts =
758 save_project->hack_manifest.AnalyzePcWriteRanges(write_ranges);
759 if (!conflicts.empty()) {
760 rom_lifecycle_.SetPendingWriteConflicts(std::move(conflicts));
761 if (popup_manager_) {
763 }
765 absl::StrFormat(
766 "Save paused: %zu write conflict(s) with ASM hooks",
769 return absl::CancelledError("Write conflict confirmation required");
770 }
771 }
772 } else {
773 // Bypass is single-use, set by the warning popup.
775 }
776 }
777
778 // Delegate the final atomic disk write to RomFileManager. Save As is part of
779 // this same transaction and writes only the requested target path.
780 // A project's tracked custom_collision.json follows only the project's own
781 // ROM file, never Save As targets or practice copies opened under it.
782 std::filesystem::path collision_source;
783 if (!save_as_filename.has_value() && save_project->project_opened() &&
784 !save_project->custom_collision_json.empty() &&
785 !save_project->rom_filename.empty()) {
786 std::error_code same_ec;
787 if (std::filesystem::equivalent(
788 save_project->GetAbsolutePath(save_project->rom_filename),
789 current_rom->filename(), same_ec)) {
790 collision_source =
791 save_project->GetAbsolutePath(save_project->custom_collision_json);
792 }
793 }
794 auto save_status =
795 save_as_filename.has_value()
796 ? rom_file_manager_.SaveRomAs(current_rom, *save_as_filename)
797 : rom_file_manager_.SaveRom(current_rom, collision_source);
798 if (save_status.ok()) {
799 editor_transactions.Commit();
800 rom_transaction.Commit();
801 CaptureCurrentRomIdentity(/*preserve_editing_source=*/true);
803 if (auto* session = session_coordinator_->GetActiveRomSession()) {
804 session->backup_restore_pending = false;
805 }
806 }
807 // Write-confirm bypass is single-use. Clear it after a successful save.
809
810 if (save_as_filename.has_value()) {
812 if (auto* session = session_coordinator_->GetActiveRomSession()) {
813 session->filepath = *save_as_filename;
814 }
815 }
816
818 manager.AddFile(*save_as_filename);
819 manager.Save();
820 if (popup_manager_) {
822 }
823 }
824 }
825 return save_status;
826}
827
829 auto pending_selection = std::move(pending_project_rom_selection_);
831 std::string project_rom_path = pending_selection.has_value()
832 ? pending_selection->candidate_path
834 const std::string previous_project_rom_path =
835 pending_selection.has_value() ? pending_selection->previous_path
837 bool persist_selected_rom = pending_selection.has_value();
838 auto abandon_selected_rom = [&]() {
839 if (!persist_selected_rom) {
840 return;
841 }
842 current_project_.rom_filename = previous_project_rom_path;
844 };
845
846 // Check if project has a ROM file specified
847 if (project_rom_path.empty()) {
848 // No ROM specified - prompt user to select one
850 "Project has no ROM file configured. Please select a ROM.",
852#if defined(__APPLE__) && TARGET_OS_IOS == 1
853 // Guard: if the project lives inside a .yazeproj bundle the ROM path
854 // defaults to bundle/rom, which may not exist yet because iCloud hasn't
855 // finished the download. Popping the file picker here would let a
856 // temporary path overwrite the correct bundle path in project.yaze.
857 // Show guidance and let the user reopen the project once the download
858 // is complete.
859 {
860 auto bundle_parent =
861 std::filesystem::path(current_project_.filepath).parent_path();
862 if (bundle_parent.extension() == ".yazeproj") {
864 "ROM is downloading from iCloud. Reopen the project in a few "
865 "seconds.",
866 ToastType::kInfo, 6.0f);
867 return absl::UnavailableError(
868 "Project ROM is not available from iCloud yet");
869 }
870 }
871 const std::string target_project_filepath = current_project_.filepath;
872 const std::optional<size_t> target_session_id =
874 const uint64_t selection_generation = ++project_rom_selection_generation_;
877 [this, target_project_filepath, target_session_id,
878 selection_generation](const std::string& rom_path) {
879 if (selection_generation != project_rom_selection_generation_ ||
880 current_project_.filepath != target_project_filepath ||
881 active_project_context_session_id_ != target_session_id) {
882 toast_manager_.Show(
883 "ROM selection ignored because the active project changed",
884 ToastType::kWarning);
885 return;
886 }
887 const bool restore_pending_transition =
889 const std::optional<size_t> previous_session_id =
891 auto restore_after_failure = [&]() {
892 if (restore_pending_transition) {
893 RestoreProjectContextAfterFailedOpen(previous_session_id);
894 }
895 };
896 if (rom_path.empty()) {
897 restore_after_failure();
898 return;
899 }
900 auto path_status =
901 session_coordinator_->CheckBackingFileAvailable(rom_path);
902 if (!path_status.ok()) {
903 toast_manager_.Show(std::string(path_status.message()),
905 restore_after_failure();
906 return;
907 }
908 auto selection_status = ValidateProjectRomSelection(rom_path);
909 if (!selection_status.ok()) {
911 absl::StrFormat("Failed to update project ROM: %s",
912 selection_status.message()),
914 restore_after_failure();
915 return;
916 }
917 auto status = LoadProjectWithRom();
918 if (!status.ok()) {
920 absl::StrFormat("Failed to load project ROM: %s",
921 status.message()),
923 restore_after_failure();
924 }
925 });
926 return absl::OkStatus();
927#else
930 if (rom_path.empty()) {
931 return absl::CancelledError("Project ROM selection cancelled");
932 }
933 RETURN_IF_ERROR(session_coordinator_->CheckBackingFileAvailable(rom_path));
934 project_rom_path = rom_path;
935 persist_selected_rom = true;
936#endif
937 }
938
939 auto backing_status =
940 session_coordinator_->CheckBackingFileAvailable(project_rom_path);
941 if (!backing_status.ok()) {
942 abandon_selected_rom();
943 return backing_status;
944 }
945
946 // Load ROM from project
947 Rom temp_rom;
948 auto load_status = rom_file_manager_.LoadRom(&temp_rom, project_rom_path);
949 if (!load_status.ok()) {
950 // ROM file not found or invalid - prompt user to select new ROM
952 absl::StrFormat("Could not load ROM '%s': %s. Please select a new ROM.",
953 project_rom_path, load_status.message()),
955#if defined(__APPLE__) && TARGET_OS_IOS == 1
956 abandon_selected_rom();
957 // If the ROM is inside a .yazeproj bundle its path may not be readable
958 // yet because iCloud hasn't finished downloading it. Saving any other
959 // path here would corrupt the project file with a temporary location.
960 // Show guidance and bail without touching current_project_.rom_filename.
961 {
962 auto rom_parent = std::filesystem::path(project_rom_path).parent_path();
963 if (rom_parent.extension() == ".yazeproj") {
965 "ROM is still downloading from iCloud. Try reopening the project "
966 "in a moment.",
967 ToastType::kInfo, 6.0f);
968 return absl::UnavailableError(
969 "Project ROM is still downloading from iCloud");
970 }
971 }
972 const std::string target_project_filepath = current_project_.filepath;
973 const std::optional<size_t> target_session_id =
975 const uint64_t selection_generation = ++project_rom_selection_generation_;
978 [this, target_project_filepath, target_session_id,
979 selection_generation](const std::string& rom_path) {
980 if (selection_generation != project_rom_selection_generation_ ||
981 current_project_.filepath != target_project_filepath ||
982 active_project_context_session_id_ != target_session_id) {
983 toast_manager_.Show(
984 "ROM selection ignored because the active project changed",
985 ToastType::kWarning);
986 return;
987 }
988 const bool restore_pending_transition =
990 const std::optional<size_t> previous_session_id =
992 auto restore_after_failure = [&]() {
993 if (restore_pending_transition) {
994 RestoreProjectContextAfterFailedOpen(previous_session_id);
995 }
996 };
997 if (rom_path.empty()) {
998 restore_after_failure();
999 return;
1000 }
1001 auto path_status =
1002 session_coordinator_->CheckBackingFileAvailable(rom_path);
1003 if (!path_status.ok()) {
1004 toast_manager_.Show(std::string(path_status.message()),
1006 restore_after_failure();
1007 return;
1008 }
1009 auto selection_status = ValidateProjectRomSelection(rom_path);
1010 if (!selection_status.ok()) {
1012 absl::StrFormat("Failed to update project ROM: %s",
1013 selection_status.message()),
1015 restore_after_failure();
1016 return;
1017 }
1018 auto status = LoadProjectWithRom();
1019 if (!status.ok()) {
1021 absl::StrFormat("Failed to load project ROM: %s",
1022 status.message()),
1024 restore_after_failure();
1025 }
1026 });
1027 return absl::OkStatus();
1028#else
1031 if (rom_path.empty()) {
1032 return absl::CancelledError("Project ROM selection cancelled");
1033 }
1034 RETURN_IF_ERROR(session_coordinator_->CheckBackingFileAvailable(rom_path));
1035 project_rom_path = rom_path;
1036 persist_selected_rom = true;
1037 RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, rom_path));
1038#endif
1039 }
1040
1041 auto policy_status = rom_lifecycle_.CheckRomOpenPolicy(&temp_rom);
1042 if (!policy_status.ok()) {
1043 abandon_selected_rom();
1044 return policy_status;
1045 }
1046
1047 if (persist_selected_rom) {
1048 current_project_.rom_filename = project_rom_path;
1049 }
1050
1051 const size_t previous_session_count =
1052 session_coordinator_->GetTotalSessionCount();
1053 auto session_or = session_coordinator_->CreateSessionFromRom(
1054 std::move(temp_rom), project_rom_path);
1055 if (!session_or.ok()) {
1056 abandon_selected_rom();
1057 return session_or.status();
1058 }
1059 RomSession* session = *session_or;
1060
1064
1065 // Auto-enable custom object rendering when a project defines custom object
1066 // data but the stale feature flag is off.
1067 if (ProjectUsesCustomObjects(current_project_) &&
1070 LOG_WARN("EditorManager",
1071 "Project has custom object data but 'enable_custom_objects' was "
1072 "disabled. Enabling at runtime.");
1073 toast_manager_.Show("Custom object rendering auto-enabled for this project",
1075 }
1076 std::string legacy_mapping_warning;
1077 if (SeedLegacyTrackObjectMapping(&current_project_,
1078 &legacy_mapping_warning)) {
1079 LOG_WARN("EditorManager", "%s", legacy_mapping_warning.c_str());
1081 "Seeded default custom object mapping for object 0x31 (save project "
1082 "to persist)",
1084 }
1085
1086 // Apply project feature flags to both session and global singleton.
1090#if !defined(NDEBUG)
1091 LOG_INFO(
1092 "EditorManager",
1093 "Feature flags applied: kEnableCustomObjects=%s, "
1094 "custom_objects_folder='%s', custom_object_files=%zu entries",
1098#endif
1099
1102 if (version_manager_) {
1103 // Opening a project never creates a Git repository; the Initialize Git
1104 // action in Project Management is the opt-in. Adopt one that exists.
1106 current_project_.git_repository = session->project_context->git_repository;
1107 }
1108
1109 if (auto* rom = GetCurrentRom(); rom && rom->is_loaded()) {
1110 if (IsRomHashMismatch()) {
1112 "Project ROM hash mismatch detected. Check ROM Identity settings.",
1114 }
1115 auto warnings = ValidateRomAddressOverrides(
1117 if (!warnings.empty()) {
1118 for (const auto& warning : warnings) {
1119 LOG_WARN("EditorManager", "%s", warning.c_str());
1120 }
1121 toast_manager_.Show(absl::StrFormat("ROM override warnings: %d (see log)",
1122 warnings.size()),
1124 }
1125 }
1126
1127 // Update test manager with current ROM for ROM-dependent tests (only when
1128 // tests are enabled)
1129#ifdef YAZE_ENABLE_TESTING
1130 LOG_DEBUG("EditorManager", "Setting ROM in TestManager - %p ('%s')",
1131 (void*)GetCurrentRom(),
1132 GetCurrentRom() ? GetCurrentRom()->title().c_str() : "null");
1134#endif
1135
1136 if (auto* editor_set = GetCurrentEditorSet();
1137 editor_set && !current_project_.code_folder.empty()) {
1138 const std::string absolute_code_folder =
1140 // iOS: avoid blocking the main thread during project open / scene updates.
1141#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
1142 editor_set->OpenAssemblyFolder(absolute_code_folder);
1143#endif
1144 // Also set the sidebar file browser path (refresh happens during UI draw).
1145 window_manager_.SetFileBrowserPath("Assembly", absolute_code_folder);
1146 }
1147
1148 // Initialize labels before loading editor assets so room lists / command
1149 // palette entries resolve project registry labels on first render.
1151
1152 auto asset_status = LoadAssetsForMode();
1153 if (!asset_status.ok()) {
1154 abandon_selected_rom();
1155 auto rollback_status =
1156 DiscardProvisionalSessionCreatedSince(previous_session_count);
1157 if (!rollback_status.ok()) {
1158 return absl::InternalError(
1159 absl::StrFormat("%s; session rollback failed: %s",
1160 asset_status.message(), rollback_status.message()));
1161 }
1162 return asset_status;
1163 }
1164
1165 if (persist_selected_rom) {
1166 auto save_status = current_project_.Save();
1167 if (!save_status.ok()) {
1168 abandon_selected_rom();
1169 auto rollback_status =
1170 DiscardProvisionalSessionCreatedSince(previous_session_count);
1171 if (!rollback_status.ok()) {
1172 return absl::InternalError(
1173 absl::StrFormat("%s; session rollback failed: %s",
1174 save_status.message(), rollback_status.message()));
1175 }
1176 return save_status;
1177 }
1179 }
1180
1182
1184
1185 // Apply workspace settings
1194 ImGui::GetIO().FontGlobalScale = user_settings_.prefs().font_global_scale;
1195
1198
1206 project_management_panel_->SetBuildLogOutput("");
1207 }
1209 active_project_build_.reset();
1211
1212 // Publish project context for hack workflow panels and other consumers
1214
1215 // Add to recent files
1217 manager.AddFile(current_project_.filepath);
1218 manager.Save();
1219
1220 // Update project management panel with loaded project
1223 project_management_panel_->SetVersionManager(version_manager_);
1225 }
1226
1227 toast_manager_.Show(absl::StrFormat("Project '%s' loaded successfully",
1230
1233 return absl::OkStatus();
1234}
1235
1237 Rom&& rom, const std::string& filepath) {
1238 if (!session_coordinator_) {
1239 return absl::FailedPreconditionError("No session coordinator");
1240 }
1241 auto* session = session_coordinator_->GetActiveRomSession();
1242 if (!session) {
1243 return absl::FailedPreconditionError("No active ROM session");
1244 }
1245
1246 // Lazy asset loading can defer OverworldEditor::Load after replacement.
1247 // Refuse a changed image while its read-only canvas still owns ROM-backed
1248 // maps and textures, before changing bytes, dirty state or undo history.
1249 if (auto* overworld_editor = static_cast<OverworldEditor*>(
1250 session->editors.GetExistingEditor(EditorType::kOverworld))) {
1251 RETURN_IF_ERROR(overworld_editor->CheckRomReplacement(rom));
1252 }
1253
1254 // Reject invalid shared assets before invalidating live editor models and
1255 // their Undo histories. Rebuilding the old ROM on failure cannot recover
1256 // model-only edits. Keep this preflight CPU-only and leave lazy, unloaded
1257 // sessions lazy; editor-specific refresh failures still use rollback below.
1258 if (session->game_data_loaded || asset_load_mode_ != AssetLoadMode::kLazy) {
1259 zelda3::GameData replacement_data;
1260 zelda3::LoadOptions options;
1261 options.load_graphics = false;
1262 options.expand_rom = false;
1263 RETURN_IF_ERROR(zelda3::LoadGameData(rom, replacement_data, options));
1264 }
1265
1266 Rom previous_rom = session->rom;
1267 const std::string previous_filepath = session->filepath;
1268 const bool game_data_was_loaded = session->game_data_loaded;
1269 auto* dungeon_editor = static_cast<DungeonEditorV2*>(
1270 session->editors.GetExistingEditor(EditorType::kDungeon));
1271 const size_t dungeon_index = EditorTypeIndex(EditorType::kDungeon);
1272 const bool dungeon_was_initialized =
1273 session->editor_initialized[dungeon_index];
1274 const bool dungeon_assets_were_loaded =
1275 session->editor_assets_loaded[dungeon_index];
1276 auto* screen_editor = static_cast<ScreenEditor*>(
1277 session->editors.GetExistingEditor(EditorType::kScreen));
1278 const size_t screen_index = EditorTypeIndex(EditorType::kScreen);
1279 const bool screen_was_initialized = session->editor_initialized[screen_index];
1280 const bool screen_assets_were_loaded =
1281 session->editor_assets_loaded[screen_index];
1282 auto restore_dungeon_asset_state = [&]() {
1283 if (dungeon_editor) {
1284 session->editor_initialized[dungeon_index] =
1285 session->editor_initialized[dungeon_index] || dungeon_was_initialized;
1286 session->editor_assets_loaded[dungeon_index] =
1287 session->editor_assets_loaded[dungeon_index] ||
1288 dungeon_assets_were_loaded;
1289 }
1290 };
1291 auto restore_screen_asset_state = [&]() {
1292 if (screen_editor) {
1293 session->editor_initialized[screen_index] =
1294 session->editor_initialized[screen_index] || screen_was_initialized;
1295 session->editor_assets_loaded[screen_index] =
1296 session->editor_assets_loaded[screen_index] ||
1297 screen_assets_were_loaded;
1298 }
1299 };
1300
1301 session->editors.InvalidateScreenRomBackedState();
1302 gfx::PaletteManager::Get().ReleaseSession(&session->game_data);
1303 session->rom = std::move(rom);
1304 session->filepath = filepath;
1305 session->game_data.Clear();
1306 session->game_data.set_rom(&session->rom);
1307 session->editors.SetGameData(&session->game_data);
1308 gfx::PaletteManager::Get().ActivateSession(&session->game_data);
1309 ResetAssetState(session);
1310 ConfigureSession(session);
1312 // Retain the prior baseline until the replacement and its assets succeed.
1313 auto load_status = LoadAssetsForMode();
1314 if (load_status.ok() && game_data_was_loaded && !session->game_data_loaded) {
1315 load_status = EnsureGameDataLoaded();
1316 }
1317 if (load_status.ok() && screen_editor && screen_assets_were_loaded) {
1318 load_status = screen_editor->RefreshRomBackedState();
1319 }
1320 if (load_status.ok()) {
1321 restore_screen_asset_state();
1322 }
1323 if (load_status.ok() && dungeon_editor) {
1324 load_status = dungeon_editor->RefreshRomBackedState();
1325 if (load_status.ok()) {
1326 restore_dungeon_asset_state();
1327 }
1328 }
1329 if (!load_status.ok()) {
1330 // Loading editors is fallible. Restore the prior ROM and rebuild its
1331 // assets so a failed reload never leaves a half-initialized live session.
1332 session->editors.InvalidateScreenRomBackedState();
1333 gfx::PaletteManager::Get().ReleaseSession(&session->game_data);
1334 session->rom = std::move(previous_rom);
1335 session->filepath = previous_filepath;
1336 session->game_data.Clear();
1337 session->game_data.set_rom(&session->rom);
1338 session->editors.SetGameData(&session->game_data);
1339 gfx::PaletteManager::Get().ActivateSession(&session->game_data);
1340 ResetAssetState(session);
1341 ConfigureSession(session);
1344 auto rollback_status = LoadAssetsForMode();
1345 if (game_data_was_loaded && !session->game_data_loaded) {
1346 auto game_data_status = EnsureGameDataLoaded();
1347 if (rollback_status.ok()) {
1348 rollback_status = game_data_status;
1349 }
1350 }
1351 if (screen_editor && screen_assets_were_loaded) {
1352 auto screen_status = screen_editor->RefreshRomBackedState();
1353 if (screen_status.ok()) {
1354 restore_screen_asset_state();
1355 }
1356 if (rollback_status.ok()) {
1357 rollback_status = screen_status;
1358 }
1359 } else {
1360 restore_screen_asset_state();
1361 }
1362 if (dungeon_editor) {
1363 auto dungeon_status = dungeon_editor->RefreshRomBackedState();
1364 if (dungeon_status.ok()) {
1365 restore_dungeon_asset_state();
1366 }
1367 if (rollback_status.ok()) {
1368 rollback_status = dungeon_status;
1369 }
1370 }
1372 if (!rollback_status.ok()) {
1373 return absl::InternalError(absl::StrFormat(
1374 "ROM reload failed (%s) and restoring prior assets failed (%s)",
1375 load_status.message(), rollback_status.message()));
1376 }
1377 return load_status;
1378 }
1381 return absl::OkStatus();
1382}
1383
1385 // Avoid a wall of onboarding toasts after load. Each subsequent load
1386 // advances to the next unseen, context-relevant tip.
1388 return;
1389 }
1390 Rom* rom = GetCurrentRom();
1391 if (rom && rom->is_loaded()) {
1395 return;
1396 }
1397 }
1399}
1400
1401} // 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
auto data() const
Definition rom.h:201
auto size() const
Definition rom.h:200
bool is_loaded() const
Definition rom.h:187
static TimingManager & Get()
Definition timing.h:20
float GetElapsedTime() const
Get total elapsed time since first update.
Definition timing.h:70
static Flags & get()
Definition features.h:119
std::vector< WriteConflict > AnalyzePcWriteRanges(const std::vector< std::pair< uint32_t, uint32_t > > &pc_ranges) const
Analyze a set of PC-offset ranges for write conflicts.
bool loaded() const
Check if the manifest has been loaded.
DungeonEditorV2 - Simplified dungeon editor using component delegation.
std::unique_ptr< SessionCoordinator > session_coordinator_
RomLifecycleManager rom_lifecycle_
void SwitchToEditor(EditorType editor_type, bool force_visible=false, bool from_dialog=false) override
project::YazeProject current_project_
absl::Status LoadAssetsForMode(uint64_t loading_handle=0)
absl::Status CheckRomWritePolicy(const std::optional< std::string > &target_filename=std::nullopt)
Save the current ROM file.
Rom * GetCurrentRom() const override
void ResetAssetState(RomSession *session)
core::VersionManager * version_manager_
void SyncEditorContextForCategory(const std::string &category)
Editor * ResolveEditorForCategory(const std::string &category)
absl::Status FinalizeNewProject(const std::string &project_name, const std::string &project_path=std::string())
FeatureHintManager feature_hint_manager_
absl::Status OpenRomOrProjectInternal(const std::string &filename)
auto GetCurrentEditorSet() const -> EditorSet *
void HandleSessionRomLoaded(size_t index, Rom *rom)
WorkspaceWindowManager window_manager_
absl::Status SaveRomInternal(const std::optional< std::string > &save_as_filename)
std::optional< size_t > pending_project_open_previous_session_id_
std::optional< size_t > active_project_context_session_id_
RomLoadOptionsDialog rom_load_options_dialog_
void BindProjectContextToSession(RomSession *session, const project::YazeProject &project)
size_t GetCurrentSessionId() const
Stable workspace identity; unlike the UI index, it does not compact.
absl::Status ValidateProjectRomSelection(const std::string &rom_path)
void ConfigureSession(RomSession *session) override
std::unique_ptr< BackgroundCommandTask > active_project_build_
void ConfigureEditorDependencies(EditorSet *editor_set, Rom *rom, size_t session_id)
absl::StatusOr< const project::YazeProject * > PrepareActiveProjectContextForSave()
std::unique_ptr< ProjectManagementPanel > project_management_panel_
void RestoreProjectContextAfterFailedOpen(std::optional< size_t > previous_session_id)
absl::Status CheckOracleRomSafetyPreSave(Rom *rom)
absl::Status DiscardProvisionalSessionCreatedSince(size_t previous_session_count)
absl::Status EnsureEditorAssetsLoaded(EditorType type)
std::unique_ptr< PopupManager > popup_manager_
bool RestoreProjectContextForSession(RomSession *session)
void CaptureCurrentRomIdentity(bool preserve_editing_source=false)
std::unique_ptr< UICoordinator > ui_coordinator_
std::optional< PendingProjectRomSelection > pending_project_rom_selection_
absl::Status ReplaceActiveSessionRom(Rom &&rom, const std::string &filepath)
static EditorType GetEditorTypeFromCategory(const std::string &category)
static EditorExperimentPolicy GetExperimentPolicy(EditorType type)
Interface for editor classes.
Definition editor.h:249
virtual absl::Status BeginSaveTransaction()
Definition editor.h:276
EditorType type() const
Definition editor.h:343
bool MaybeShow(std::string_view id)
static std::vector< std::string > GetDefaultWindows(EditorType type)
Main UI class for editing overworld maps in A Link to the Past.
absl::Status SaveRom(Rom *rom, const std::filesystem::path &collision_source={})
absl::Status LoadRom(Rom *rom, const std::string &filename)
absl::Status SaveRomAs(Rom *rom, const std::string &filename)
Manages ROM and project persistence state.
void SetProjectContext(const project::YazeProject *project)
absl::Status CheckRomOpenPolicy(Rom *rom)
Validate that the loaded ROM is a safe project target to open/edit.
const std::vector< core::WriteConflict > & pending_write_conflicts() const
void SetPendingWriteConflicts(std::vector< core::WriteConflict > conflicts)
void ApplyDefaultBackupPolicy(bool enabled, const std::string &folder, int retention_count, bool keep_daily, int keep_daily_days)
Apply default backup policy from user settings.
void Open(Rom *rom, const std::string &rom_filename)
Open the dialog after ROM detection.
void SetConfirmCallback(std::function< void(const LoadOptions &)> callback)
Set callback for when options are confirmed.
The ScreenEditor class allows the user to edit a variety of screens in the game or create a custom me...
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f, std::string action_label={}, std::function< void()> on_action=nullptr)
void EnableFileBrowser(const std::string &category, const std::string &root_path="")
std::vector< WindowDescriptor > GetWindowsInCategory(size_t session_id, const std::string &category) const
void SetEditorResolver(std::function< Editor *(const std::string &)> resolver)
void RestoreVisibilityState(size_t session_id, const std::unordered_map< std::string, bool > &state, bool publish_events=false)
Restore panel visibility state from persistence.
void SetPanelBrowserCategoryWidthChangedCallback(std::function< void(float)> cb)
void SetSidebarStateChangedCallback(std::function< void(bool, bool)> cb)
static constexpr const char * kDashboardCategory
void SetCategoryChangedCallback(std::function< void(const std::string &)> cb)
void SetFileBrowserPath(const std::string &category, const std::string &path)
void SetOnWindowCategorySelectedCallback(std::function< void(const std::string &)> callback)
void SetOnWindowClickedCallback(std::function< void(const std::string &)> callback)
bool OpenWindow(size_t session_id, const std::string &base_window_id)
void SetSidePanelWidthChangedCallback(std::function< void(float)> cb)
void SetFileClickedCallback(std::function< void(const std::string &category, const std::string &path)> callback)
ScopedEditorSaveTransactions & operator=(const ScopedEditorSaveTransactions &)=delete
void ActivateSession(zelda3::GameData *game_data)
Select the palette state for a ROM session without resnapshotting it.
static PaletteManager & Get()
Get the singleton instance.
void ReleaseSession(const zelda3::GameData *game_data)
Forget all palette tracking for a closing or reloaded ROM session.
static RecentFilesManager & GetInstance()
Definition project.h:598
void SetCurrentRom(Rom *rom)
static TestManager & Get()
static void ShowOpenFileDialogAsync(const FileDialogOptions &options, std::function< void(const std::string &)> callback)
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
static const std::vector< std::string > & DefaultSubtypeFilenamesForObject(int object_id)
static DrawRoutineRegistry & Get()
static OverworldVersion GetVersion(const Rom &rom)
Detect ROM version from ASM marker byte.
int main(int argc, char **argv)
Definition emu.cc:43
#define LOG_DEBUG(category, format,...)
Definition log.h:133
#define LOG_WARN(category, format,...)
Definition log.h:138
#define LOG_INFO(category, format,...)
Definition log.h:136
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
constexpr char kOverworldExpandedPtrHigh[]
constexpr char kOverworldEntrancePosExpanded[]
constexpr char kExpandedMusicHook[]
constexpr char kExpandedMusicMain[]
constexpr char kOverworldExpandedPtrMagic[]
constexpr char kExpandedMessageEnd[]
constexpr char kOverworldExpandedPtrMarker[]
constexpr char kOverworldEntranceMapExpanded[]
constexpr char kOverworldEntranceFlagExpanded[]
constexpr char kExpandedMessageStart[]
constexpr char kOverworldEntranceIdExpanded[]
constexpr char kOverworldExpandedPtrLow[]
constexpr char kExpandedMusicAux[]
void SetEditorWindowContext(const std::string &category, Editor *editor)
void SetBuildWorkflowStatus(const ProjectWorkflowStatus &status)
void SetBuildWorkflowLog(const std::string &output)
void SetRunWorkflowStatus(const ProjectWorkflowStatus &status)
void SetCurrentProject(::yaze::project::YazeProject *project)
Set the current project instance.
constexpr const char * kZScreamZso
constexpr const char * kZScreamWelcome
constexpr const char * kZScreamProjects
constexpr const char * kSaveAs
constexpr const char * kWriteConflictWarning
bool HasAnyOverride(const core::RomAddressOverrides &overrides, std::initializer_list< const char * > keys)
bool SeedLegacyTrackObjectMapping(project::YazeProject *project, std::string *warning)
std::vector< std::string > ValidateRomAddressOverrides(const core::RomAddressOverrides &overrides, const Rom &rom)
Editors are the view controllers for the application.
size_t EditorTypeIndex(EditorType type)
Definition editor.h:239
void PostOverlayCommand(const char *command)
DiffSummary ComputeDiffRanges(const std::vector< uint8_t > &before, const std::vector< uint8_t > &after)
Definition rom_diff.cc:11
FileDialogOptions MakeRomFileDialogOptions(bool include_all_files)
Definition file_util.cc:87
absl::Status SaveGfxGroups(Rom &rom, const GameData &data)
Definition game_data.cc:362
absl::Status LoadGameData(Rom &rom, GameData &data, const LoadOptions &options)
Loads all Zelda3-specific game data from a generic ROM.
Definition game_data.cc:123
constexpr int kExpandedPtrTableMarker
Definition overworld.h:186
@ kVanilla
0xFF in ROM, no ZScream ASM applied
constexpr uint8_t kExpandedPtrTableMagic
Definition overworld.h:187
constexpr int kOverworldEntranceExpandedFlagPos
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
struct yaze::core::FeatureFlags::Flags::Dungeon dungeon
std::unordered_map< std::string, uint32_t > addresses
std::optional< uint32_t > GetAddress(const std::string &key) const
Represents a single session, containing a ROM and its associated editors.
core::FeatureFlags::Flags feature_flags
std::optional< project::YazeProject > project_context
std::unordered_map< std::string, std::unordered_map< std::string, bool > > panel_visibility_state
Modern project structure with comprehensive settings consolidation.
Definition project.h:287
std::string rom_backup_folder
Definition project.h:306
std::unordered_map< int, std::vector< std::string > > custom_object_files
Definition project.h:342
std::string custom_objects_folder
Definition project.h:327
bool project_opened() const
Definition project.h:505
std::string git_repository
Definition project.h:374
core::HackManifest hack_manifest
Definition project.h:360
std::string GetDisplayName() const
Definition project.cc:2001
WorkspaceSettings workspace_settings
Definition project.h:346
std::string custom_collision_json
Definition project.h:325
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:2028
core::FeatureFlags::Flags feature_flags
Definition project.h:345
core::RomAddressOverrides rom_address_overrides
Definition project.h:351