yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
rom_lifecycle_manager.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cctype>
5#include <filesystem>
6#include <string>
7#include <vector>
8
9#include "absl/status/status.h"
10#include "absl/strings/ascii.h"
11#include "absl/strings/match.h"
12#include "absl/strings/str_format.h"
16#include "core/hack_manifest.h"
17#include "core/project.h"
18#include "rom/rom.h"
19#include "util/rom_hash.h"
21
22namespace yaze::editor {
23
24namespace {
25
26std::string NormalizeHash(std::string value) {
27 absl::AsciiStrToLower(&value);
28 if (absl::StartsWith(value, "0x")) {
29 value = value.substr(2);
30 }
31 value.erase(std::remove_if(value.begin(), value.end(),
32 [](unsigned char c) { return std::isspace(c); }),
33 value.end());
34 return value;
35}
36
37std::string NormalizePath(std::string value) {
38 if (value.empty()) {
39 return value;
40 }
41 std::filesystem::path path(value);
42 path = path.lexically_normal();
43 path.make_preferred();
44 return path.string();
45}
46
47std::string Lowercase(std::string value) {
48 absl::AsciiStrToLower(&value);
49 return value;
50}
51
52bool PathsMatch(const std::string& lhs, const std::string& rhs) {
53 if (lhs.empty() || rhs.empty()) {
54 return false;
55 }
56 const std::string normalized_lhs = NormalizePath(lhs);
57 const std::string normalized_rhs = NormalizePath(rhs);
58 if (normalized_lhs == normalized_rhs) {
59 return true;
60 }
61 return Lowercase(std::filesystem::path(normalized_lhs).filename().string()) ==
62 Lowercase(std::filesystem::path(normalized_rhs).filename().string());
63}
64
66 std::string loaded_path;
69 std::string build_output_path;
70 bool matches_configured_project = false;
71 bool matches_editable_target = false;
72 bool matches_build_output = false;
73};
74
76 if (!project) {
77 return "";
78 }
79 if (project->hack_manifest.loaded() &&
80 !project->hack_manifest.build_pipeline().dev_rom.empty()) {
81 return NormalizePath(project->GetAbsolutePath(
83 }
84 return NormalizePath(project->GetAbsolutePath(project->rom_filename));
85}
86
88 if (!project) {
89 return "";
90 }
91 if (project->hack_manifest.loaded() &&
92 !project->hack_manifest.build_pipeline().patched_rom.empty()) {
93 return NormalizePath(project->GetAbsolutePath(
95 }
96 return NormalizePath(project->GetAbsolutePath(project->build_target));
97}
98
100 const std::string& loaded_path) {
101 RomTargetInfo info;
102 info.loaded_path = NormalizePath(loaded_path);
103 if (!project) {
104 return info;
105 }
106
110
117 return info;
118}
119
120std::string GetEditableTargetPath(const RomTargetInfo& info) {
121 if (!info.editable_target_path.empty()) {
122 return info.editable_target_path;
123 }
124 if (!info.configured_project_path.empty()) {
125 return info.configured_project_path;
126 }
127 return "(unknown)";
128}
129
130} // namespace
131
133 : rom_file_manager_(deps.rom_file_manager),
134 session_coordinator_(deps.session_coordinator),
135 toast_manager_(deps.toast_manager),
136 popup_manager_(deps.popup_manager),
137 project_(deps.project) {}
138
146
147// =============================================================================
148// ROM Hash & Write Policy
149// =============================================================================
150
152 if (!rom || !rom->is_loaded()) {
153 current_rom_hash_.clear();
154 current_rom_path_.clear();
155 return;
156 }
158 current_rom_path_ = NormalizePath(rom->filename());
159}
160
162 if (!project_ || !project_->project_opened()) {
163 return false;
164 }
165 const auto target_info = GetRomTargetInfo(project_, current_rom_path_);
166 if (target_info.matches_editable_target &&
167 !target_info.matches_build_output) {
168 return false;
169 }
170 const auto expected = NormalizeHash(project_->rom_metadata.expected_hash);
171 const auto actual = NormalizeHash(current_rom_hash_);
172 if (expected.empty() || actual.empty()) {
173 return false;
174 }
175 return expected != actual;
176}
177
179 if (!project_ || !project_->project_opened() || !rom || !rom->is_loaded()) {
180 return absl::OkStatus();
181 }
182
183 const auto target_info = GetRomTargetInfo(project_, rom->filename());
184 if (!target_info.matches_build_output) {
185 return absl::OkStatus();
186 }
187
190 return absl::OkStatus();
191 }
192
194 return absl::OkStatus();
195 }
196
197 const std::string editable_target = GetEditableTargetPath(target_info);
198 if (toast_manager_) {
200 absl::StrFormat("Project opened a build-output ROM; edit '%s' instead",
201 editable_target),
203 }
204 return absl::FailedPreconditionError(absl::StrFormat(
205 "Loaded ROM '%s' matches the project's build output. Edit '%s' instead.",
206 target_info.loaded_path.empty() ? rom->filename()
207 : target_info.loaded_path,
208 editable_target));
209}
210
212 Rom* rom, const std::optional<std::string>& target_filename) {
213 if (!project_ || !project_->project_opened()) {
214 return absl::OkStatus();
215 }
216
217 const auto policy = project_->rom_metadata.write_policy;
218 const std::string target_path =
219 target_filename.has_value()
220 ? *target_filename
221 : (rom && !rom->filename().empty() ? rom->filename()
223 const std::string source_path =
224 rom && !rom->filename().empty() ? rom->filename() : current_rom_path_;
225 const auto target_info = GetRomTargetInfo(project_, target_path);
226 const auto source_info = GetRomTargetInfo(project_, source_path);
227 if (target_info.matches_build_output) {
228 if (policy == project::RomWritePolicy::kAllow) {
229 return absl::OkStatus();
230 }
231 if (toast_manager_) {
233 absl::StrFormat(
234 "ROM write blocked: '%s' is the project's build output",
235 target_info.loaded_path.empty() ? target_path
236 : target_info.loaded_path),
238 }
239 return absl::PermissionDeniedError(absl::StrFormat(
240 "ROM write blocked: '%s' is the project's build output. Edit '%s' "
241 "instead.",
242 target_info.loaded_path.empty() ? target_path : target_info.loaded_path,
243 GetEditableTargetPath(target_info)));
244 }
245
246 if (source_info.matches_editable_target &&
247 !source_info.matches_build_output) {
248 return absl::OkStatus();
249 }
250
251 const auto expected = NormalizeHash(project_->rom_metadata.expected_hash);
252 const auto actual = NormalizeHash(current_rom_hash_);
253
254 if (expected.empty() || actual.empty() || expected == actual) {
255 return absl::OkStatus();
256 }
257
258 if (policy == project::RomWritePolicy::kAllow) {
259 return absl::OkStatus();
260 }
261
262 if (policy == project::RomWritePolicy::kBlock) {
263 if (toast_manager_) {
265 "ROM write blocked: project expects a different ROM hash",
267 }
268 return absl::PermissionDeniedError(
269 "ROM write blocked by project write policy");
270 }
271
274 if (popup_manager_) {
276 }
277 if (toast_manager_) {
278 toast_manager_->Show("ROM hash mismatch: confirmation required to save",
280 }
281 return absl::CancelledError("ROM write confirmation required");
282 }
283
284 return absl::OkStatus();
285}
286
288 if (!rom || !rom->is_loaded()) {
289 return absl::InvalidArgumentError("ROM not loaded");
290 }
291
292 if (!project_ || !project_->project_opened()) {
293 return absl::OkStatus();
294 }
295
296 // An explicitly configured manifest is part of the project's save-safety
297 // contract. Never silently disable those guardrails when the file is missing
298 // or malformed.
299 if (!project_->hack_manifest_file.empty() &&
301 const std::string manifest_path =
303 if (toast_manager_) {
305 absl::StrFormat(
306 "Save blocked: configured hack manifest is unavailable: %s",
307 manifest_path),
309 }
310 return absl::FailedPreconditionError(absl::StrFormat(
311 "Configured hack manifest is missing or malformed: %s", manifest_path));
312 }
313
314 if (!project_->hack_manifest.loaded() ||
316 return absl::OkStatus();
317 }
318
322 options.validate_water_fill_table = true;
323 options.validate_custom_collision_maps = true;
324 options.max_collision_errors = 6;
325
326 const auto preflight = zelda3::RunOracleRomSafetyPreflight(rom, options);
327 if (preflight.ok()) {
328 return absl::OkStatus();
329 }
330
331 const auto& first = preflight.errors.front();
332 if (toast_manager_) {
333 toast_manager_->Show(absl::StrFormat("Oracle ROM safety [%s]: %s",
334 first.code, first.message.c_str()),
336 }
337 return preflight.ToStatus();
338}
339
340// =============================================================================
341// ROM Write Confirmation State Machine
342// =============================================================================
343
348
353
354// =============================================================================
355// Pot Item Save Confirmation
356// =============================================================================
357
359 int total_rooms) {
361 pending_pot_item_unloaded_rooms_ = unloaded_rooms;
362 pending_pot_item_total_rooms_ = total_rooms;
363}
364
367 PotItemSaveDecision decision) {
371
372 if (decision == PotItemSaveDecision::kCancel) {
373 return decision;
374 }
375
378 }
380 return decision;
381}
382
383// =============================================================================
384// Backup Management
385// =============================================================================
386
387std::vector<RomFileManager::BackupEntry> RomLifecycleManager::GetRomBackups(
388 Rom* rom) const {
389 if (!rom || !rom_file_manager_) {
390 return {};
391 }
392 return rom_file_manager_->ListBackups(rom->filename());
393}
394
396 if (!rom) {
397 return absl::FailedPreconditionError("No ROM loaded");
398 }
399 if (!rom_file_manager_) {
400 return absl::FailedPreconditionError("No ROM file manager");
401 }
403}
404
406 const std::string& folder,
407 int retention_count,
408 bool keep_daily,
409 int keep_daily_days) {
411 return;
417}
418
419} // 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:28
auto filename() const
Definition rom.h:157
auto data() const
Definition rom.h:151
auto size() const
Definition rom.h:150
bool is_loaded() const
Definition rom.h:144
bool HasProjectRegistry() const
bool loaded() const
Check if the manifest has been loaded.
const BuildPipeline & build_pipeline() const
void Show(const char *name)
void SetBackupRetentionCount(int count)
absl::Status PruneBackups(const std::string &rom_filename) const
std::vector< BackupEntry > ListBackups(const std::string &rom_filename) const
void SetBackupBeforeSave(bool enabled)
void SetBackupFolder(const std::string &folder)
void SetBackupKeepDaily(bool enabled)
void UpdateCurrentRomHash(Rom *rom)
Recompute the hash of the current ROM.
std::vector< RomFileManager::BackupEntry > GetRomBackups(Rom *rom) const
void SetPotItemConfirmPending(int unloaded_rooms, int total_rooms)
Set pot-item confirmation pending (called by SaveRom when needed).
absl::Status CheckRomWritePolicy(Rom *rom, const std::optional< std::string > &target_filename=std::nullopt)
Enforce project write policy; may set pending_rom_write_confirm.
const project::YazeProject * project_
bool IsRomHashMismatch() const
Check whether the ROM hash mismatches the project's expected hash.
absl::Status CheckRomOpenPolicy(Rom *rom)
Validate that the loaded ROM is a safe project target to open/edit.
PotItemSaveDecision ResolvePotItemSaveConfirmation(PotItemSaveDecision decision)
absl::Status CheckOracleRomSafetyPreSave(Rom *rom)
Run Oracle-specific ROM safety preflight before saving.
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 Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
constexpr const char * kRomWriteConfirm
std::string GetBuildOutputProjectRomPath(const project::YazeProject *project)
std::string GetEditableProjectRomPath(const project::YazeProject *project)
RomTargetInfo GetRomTargetInfo(const project::YazeProject *project, const std::string &loaded_path)
bool PathsMatch(const std::string &lhs, const std::string &rhs)
Editors are the view controllers for the application.
std::string ComputeRomHash(const uint8_t *data, size_t size)
Definition rom_hash.cc:70
OracleRomSafetyPreflightResult RunOracleRomSafetyPreflight(Rom *rom, const OracleRomSafetyPreflightOptions &options)
std::string expected_hash
Definition project.h:109
RomWritePolicy write_policy
Definition project.h:110
Modern project structure with comprehensive settings consolidation.
Definition project.h:172
bool project_opened() const
Definition project.h:348
core::HackManifest hack_manifest
Definition project.h:212
std::string hack_manifest_file
Definition project.h:194
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:1486