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 if (!project_ || !project_->project_opened()) {
213 return absl::OkStatus();
214 }
215
216 const auto policy = project_->rom_metadata.write_policy;
217 const auto target_info = GetRomTargetInfo(project_, current_rom_path_);
218 if (target_info.matches_build_output) {
219 if (policy == project::RomWritePolicy::kAllow) {
220 return absl::OkStatus();
221 }
222 if (toast_manager_) {
224 absl::StrFormat(
225 "ROM write blocked: '%s' is the project's build output",
226 target_info.loaded_path.empty() ? current_rom_path_
227 : target_info.loaded_path),
229 }
230 return absl::PermissionDeniedError(absl::StrFormat(
231 "ROM write blocked: '%s' is the project's build output. Edit '%s' "
232 "instead.",
233 target_info.loaded_path.empty() ? current_rom_path_
234 : target_info.loaded_path,
235 GetEditableTargetPath(target_info)));
236 }
237
238 if (target_info.matches_editable_target &&
239 !target_info.matches_build_output) {
240 return absl::OkStatus();
241 }
242
243 const auto expected = NormalizeHash(project_->rom_metadata.expected_hash);
244 const auto actual = NormalizeHash(current_rom_hash_);
245
246 if (expected.empty() || actual.empty() || expected == actual) {
247 return absl::OkStatus();
248 }
249
250 if (policy == project::RomWritePolicy::kAllow) {
251 return absl::OkStatus();
252 }
253
254 if (policy == project::RomWritePolicy::kBlock) {
255 if (toast_manager_) {
257 "ROM write blocked: project expects a different ROM hash",
259 }
260 return absl::PermissionDeniedError(
261 "ROM write blocked by project write policy");
262 }
263
266 if (popup_manager_) {
268 }
269 if (toast_manager_) {
270 toast_manager_->Show("ROM hash mismatch: confirmation required to save",
272 }
273 return absl::CancelledError("ROM write confirmation required");
274 }
275
276 return absl::OkStatus();
277}
278
280 if (!rom || !rom->is_loaded()) {
281 return absl::InvalidArgumentError("ROM not loaded");
282 }
283
284 if (!project_ || !project_->project_opened() ||
287 return absl::OkStatus();
288 }
289
293 options.validate_water_fill_table = true;
294 options.validate_custom_collision_maps = true;
295 options.max_collision_errors = 6;
296
297 const auto preflight = zelda3::RunOracleRomSafetyPreflight(rom, options);
298 if (preflight.ok()) {
299 return absl::OkStatus();
300 }
301
302 const auto& first = preflight.errors.front();
303 if (toast_manager_) {
304 toast_manager_->Show(absl::StrFormat("Oracle ROM safety [%s]: %s",
305 first.code, first.message.c_str()),
307 }
308 return preflight.ToStatus();
309}
310
311// =============================================================================
312// ROM Write Confirmation State Machine
313// =============================================================================
314
319
324
325// =============================================================================
326// Pot Item Save Confirmation
327// =============================================================================
328
330 int total_rooms) {
332 pending_pot_item_unloaded_rooms_ = unloaded_rooms;
333 pending_pot_item_total_rooms_ = total_rooms;
334}
335
338 PotItemSaveDecision decision) {
342
343 if (decision == PotItemSaveDecision::kCancel) {
344 return decision;
345 }
346
349 }
351 return decision;
352}
353
354// =============================================================================
355// Backup Management
356// =============================================================================
357
358std::vector<RomFileManager::BackupEntry> RomLifecycleManager::GetRomBackups(
359 Rom* rom) const {
360 if (!rom || !rom_file_manager_) {
361 return {};
362 }
363 return rom_file_manager_->ListBackups(rom->filename());
364}
365
367 if (!rom) {
368 return absl::FailedPreconditionError("No ROM loaded");
369 }
370 if (!rom_file_manager_) {
371 return absl::FailedPreconditionError("No ROM file manager");
372 }
374}
375
377 const std::string& folder,
378 int retention_count,
379 bool keep_daily,
380 int keep_daily_days) {
382 return;
388}
389
390} // 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:145
auto data() const
Definition rom.h:139
auto size() const
Definition rom.h:138
bool is_loaded() const
Definition rom.h:132
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).
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 CheckRomWritePolicy(Rom *rom)
Enforce project write policy; may set pending_rom_write_confirm.
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:164
bool project_opened() const
Definition project.h:332
core::HackManifest hack_manifest
Definition project.h:204
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:1319