yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
project_manager.cc
Go to the documentation of this file.
1#include "project_manager.h"
2
3#include <filesystem>
4#include <fstream>
5
6#include "absl/strings/str_format.h"
8#include "core/project.h"
9#include "util/macro.h"
10
11namespace yaze {
12namespace editor {
13
15 : toast_manager_(toast_manager) {}
16
18 const std::string& template_name) {
19 // ROM-first workflow: Creating a project requires a ROM to be loaded
20 // The actual project creation happens after ROM selection in the wizard
21
22 if (template_name.empty()) {
23 // Create default project - will be configured after ROM load
25 current_project_.name = "New Project";
27
28 if (toast_manager_) {
29 toast_manager_->Show("New project created - select a ROM to continue",
31 }
32
33 // Mark that we're waiting for ROM selection
35 return absl::OkStatus();
36 }
37
38 return CreateFromTemplate(template_name, "New Project");
39}
40
41absl::Status ProjectManager::OpenProject(const std::string& filename) {
42 if (filename.empty()) {
43 // TODO: Show file dialog
44 return absl::InvalidArgumentError("No filename provided");
45 }
46
47 return LoadProjectFromFile(filename);
48}
49
50absl::Status ProjectManager::LoadProjectFromFile(const std::string& filename) {
51 if (!IsValidProjectFile(filename)) {
52 return absl::InvalidArgumentError(
53 absl::StrFormat("Invalid project file: %s", filename));
54 }
55
56 auto status = current_project_.Open(filename);
57 if (!status.ok()) {
58 if (toast_manager_) {
60 absl::StrFormat("Failed to load project: %s", status.message()),
62 }
63 return status;
64 }
65
66 if (toast_manager_) {
67 toast_manager_->Show(absl::StrFormat("Project loaded: %s",
70 }
71
72 return absl::OkStatus();
73}
74
76 if (!HasActiveProject()) {
77 return absl::FailedPreconditionError("No active project to save");
78 }
79
81}
82
83absl::Status ProjectManager::SaveProjectAs(const std::string& filename) {
84 if (filename.empty()) {
85 // TODO: Show save dialog
86 return absl::InvalidArgumentError("No filename provided for save as");
87 }
88
89 return SaveProjectToFile(filename);
90}
91
92absl::Status ProjectManager::SaveProjectToFile(const std::string& filename) {
93 auto status = current_project_.SaveAs(filename);
94 if (!status.ok()) {
95 if (toast_manager_) {
97 absl::StrFormat("Failed to save project: %s", status.message()),
99 }
100 return status;
101 }
102
103 if (toast_manager_) {
104 toast_manager_->Show(absl::StrFormat("Project saved: %s", filename),
106 }
107
108 return absl::OkStatus();
109}
110
111absl::Status ProjectManager::ImportProject(const std::string& project_path) {
112 if (project_path.empty()) {
113 return absl::InvalidArgumentError("No project path provided");
114 }
115
116#ifndef __EMSCRIPTEN__
117 if (!std::filesystem::exists(project_path)) {
118 return absl::NotFoundError(
119 absl::StrFormat("Project path does not exist: %s", project_path));
120 }
121#endif
122
123 project::YazeProject imported_project;
124
125 // Handle ZScream project imports (.zsproj files)
126 if (project_path.ends_with(".zsproj")) {
127 RETURN_IF_ERROR(imported_project.ImportZScreamProject(project_path));
128 if (toast_manager_) {
130 "ZScream project imported successfully. Please configure ROM and "
131 "folders.",
132 ToastType::kInfo, 5.0f);
133 }
134 } else {
135 // Standard yaze project import
136 RETURN_IF_ERROR(imported_project.Open(project_path));
137 if (toast_manager_) {
139 absl::StrFormat("Project imported: %s", project_path),
141 }
142 }
143
144 current_project_ = std::move(imported_project);
145 return absl::OkStatus();
146}
147
148absl::Status ProjectManager::ExportProject(const std::string& export_path) {
149 if (!HasActiveProject()) {
150 return absl::FailedPreconditionError("No active project to export");
151 }
152
153 if (export_path.empty()) {
154 return absl::InvalidArgumentError("No export path provided");
155 }
156
157 // TODO: Implement project export logic
158 // This would typically create a package with all project files
159
160 if (toast_manager_) {
161 toast_manager_->Show(absl::StrFormat("Project exported: %s", export_path),
163 }
164
165 return absl::OkStatus();
166}
167
169 if (!HasActiveProject()) {
170 return absl::FailedPreconditionError("No active project to repair");
171 }
172
173 // TODO: Implement project repair logic
174 // This would check for missing files, broken references, etc.
175
176 if (toast_manager_) {
177 toast_manager_->Show("Project repair completed", ToastType::kSuccess);
178 }
179
180 return absl::OkStatus();
181}
182
184 if (!HasActiveProject()) {
185 return absl::FailedPreconditionError("No active project to validate");
186 }
187
188 auto result = current_project_.Validate();
189 if (!result.ok()) {
190 if (toast_manager_) {
192 absl::StrFormat("Project validation failed: %s", result.message()),
194 }
195 return result;
196 }
197
198 if (toast_manager_) {
199 toast_manager_->Show("Project validation passed", ToastType::kSuccess);
200 }
201
202 return absl::OkStatus();
203}
204
206 return current_project_.name;
207}
208
211}
212
213std::vector<std::string> ProjectManager::GetAvailableTemplates() const {
214 // TODO: Scan templates directory and return available templates
215 return {"Empty Project", "Dungeon Editor Project", "Overworld Editor Project",
216 "Graphics Editor Project", "Full Editor Project"};
217}
218
220 const std::string& template_name, const std::string& project_name) {
221 if (template_name.empty() || project_name.empty()) {
222 return absl::InvalidArgumentError(
223 "Template name and project name required");
224 }
225
226 // TODO: Implement template-based project creation
227 // This would copy template files and customize them
228
230 current_project_.name = project_name;
232 const std::string preset_name =
233 template_name == "Basic ROM Hack" ? "Vanilla ROM Hack" : template_name;
234 const auto zso_templates = GetZsoTemplates();
235 if (std::any_of(zso_templates.begin(), zso_templates.end(),
236 [&preset_name](const auto& project_template) {
237 return project_template.name == preset_name;
238 })) {
239 RETURN_IF_ERROR(ApplyZsoPreset(preset_name));
240 }
242 pending_template_name_ = template_name;
243
244 if (toast_manager_) {
246 absl::StrFormat("Project created from template: %s", template_name),
248 }
249
250 return absl::OkStatus();
251}
252
254 const std::string& project_name) const {
255 // Convert project name to valid filename
256 std::string filename = project_name;
257 std::replace(filename.begin(), filename.end(), ' ', '_');
258 std::replace(filename.begin(), filename.end(), '/', '_');
259 std::replace(filename.begin(), filename.end(), '\\', '_');
260
261 return absl::StrFormat("%s.yaze", filename);
262}
263
264bool ProjectManager::IsValidProjectFile(const std::string& filename) const {
265 if (filename.empty()) {
266 return false;
267 }
268
269 std::string extension = std::filesystem::path(filename).extension().string();
270
271 // Accept .yazeproj by extension on all platforms. On native builds we can
272 // additionally verify it is a directory; on WASM we trust the extension.
273 if (extension == ".yazeproj") {
274#ifndef __EMSCRIPTEN__
275 return std::filesystem::is_directory(filename);
276#else
277 return true;
278#endif
279 }
280
281#ifndef __EMSCRIPTEN__
282 // Accept a file that lives inside a .yazeproj bundle directory.
283 const std::string bundle_root =
285 if (!bundle_root.empty()) {
286 return true;
287 }
288
289 if (!std::filesystem::exists(filename)) {
290 return false;
291 }
292#endif
293
294 return extension == ".yaze" || extension == ".zsproj";
295}
296
298 const std::string& project_path) {
299#ifdef __EMSCRIPTEN__
300 // WASM uses virtual storage; nothing to provision eagerly.
301 return absl::OkStatus();
302#else
303 try {
304 std::filesystem::create_directories(project_path);
305 std::filesystem::create_directories(project_path + "/assets");
306 std::filesystem::create_directories(project_path + "/scripts");
307 std::filesystem::create_directories(project_path + "/output");
308 return absl::OkStatus();
309 } catch (const std::exception& e) {
310 return absl::InternalError(
311 absl::StrFormat("Failed to create project structure: %s", e.what()));
312 }
313#endif
314}
315
316// ============================================================================
317// ROM-First Workflow Implementation
318// ============================================================================
319
320absl::Status ProjectManager::SetProjectRom(const std::string& rom_path) {
321 if (rom_path.empty()) {
322 return absl::InvalidArgumentError("ROM path cannot be empty");
323 }
324
325#ifndef __EMSCRIPTEN__
326 if (!std::filesystem::exists(rom_path)) {
327 return absl::NotFoundError(
328 absl::StrFormat("ROM file not found: %s", rom_path));
329 }
330#endif
331
333
334 if (toast_manager_) {
336 absl::StrFormat("ROM set for project: %s",
337 std::filesystem::path(rom_path).filename().string()),
339 }
340
341 return absl::OkStatus();
342}
343
345 const std::string& project_name, const std::string& project_path) const {
346 if (!project_path.empty()) {
347#ifndef __EMSCRIPTEN__
348 const std::filesystem::path requested_path(project_path);
349 std::error_code directory_error;
350 if (requested_path.extension() != ".yazeproj" &&
351 std::filesystem::is_directory(requested_path, directory_error)) {
352 return (requested_path / GenerateProjectFilename(project_name)).string();
353 }
354#endif
355 return project_path;
356 }
357 if (!current_project_.rom_filename.empty()) {
358 return (std::filesystem::path(current_project_.rom_filename).parent_path() /
359 GenerateProjectFilename(project_name))
360 .string();
361 }
362 return GenerateProjectFilename(project_name);
363}
364
366 const std::string& project_name, const std::string& project_path) const {
367 if (project_name.empty()) {
368 return absl::InvalidArgumentError("Project name cannot be empty");
369 }
370
371 const std::string target_filepath =
372 ResolveProjectCreationPath(project_name, project_path);
373
374#ifndef __EMSCRIPTEN__
375 std::error_code exists_error;
376 const bool target_exists =
377 std::filesystem::exists(target_filepath, exists_error);
378 if (exists_error) {
379 return absl::InternalError(
380 absl::StrFormat("Could not inspect project file %s: %s",
381 target_filepath, exists_error.message()));
382 }
383 if (target_exists) {
384 return absl::AlreadyExistsError(
385 absl::StrFormat("Project file already exists: %s", target_filepath));
386 }
387#endif
388
389 return absl::OkStatus();
390}
391
393 const std::string& project_name, const std::string& project_path) {
394 RETURN_IF_ERROR(ValidateProjectCreationTarget(project_name, project_path));
395 const std::string target_filepath =
396 ResolveProjectCreationPath(project_name, project_path);
397
398 current_project_.name = project_name;
399 current_project_.filepath = target_filepath;
400
401 std::string project_dir;
402#ifndef __EMSCRIPTEN__
403 project_dir =
404 std::filesystem::path(current_project_.filepath).parent_path().string();
405 if (!project_dir.empty()) {
406 std::error_code directory_error;
407 std::filesystem::create_directories(project_dir, directory_error);
408 if (directory_error) {
409 return absl::InternalError(
410 absl::StrFormat("Could not create project directory %s: %s",
411 project_dir, directory_error.message()));
412 }
413 }
414#endif
415
416 // A project is not complete until its descriptor exists on disk. Save it
417 // before provisioning auxiliary directories so a competing creator cannot
418 // leave orphaned structure after losing the atomic no-clobber race.
420
421 // Initialize project structure if we have a directory.
422 if (!project_dir.empty()) {
423 auto status = InitializeProjectStructure(project_dir);
424 if (!status.ok()) {
425 if (toast_manager_) {
426 toast_manager_->Show("Could not create project directories",
428 }
429 }
430 }
431
434
435 if (toast_manager_) {
436 toast_manager_->Show(absl::StrFormat("Project created: %s", project_name),
438 }
439
440 return absl::OkStatus();
441}
442
448
449 if (toast_manager_) {
450 toast_manager_->Show("Project creation cancelled", ToastType::kInfo);
451 }
452 }
453}
454
460
461// ============================================================================
462// ZSCustomOverworld Presets
463// ============================================================================
464
465std::vector<project::ProjectManager::ProjectTemplate>
467 std::vector<project::ProjectManager::ProjectTemplate> templates;
468
469 // Vanilla ROM Hack
470 {
472 t.name = "Vanilla ROM Hack";
473 t.description =
474 "Standard ROM editing without custom ASM patches. "
475 "Limited to vanilla game features.";
476 t.icon = "MD_GAMEPAD";
479 templates.push_back(t);
480 }
481
482 // ZSCustomOverworld v2
483 {
485 t.name = "ZSCustomOverworld v2";
486 t.description =
487 "Basic overworld expansion with custom BG colors, "
488 "main palettes, and parent system.";
489 t.icon = "MD_MAP";
493 templates.push_back(t);
494 }
495
496 // ZSCustomOverworld v3 (Recommended)
497 {
499 t.name = "ZSCustomOverworld v3";
500 t.description =
501 "Full overworld expansion: wide/tall areas, animated GFX, "
502 "subscreen overlays, and all custom features.";
503 t.icon = "MD_TERRAIN";
513 templates.push_back(t);
514 }
515
516 // Randomizer Compatible
517 {
519 t.name = "Randomizer Compatible";
520 t.description =
521 "Minimal editing preset compatible with ALttP Randomizer. "
522 "Avoids changes that break randomizer compatibility.";
523 t.icon = "MD_SHUFFLE";
526 templates.push_back(t);
527 }
528
529 return templates;
530}
531
532absl::Status ProjectManager::ApplyZsoPreset(const std::string& preset_name) {
533 auto templates = GetZsoTemplates();
534
535 for (const auto& t : templates) {
536 if (t.name == preset_name) {
537 // Apply feature flags from template
538 current_project_.feature_flags = t.template_project.feature_flags;
539
540 if (toast_manager_) {
541 toast_manager_->Show(absl::StrFormat("Applied preset: %s", preset_name),
543 }
544
545 return absl::OkStatus();
546 }
547 }
548
549 return absl::NotFoundError(
550 absl::StrFormat("Unknown preset: %s", preset_name));
551}
552
553} // namespace editor
554} // namespace yaze
std::string GenerateProjectFilename(const std::string &project_name) const
absl::Status FinalizeProjectCreation(const std::string &project_name, const std::string &project_path)
Complete project creation after ROM is loaded.
absl::Status SaveProjectAs(const std::string &filename="")
absl::Status ExportProject(const std::string &export_path)
std::vector< std::string > GetAvailableTemplates() const
absl::Status ValidateProjectCreationTarget(const std::string &project_name, const std::string &project_path=std::string()) const
std::string GetProjectPath() const
std::string ResolveProjectCreationPath(const std::string &project_name, const std::string &project_path) const
absl::Status ApplyZsoPreset(const std::string &preset_name)
Apply a ZSO preset to the current project.
bool IsValidProjectFile(const std::string &filename) const
absl::Status SetProjectRom(const std::string &rom_path)
Set the ROM for the current project.
absl::Status CreateNewProject(const std::string &template_name="")
project::YazeProject current_project_
absl::Status OpenProject(const std::string &filename="")
void RequestRomSelection()
Request ROM selection (triggers callback)
static std::vector< project::ProjectManager::ProjectTemplate > GetZsoTemplates()
Get ZSO-specific project templates.
void CancelPendingProject()
Cancel pending project creation.
ProjectManager(ToastManager *toast_manager)
absl::Status ImportProject(const std::string &project_path)
std::function< void()> rom_selection_callback_
absl::Status LoadProjectFromFile(const std::string &filename)
absl::Status InitializeProjectStructure(const std::string &project_path)
absl::Status CreateFromTemplate(const std::string &template_name, const std::string &project_name)
absl::Status SaveProjectToFile(const std::string &filename)
std::string GetProjectName() const
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
struct yaze::core::FeatureFlags::Flags::Overworld overworld
Modern project structure with comprehensive settings consolidation.
Definition project.h:172
static std::string ResolveBundleRoot(const std::string &path)
Definition project.cc:403
absl::Status ImportZScreamProject(const std::string &zscream_project_path)
Definition project.cc:1294
std::string GetDisplayName() const
Definition project.cc:1459
absl::Status SaveAs(const std::string &new_path)
Definition project.cc:626
absl::Status SaveNew()
Definition project.cc:591
absl::Status Open(const std::string &project_path)
Definition project.cc:429
absl::Status Validate() const
Definition project.cc:1355
core::FeatureFlags::Flags feature_flags
Definition project.h:200