yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
project.cc
Go to the documentation of this file.
1#include "core/project.h"
2
3#include <cctype>
4#include <chrono>
5#include <filesystem>
6#include <fstream>
7#include <iomanip>
8#include <sstream>
9
10#include "absl/strings/str_format.h"
11#include "absl/strings/str_join.h"
12#include "absl/strings/str_split.h"
13#include "app/gui/core/icons.h"
14#include "imgui/imgui.h"
15#include "util/file_util.h"
16#include "util/json.h"
17#include "util/log.h"
18#include "util/macro.h"
19#include "util/platform_paths.h"
20#include "yaze_config.h"
22
23#ifdef __EMSCRIPTEN__
25#endif
26
27// #ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
28// #include "nlohmann/json.hpp"
29// using json = nlohmann::json;
30// #endif
31
32namespace yaze {
33namespace project {
34
35namespace {
36// Helper functions for parsing key-value pairs
37std::pair<std::string, std::string> ParseKeyValue(const std::string& line) {
38 size_t eq_pos = line.find('=');
39 if (eq_pos == std::string::npos)
40 return {"", ""};
41
42 std::string key = line.substr(0, eq_pos);
43 std::string value = line.substr(eq_pos + 1);
44
45 // Trim whitespace
46 key.erase(0, key.find_first_not_of(" \t"));
47 key.erase(key.find_last_not_of(" \t") + 1);
48 value.erase(0, value.find_first_not_of(" \t"));
49 value.erase(value.find_last_not_of(" \t") + 1);
50
51 return {key, value};
52}
53
54bool ParseBool(const std::string& value) {
55 return value == "true" || value == "1" || value == "yes";
56}
57
58float ParseFloat(const std::string& value) {
59 try {
60 return std::stof(value);
61 } catch (...) {
62 return 0.0f;
63 }
64}
65
66std::vector<std::string> ParseStringList(const std::string& value) {
67 std::vector<std::string> result;
68 if (value.empty())
69 return result;
70
71 std::vector<std::string> parts = absl::StrSplit(value, ',');
72 for (const auto& part : parts) {
73 std::string trimmed = std::string(part);
74 trimmed.erase(0, trimmed.find_first_not_of(" \t"));
75 trimmed.erase(trimmed.find_last_not_of(" \t") + 1);
76 if (!trimmed.empty()) {
77 result.push_back(trimmed);
78 }
79 }
80 return result;
81}
82
83std::string SanitizeStorageKey(absl::string_view input) {
84 std::string key(input);
85 for (char& c : key) {
86 if (!std::isalnum(static_cast<unsigned char>(c))) {
87 c = '_';
88 }
89 }
90 if (key.empty()) {
91 key = "project";
92 }
93 return key;
94}
95} // namespace
96
97// YazeProject Implementation
98absl::Status YazeProject::Create(const std::string& project_name,
99 const std::string& base_path) {
100 name = project_name;
101 filepath = base_path + "/" + project_name + ".yaze";
102
103 // Initialize metadata
104 auto now = std::chrono::system_clock::now();
105 auto time_t = std::chrono::system_clock::to_time_t(now);
106 std::stringstream ss;
107 ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
108
109 metadata.created_date = ss.str();
110 metadata.last_modified = ss.str();
111 metadata.yaze_version = "0.3.2"; // TODO: Get from version header
112 metadata.version = "2.0";
113 metadata.created_by = "YAZE";
115
117
118#ifndef __EMSCRIPTEN__
119 // Create project directory structure
120 std::filesystem::path project_dir(base_path + "/" + project_name);
121 std::filesystem::create_directories(project_dir);
122 std::filesystem::create_directories(project_dir / "code");
123 std::filesystem::create_directories(project_dir / "assets");
124 std::filesystem::create_directories(project_dir / "patches");
125 std::filesystem::create_directories(project_dir / "backups");
126 std::filesystem::create_directories(project_dir / "output");
127
128 // Set folder paths
129 code_folder = (project_dir / "code").string();
130 assets_folder = (project_dir / "assets").string();
131 patches_folder = (project_dir / "patches").string();
132 rom_backup_folder = (project_dir / "backups").string();
133 output_folder = (project_dir / "output").string();
134 labels_filename = (project_dir / "labels.txt").string();
135 symbols_filename = (project_dir / "symbols.txt").string();
136#else
137 // WASM: keep paths relative; persistence handled by WasmStorage/IDBFS
138 code_folder = "code";
139 assets_folder = "assets";
140 patches_folder = "patches";
141 rom_backup_folder = "backups";
142 output_folder = "output";
143 labels_filename = "labels.txt";
144 symbols_filename = "symbols.txt";
145#endif
146
147 return Save();
148}
149
150absl::Status YazeProject::Open(const std::string& project_path) {
151 filepath = project_path;
152
153#ifdef __EMSCRIPTEN__
154 // Prefer persistent storage in WASM builds
155 auto storage_key = MakeStorageKey("project");
156 auto storage_or = platform::WasmStorage::LoadProject(storage_key);
157 if (storage_or.ok()) {
158 return ParseFromString(storage_or.value());
159 }
160#endif
161
162 // Determine format and load accordingly
163 if (project_path.ends_with(".yaze")) {
165
166 // Try to detect if it's JSON format by peeking at first character
167 std::ifstream file(project_path);
168 if (file.is_open()) {
169 std::stringstream buffer;
170 buffer << file.rdbuf();
171 std::string content = buffer.str();
172
173#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
174 if (!content.empty() && content.front() == '{') {
175 LOG_DEBUG("Project", "Detected JSON format project file");
176 return LoadFromJsonFormat(project_path);
177 }
178#endif
179
180 return ParseFromString(content);
181 }
182
183 return absl::InvalidArgumentError(
184 absl::StrFormat("Cannot open project file: %s", project_path));
185 } else if (project_path.ends_with(".zsproj")) {
187 return ImportFromZScreamFormat(project_path);
188 }
189
190 return absl::InvalidArgumentError("Unsupported project file format");
191}
192
193absl::Status YazeProject::Save() {
194 return SaveToYazeFormat();
195}
196
197absl::Status YazeProject::SaveAs(const std::string& new_path) {
198 std::string old_filepath = filepath;
199 filepath = new_path;
200
201 auto status = Save();
202 if (!status.ok()) {
203 filepath = old_filepath; // Restore on failure
204 }
205
206 return status;
207}
208
209std::string YazeProject::MakeStorageKey(absl::string_view suffix) const {
210 std::string base;
211 if (!metadata.project_id.empty()) {
212 base = metadata.project_id;
213 } else if (!name.empty()) {
214 base = name;
215 } else if (!filepath.empty()) {
216 base = std::filesystem::path(filepath).stem().string();
217 }
218 base = SanitizeStorageKey(base);
219 if (suffix.empty()) {
220 return base;
221 }
222 return absl::StrFormat("%s_%s", base, suffix);
223}
224
225absl::StatusOr<std::string> YazeProject::SerializeToString() const {
226 std::ostringstream file;
227
228 // Write header comment
229 file << "# yaze Project File\n";
230 file << "# Format Version: 2.0\n";
231 file << "# Generated by YAZE " << metadata.yaze_version << "\n";
232 file << "# Last Modified: " << metadata.last_modified << "\n\n";
233
234 // Project section
235 file << "[project]\n";
236 file << "name=" << name << "\n";
237 file << "description=" << metadata.description << "\n";
238 file << "author=" << metadata.author << "\n";
239 file << "license=" << metadata.license << "\n";
240 file << "version=" << metadata.version << "\n";
241 file << "created_date=" << metadata.created_date << "\n";
242 file << "last_modified=" << metadata.last_modified << "\n";
243 file << "yaze_version=" << metadata.yaze_version << "\n";
244 file << "created_by=" << metadata.created_by << "\n";
245 file << "project_id=" << metadata.project_id << "\n";
246 file << "tags=" << absl::StrJoin(metadata.tags, ",") << "\n\n";
247
248 // Files section
249 file << "[files]\n";
250 file << "rom_filename=" << GetRelativePath(rom_filename) << "\n";
251 file << "rom_backup_folder=" << GetRelativePath(rom_backup_folder) << "\n";
252 file << "code_folder=" << GetRelativePath(code_folder) << "\n";
253 file << "assets_folder=" << GetRelativePath(assets_folder) << "\n";
254 file << "patches_folder=" << GetRelativePath(patches_folder) << "\n";
255 file << "labels_filename=" << GetRelativePath(labels_filename) << "\n";
256 file << "symbols_filename=" << GetRelativePath(symbols_filename) << "\n";
257 file << "output_folder=" << GetRelativePath(output_folder) << "\n";
258 file << "custom_objects_folder=" << GetRelativePath(custom_objects_folder)
259 << "\n";
260 file << "additional_roms=" << absl::StrJoin(additional_roms, ",") << "\n\n";
261
262 // Feature flags section
263 file << "[feature_flags]\n";
264 file << "load_custom_overworld="
265 << (feature_flags.overworld.kLoadCustomOverworld ? "true" : "false")
266 << "\n";
267 file << "apply_zs_custom_overworld_asm="
269 : "false")
270 << "\n";
271 file << "save_dungeon_maps="
272 << (feature_flags.kSaveDungeonMaps ? "true" : "false") << "\n";
273 file << "save_graphics_sheet="
274 << (feature_flags.kSaveGraphicsSheet ? "true" : "false") << "\n";
275 file << "enable_custom_objects="
276 << (feature_flags.kEnableCustomObjects ? "true" : "false") << "\n\n";
277
278 // Workspace settings section
279 file << "[workspace]\n";
280 file << "font_global_scale=" << workspace_settings.font_global_scale << "\n";
281 file << "dark_mode=" << (workspace_settings.dark_mode ? "true" : "false")
282 << "\n";
283 file << "ui_theme=" << workspace_settings.ui_theme << "\n";
284 file << "autosave_enabled="
285 << (workspace_settings.autosave_enabled ? "true" : "false") << "\n";
286 file << "autosave_interval_secs=" << workspace_settings.autosave_interval_secs
287 << "\n";
288 file << "backup_on_save="
289 << (workspace_settings.backup_on_save ? "true" : "false") << "\n";
290 file << "show_grid=" << (workspace_settings.show_grid ? "true" : "false")
291 << "\n";
292 file << "show_collision="
293 << (workspace_settings.show_collision ? "true" : "false") << "\n";
294 file << "prefer_hmagic_names="
295 << (workspace_settings.prefer_hmagic_names ? "true" : "false") << "\n";
296 file << "last_layout_preset=" << workspace_settings.last_layout_preset
297 << "\n";
298 file << "saved_layouts="
299 << absl::StrJoin(workspace_settings.saved_layouts, ",") << "\n";
300 file << "recent_files=" << absl::StrJoin(workspace_settings.recent_files, ",")
301 << "\n\n";
302
303 // AI Agent settings section
304 file << "[agent_settings]\n";
305 file << "ai_provider=" << agent_settings.ai_provider << "\n";
306 file << "ai_model=" << agent_settings.ai_model << "\n";
307 file << "ollama_host=" << agent_settings.ollama_host << "\n";
308 file << "gemini_api_key=" << agent_settings.gemini_api_key << "\n";
309 file << "custom_system_prompt="
311 file << "use_custom_prompt="
312 << (agent_settings.use_custom_prompt ? "true" : "false") << "\n";
313 file << "show_reasoning="
314 << (agent_settings.show_reasoning ? "true" : "false") << "\n";
315 file << "verbose=" << (agent_settings.verbose ? "true" : "false") << "\n";
316 file << "max_tool_iterations=" << agent_settings.max_tool_iterations << "\n";
317 file << "max_retry_attempts=" << agent_settings.max_retry_attempts << "\n";
318 file << "temperature=" << agent_settings.temperature << "\n";
319 file << "top_p=" << agent_settings.top_p << "\n";
320 file << "max_output_tokens=" << agent_settings.max_output_tokens << "\n";
321 file << "stream_responses="
322 << (agent_settings.stream_responses ? "true" : "false") << "\n";
323 file << "favorite_models="
324 << absl::StrJoin(agent_settings.favorite_models, ",") << "\n";
325 file << "model_chain=" << absl::StrJoin(agent_settings.model_chain, ",")
326 << "\n";
327 file << "chain_mode=" << agent_settings.chain_mode << "\n";
328 file << "enable_tool_resources="
329 << (agent_settings.enable_tool_resources ? "true" : "false") << "\n";
330 file << "enable_tool_dungeon="
331 << (agent_settings.enable_tool_dungeon ? "true" : "false") << "\n";
332 file << "enable_tool_overworld="
333 << (agent_settings.enable_tool_overworld ? "true" : "false") << "\n";
334 file << "enable_tool_messages="
335 << (agent_settings.enable_tool_messages ? "true" : "false") << "\n";
336 file << "enable_tool_dialogue="
337 << (agent_settings.enable_tool_dialogue ? "true" : "false") << "\n";
338 file << "enable_tool_gui="
339 << (agent_settings.enable_tool_gui ? "true" : "false") << "\n";
340 file << "enable_tool_music="
341 << (agent_settings.enable_tool_music ? "true" : "false") << "\n";
342 file << "enable_tool_sprite="
343 << (agent_settings.enable_tool_sprite ? "true" : "false") << "\n";
344 file << "enable_tool_emulator="
345 << (agent_settings.enable_tool_emulator ? "true" : "false") << "\n";
346 file << "builder_blueprint_path=" << agent_settings.builder_blueprint_path
347 << "\n\n";
348
349 // Custom keybindings section
351 file << "[keybindings]\n";
352 for (const auto& [key, value] : workspace_settings.custom_keybindings) {
353 file << key << "=" << value << "\n";
354 }
355 file << "\n";
356 }
357
358 // Editor visibility section
360 file << "[editor_visibility]\n";
361 for (const auto& [key, value] : workspace_settings.editor_visibility) {
362 file << key << "=" << (value ? "true" : "false") << "\n";
363 }
364 file << "\n";
365 }
366
367 // Resource labels sections
368 for (const auto& [type, labels] : resource_labels) {
369 if (!labels.empty()) {
370 file << "[labels_" << type << "]\n";
371 for (const auto& [key, value] : labels) {
372 file << key << "=" << value << "\n";
373 }
374 file << "\n";
375 }
376 }
377
378 // Build settings section
379 file << "[build]\n";
380 file << "build_script=" << build_script << "\n";
381 file << "output_folder=" << GetRelativePath(output_folder) << "\n";
382 file << "git_repository=" << git_repository << "\n";
383 file << "track_changes=" << (track_changes ? "true" : "false") << "\n";
384 file << "build_configurations=" << absl::StrJoin(build_configurations, ",")
385 << "\n";
386 file << "build_target=" << build_target << "\n";
387 file << "asm_entry_point=" << asm_entry_point << "\n";
388 file << "asm_sources=" << absl::StrJoin(asm_sources, ",") << "\n";
389 file << "last_build_hash=" << last_build_hash << "\n";
390 file << "build_number=" << build_number << "\n\n";
391
392 // Music persistence section (for WASM/offline state)
393 file << "[music]\n";
394 file << "persist_custom_music="
395 << (music_persistence.persist_custom_music ? "true" : "false") << "\n";
396 file << "storage_key=" << music_persistence.storage_key << "\n";
397 file << "last_saved_at=" << music_persistence.last_saved_at << "\n\n";
398
399 // ZScream compatibility section
400 if (!zscream_project_file.empty()) {
401 file << "[zscream_compatibility]\n";
402 file << "original_project_file=" << zscream_project_file << "\n";
403 for (const auto& [key, value] : zscream_mappings) {
404 file << key << "=" << value << "\n";
405 }
406 file << "\n";
407 }
408
409 file << "# End of YAZE Project File\n";
410 return file.str();
411}
412
413absl::Status YazeProject::ParseFromString(const std::string& content) {
414 std::istringstream stream(content);
415 std::string line;
416 std::string current_section;
417
418 while (std::getline(stream, line)) {
419 if (line.empty() || line[0] == '#')
420 continue;
421
422 if (line.front() == '[' && line.back() == ']') {
423 current_section = line.substr(1, line.length() - 2);
424 continue;
425 }
426
427 auto [key, value] = ParseKeyValue(line);
428 if (key.empty())
429 continue;
430
431 if (current_section == "project") {
432 if (key == "name")
433 name = value;
434 else if (key == "description")
435 metadata.description = value;
436 else if (key == "author")
437 metadata.author = value;
438 else if (key == "license")
439 metadata.license = value;
440 else if (key == "version")
441 metadata.version = value;
442 else if (key == "created_date")
443 metadata.created_date = value;
444 else if (key == "last_modified")
445 metadata.last_modified = value;
446 else if (key == "yaze_version")
447 metadata.yaze_version = value;
448 else if (key == "created_by")
449 metadata.created_by = value;
450 else if (key == "tags")
451 metadata.tags = ParseStringList(value);
452 else if (key == "project_id")
453 metadata.project_id = value;
454 } else if (current_section == "files") {
455 if (key == "rom_filename")
456 rom_filename = value;
457 else if (key == "rom_backup_folder")
458 rom_backup_folder = value;
459 else if (key == "code_folder")
460 code_folder = value;
461 else if (key == "assets_folder")
462 assets_folder = value;
463 else if (key == "patches_folder")
464 patches_folder = value;
465 else if (key == "labels_filename")
466 labels_filename = value;
467 else if (key == "symbols_filename")
468 symbols_filename = value;
469 else if (key == "output_folder")
470 output_folder = value;
471 else if (key == "custom_objects_folder")
472 custom_objects_folder = value;
473 else if (key == "additional_roms")
474 additional_roms = ParseStringList(value);
475 } else if (current_section == "feature_flags") {
476 if (key == "load_custom_overworld")
478 else if (key == "apply_zs_custom_overworld_asm")
480 else if (key == "save_dungeon_maps")
481 feature_flags.kSaveDungeonMaps = ParseBool(value);
482 else if (key == "save_graphics_sheet")
483 feature_flags.kSaveGraphicsSheet = ParseBool(value);
484 else if (key == "enable_custom_objects")
485 feature_flags.kEnableCustomObjects = ParseBool(value);
486 } else if (current_section == "workspace") {
487 if (key == "font_global_scale")
488 workspace_settings.font_global_scale = ParseFloat(value);
489 else if (key == "dark_mode")
490 workspace_settings.dark_mode = ParseBool(value);
491 else if (key == "ui_theme")
493 else if (key == "autosave_enabled")
494 workspace_settings.autosave_enabled = ParseBool(value);
495 else if (key == "autosave_interval_secs")
496 workspace_settings.autosave_interval_secs = ParseFloat(value);
497 else if (key == "backup_on_save")
498 workspace_settings.backup_on_save = ParseBool(value);
499 else if (key == "show_grid")
500 workspace_settings.show_grid = ParseBool(value);
501 else if (key == "show_collision")
502 workspace_settings.show_collision = ParseBool(value);
503 else if (key == "prefer_hmagic_names")
504 workspace_settings.prefer_hmagic_names = ParseBool(value);
505 else if (key == "last_layout_preset")
507 else if (key == "saved_layouts")
508 workspace_settings.saved_layouts = ParseStringList(value);
509 else if (key == "recent_files")
510 workspace_settings.recent_files = ParseStringList(value);
511 } else if (current_section == "agent_settings") {
512 if (key == "ai_provider")
514 else if (key == "ai_model")
515 agent_settings.ai_model = value;
516 else if (key == "ollama_host")
518 else if (key == "gemini_api_key")
520 else if (key == "custom_system_prompt")
522 else if (key == "use_custom_prompt")
523 agent_settings.use_custom_prompt = ParseBool(value);
524 else if (key == "show_reasoning")
525 agent_settings.show_reasoning = ParseBool(value);
526 else if (key == "verbose")
527 agent_settings.verbose = ParseBool(value);
528 else if (key == "max_tool_iterations")
529 agent_settings.max_tool_iterations = std::stoi(value);
530 else if (key == "max_retry_attempts")
531 agent_settings.max_retry_attempts = std::stoi(value);
532 else if (key == "temperature")
533 agent_settings.temperature = ParseFloat(value);
534 else if (key == "top_p")
535 agent_settings.top_p = ParseFloat(value);
536 else if (key == "max_output_tokens")
537 agent_settings.max_output_tokens = std::stoi(value);
538 else if (key == "stream_responses")
539 agent_settings.stream_responses = ParseBool(value);
540 else if (key == "favorite_models")
541 agent_settings.favorite_models = ParseStringList(value);
542 else if (key == "model_chain")
543 agent_settings.model_chain = ParseStringList(value);
544 else if (key == "chain_mode")
545 agent_settings.chain_mode = std::stoi(value);
546 else if (key == "enable_tool_resources")
547 agent_settings.enable_tool_resources = ParseBool(value);
548 else if (key == "enable_tool_dungeon")
549 agent_settings.enable_tool_dungeon = ParseBool(value);
550 else if (key == "enable_tool_overworld")
551 agent_settings.enable_tool_overworld = ParseBool(value);
552 else if (key == "enable_tool_messages")
553 agent_settings.enable_tool_messages = ParseBool(value);
554 else if (key == "enable_tool_dialogue")
555 agent_settings.enable_tool_dialogue = ParseBool(value);
556 else if (key == "enable_tool_gui")
557 agent_settings.enable_tool_gui = ParseBool(value);
558 else if (key == "enable_tool_music")
559 agent_settings.enable_tool_music = ParseBool(value);
560 else if (key == "enable_tool_sprite")
561 agent_settings.enable_tool_sprite = ParseBool(value);
562 else if (key == "enable_tool_emulator")
563 agent_settings.enable_tool_emulator = ParseBool(value);
564 else if (key == "builder_blueprint_path")
566 } else if (current_section == "build") {
567 if (key == "build_script")
568 build_script = value;
569 else if (key == "output_folder")
570 output_folder = value;
571 else if (key == "git_repository")
572 git_repository = value;
573 else if (key == "track_changes")
574 track_changes = ParseBool(value);
575 else if (key == "build_configurations")
576 build_configurations = ParseStringList(value);
577 else if (key == "build_target")
578 build_target = value;
579 else if (key == "asm_entry_point")
580 asm_entry_point = value;
581 else if (key == "asm_sources")
582 asm_sources = ParseStringList(value);
583 else if (key == "last_build_hash")
584 last_build_hash = value;
585 else if (key == "build_number")
586 build_number = std::stoi(value);
587 } else if (current_section.rfind("labels_", 0) == 0) {
588 std::string label_type = current_section.substr(7);
589 resource_labels[label_type][key] = value;
590 } else if (current_section == "keybindings") {
592 } else if (current_section == "editor_visibility") {
593 workspace_settings.editor_visibility[key] = ParseBool(value);
594 } else if (current_section == "zscream_compatibility") {
595 if (key == "original_project_file")
596 zscream_project_file = value;
597 else
598 zscream_mappings[key] = value;
599 } else if (current_section == "music") {
600 if (key == "persist_custom_music")
601 music_persistence.persist_custom_music = ParseBool(value);
602 else if (key == "storage_key")
604 else if (key == "last_saved_at")
606 }
607 }
608
609 if (metadata.project_id.empty()) {
611 }
612 if (metadata.created_by.empty()) {
613 metadata.created_by = "YAZE";
614 }
615 if (music_persistence.storage_key.empty()) {
617 }
618
619 return absl::OkStatus();
620}
621
622absl::Status YazeProject::LoadFromYazeFormat(const std::string& project_path) {
623#ifdef __EMSCRIPTEN__
624 auto storage_key = MakeStorageKey("project");
625 auto storage_or = platform::WasmStorage::LoadProject(storage_key);
626 if (storage_or.ok()) {
627 return ParseFromString(storage_or.value());
628 }
629#endif // __EMSCRIPTEN__
630
631 std::ifstream file(project_path);
632 if (!file.is_open()) {
633 return absl::InvalidArgumentError(
634 absl::StrFormat("Cannot open project file: %s", project_path));
635 }
636
637 std::stringstream buffer;
638 buffer << file.rdbuf();
639 file.close();
640 return ParseFromString(buffer.str());
641}
642
644 // Update last modified timestamp
645 auto now = std::chrono::system_clock::now();
646 auto time_t = std::chrono::system_clock::to_time_t(now);
647 std::stringstream ss;
648 ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
649 metadata.last_modified = ss.str();
650 if (music_persistence.storage_key.empty()) {
652 }
653
654 ASSIGN_OR_RETURN(auto serialized, SerializeToString());
655
656#ifdef __EMSCRIPTEN__
657 auto storage_status =
658 platform::WasmStorage::SaveProject(MakeStorageKey("project"), serialized);
659 if (!storage_status.ok()) {
660 return storage_status;
661 }
662#else
663 if (!filepath.empty()) {
664 std::ofstream file(filepath);
665 if (!file.is_open()) {
666 return absl::InvalidArgumentError(
667 absl::StrFormat("Cannot create project file: %s", filepath));
668 }
669 file << serialized;
670 file.close();
671 }
672#endif
673
674 return absl::OkStatus();
675}
676
678 const std::string& zscream_project_path) {
679 // Basic ZScream project import (to be expanded based on ZScream format)
680 zscream_project_file = zscream_project_path;
682
683 // Extract project name from path
684 std::filesystem::path zs_path(zscream_project_path);
685 name = zs_path.stem().string() + "_imported";
686
687 // Set up basic mapping for common fields
688 zscream_mappings["rom_file"] = "rom_filename";
689 zscream_mappings["source_code"] = "code_folder";
690 zscream_mappings["project_name"] = "name";
691
693
694 // TODO: Implement actual ZScream format parsing when format is known
695 // For now, just create a project structure that can be manually configured
696
697 return absl::OkStatus();
698}
699
700absl::Status YazeProject::ExportForZScream(const std::string& target_path) {
701 // Create a simplified project file that ZScream might understand
702 std::ofstream file(target_path);
703 if (!file.is_open()) {
704 return absl::InvalidArgumentError(
705 absl::StrFormat("Cannot create ZScream project file: %s", target_path));
706 }
707
708 // Write in a simple format that ZScream might understand
709 file << "# ZScream Compatible Project File\n";
710 file << "# Exported from YAZE " << metadata.yaze_version << "\n\n";
711 file << "name=" << name << "\n";
712 file << "rom_file=" << rom_filename << "\n";
713 file << "source_code=" << code_folder << "\n";
714 file << "description=" << metadata.description << "\n";
715 file << "author=" << metadata.author << "\n";
716 file << "created_with=YAZE " << metadata.yaze_version << "\n";
717
718 file.close();
719 return absl::OkStatus();
720}
721
723 // Consolidated loading of all settings from project file
724 // This replaces scattered config loading throughout the application
726}
727
729 // Consolidated saving of all settings to project file
730 return SaveToYazeFormat();
731}
732
735 return Save();
736}
737
738absl::Status YazeProject::Validate() const {
739 std::vector<std::string> errors;
740
741 if (name.empty())
742 errors.push_back("Project name is required");
743 if (filepath.empty())
744 errors.push_back("Project file path is required");
745 if (rom_filename.empty())
746 errors.push_back("ROM file is required");
747
748#ifndef __EMSCRIPTEN__
749 // Check if files exist
750 if (!rom_filename.empty() &&
751 !std::filesystem::exists(GetAbsolutePath(rom_filename))) {
752 errors.push_back("ROM file does not exist: " + rom_filename);
753 }
754
755 if (!code_folder.empty() &&
756 !std::filesystem::exists(GetAbsolutePath(code_folder))) {
757 errors.push_back("Code folder does not exist: " + code_folder);
758 }
759
760 if (!labels_filename.empty() &&
761 !std::filesystem::exists(GetAbsolutePath(labels_filename))) {
762 errors.push_back("Labels file does not exist: " + labels_filename);
763 }
764#endif // __EMSCRIPTEN__
765
766 if (!errors.empty()) {
767 return absl::InvalidArgumentError(absl::StrJoin(errors, "; "));
768 }
769
770 return absl::OkStatus();
771}
772
773std::vector<std::string> YazeProject::GetMissingFiles() const {
774 std::vector<std::string> missing;
775
776#ifndef __EMSCRIPTEN__
777 if (!rom_filename.empty() &&
778 !std::filesystem::exists(GetAbsolutePath(rom_filename))) {
779 missing.push_back(rom_filename);
780 }
781 if (!labels_filename.empty() &&
782 !std::filesystem::exists(GetAbsolutePath(labels_filename))) {
783 missing.push_back(labels_filename);
784 }
785 if (!symbols_filename.empty() &&
786 !std::filesystem::exists(GetAbsolutePath(symbols_filename))) {
787 missing.push_back(symbols_filename);
788 }
789#endif // __EMSCRIPTEN__
790
791 return missing;
792}
793
795#ifdef __EMSCRIPTEN__
796 // In the web build, filesystem layout is virtual; nothing to repair eagerly.
797 return absl::OkStatus();
798#else
799 // Create missing directories
800 std::vector<std::string> folders = {code_folder, assets_folder,
803
804 for (const auto& folder : folders) {
805 if (!folder.empty()) {
806 std::filesystem::path abs_path = GetAbsolutePath(folder);
807 if (!std::filesystem::exists(abs_path)) {
808 std::filesystem::create_directories(abs_path);
809 }
810 }
811 }
812
813 // Create missing files with defaults
814 if (!labels_filename.empty()) {
815 std::filesystem::path abs_labels = GetAbsolutePath(labels_filename);
816 if (!std::filesystem::exists(abs_labels)) {
817 std::ofstream labels_file(abs_labels);
818 labels_file << "# yaze Resource Labels\n";
819 labels_file << "# Format: [type] key=value\n\n";
820 labels_file.close();
821 }
822 }
823
824 return absl::OkStatus();
825#endif
826}
827
828std::string YazeProject::GetDisplayName() const {
829 if (!metadata.description.empty()) {
830 return metadata.description;
831 }
832 return name.empty() ? "Untitled Project" : name;
833}
834
836 const std::string& absolute_path) const {
837 if (absolute_path.empty() || filepath.empty())
838 return absolute_path;
839
840 std::filesystem::path project_dir =
841 std::filesystem::path(filepath).parent_path();
842 std::filesystem::path abs_path(absolute_path);
843
844 try {
845 std::filesystem::path relative =
846 std::filesystem::relative(abs_path, project_dir);
847 return relative.string();
848 } catch (...) {
849 return absolute_path; // Return absolute path if relative conversion fails
850 }
851}
852
854 const std::string& relative_path) const {
855 if (relative_path.empty() || filepath.empty())
856 return relative_path;
857
858 std::filesystem::path project_dir =
859 std::filesystem::path(filepath).parent_path();
860 std::filesystem::path abs_path = project_dir / relative_path;
861
862 return abs_path.string();
863}
864
866 return name.empty() && rom_filename.empty() && code_folder.empty();
867}
868
870 const std::string& project_path) {
871 // TODO: Implement ZScream format parsing when format specification is
872 // available For now, create a basic project that can be manually configured
873
874 std::filesystem::path zs_path(project_path);
875 name = zs_path.stem().string() + "_imported";
876 zscream_project_file = project_path;
877
879
880 return absl::OkStatus();
881}
882
884 if (metadata.project_id.empty()) {
886 }
887
888 // Initialize default feature flags
893 // REMOVED: kLogInstructions (deprecated)
894
895 // Initialize default workspace settings
898 workspace_settings.ui_theme = "default";
900 workspace_settings.autosave_interval_secs = 300.0f; // 5 minutes
904
905 // Initialize default build configurations
906 build_configurations = {"Debug", "Release", "Distribution"};
907 build_target.clear();
908 asm_entry_point = "asm/main.asm";
909 asm_sources = {"asm"};
910 last_build_hash.clear();
911 build_number = 0;
912
913 track_changes = true;
914
918
919 if (metadata.created_by.empty()) {
920 metadata.created_by = "YAZE";
921 }
922}
923
925 auto now = std::chrono::system_clock::now().time_since_epoch();
926 auto timestamp =
927 std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
928 return absl::StrFormat("yaze_project_%lld", timestamp);
929}
930
931// ProjectManager Implementation
932std::vector<ProjectManager::ProjectTemplate>
934 std::vector<ProjectTemplate> templates;
935
936 // ==========================================================================
937 // ZSCustomOverworld Templates (Recommended)
938 // ==========================================================================
939
940 // Vanilla ROM Hack - no ZSO
941 {
943 t.name = "Vanilla ROM Hack";
944 t.description =
945 "Standard ROM editing without custom ASM. Limited to vanilla features.";
949 false;
954 templates.push_back(t);
955 }
956
957 // ZSCustomOverworld v2 - Basic expansion
958 {
960 t.name = "ZSCustomOverworld v2";
961 t.description =
962 "Basic overworld expansion: custom BG colors, main palettes, parent "
963 "system.";
964 t.icon = ICON_MD_MAP;
967 true;
974 t.template_project.metadata.tags = {"zso_v2", "overworld", "expansion"};
975 templates.push_back(t);
976 }
977
978 // ZSCustomOverworld v3 - Full features (Recommended)
979 {
981 t.name = "ZSCustomOverworld v3 (Recommended)";
982 t.description =
983 "Full overworld expansion: wide/tall areas, animated GFX, overlays, "
984 "all features.";
988 true;
998 t.template_project.metadata.tags = {"zso_v3", "overworld", "full",
999 "recommended"};
1000 templates.push_back(t);
1001 }
1002
1003 // Randomizer Compatible
1004 {
1006 t.name = "Randomizer Compatible";
1007 t.description =
1008 "Compatible with ALttP Randomizer. Minimal custom features to avoid "
1009 "conflicts.";
1013 false;
1016 t.template_project.metadata.tags = {"randomizer", "compatible", "minimal"};
1017 templates.push_back(t);
1018 }
1019
1020 // ==========================================================================
1021 // Editor-Focused Templates
1022 // ==========================================================================
1023
1024 // Dungeon Designer
1025 {
1027 t.name = "Dungeon Designer";
1028 t.description = "Focused on dungeon creation and modification.";
1029 t.icon = ICON_MD_DOMAIN;
1033 "dungeon_default";
1034 t.template_project.metadata.tags = {"dungeons", "rooms", "design"};
1035 templates.push_back(t);
1036 }
1037
1038 // Graphics Pack
1039 {
1041 t.name = "Graphics Pack";
1042 t.description =
1043 "Project focused on graphics, sprites, and visual modifications.";
1050 "graphics_default";
1051 t.template_project.metadata.tags = {"graphics", "sprites", "palettes"};
1052 templates.push_back(t);
1053 }
1054
1055 // Complete Overhaul
1056 {
1058 t.name = "Complete Overhaul";
1059 t.description = "Full-scale ROM hack with all features enabled.";
1060 t.icon = ICON_MD_BUILD;
1063 true;
1073 t.template_project.metadata.tags = {"complete", "overhaul", "full-mod"};
1074 templates.push_back(t);
1075 }
1076
1077 return templates;
1078}
1079
1080absl::StatusOr<YazeProject> ProjectManager::CreateFromTemplate(
1081 const std::string& template_name, const std::string& project_name,
1082 const std::string& base_path) {
1083 YazeProject project;
1084 auto status = project.Create(project_name, base_path);
1085 if (!status.ok()) {
1086 return status;
1087 }
1088
1089 // Customize based on template
1090 if (template_name == "Full Overworld Mod") {
1093 project.metadata.description = "Overworld modification project";
1094 project.metadata.tags = {"overworld", "maps", "graphics"};
1095 } else if (template_name == "Dungeon Designer") {
1096 project.feature_flags.kSaveDungeonMaps = true;
1097 project.workspace_settings.show_grid = true;
1098 project.metadata.description = "Dungeon design and modification project";
1099 project.metadata.tags = {"dungeons", "rooms", "design"};
1100 } else if (template_name == "Graphics Pack") {
1101 project.feature_flags.kSaveGraphicsSheet = true;
1102 project.workspace_settings.show_grid = true;
1103 project.metadata.description = "Graphics and sprite modification project";
1104 project.metadata.tags = {"graphics", "sprites", "palettes"};
1105 } else if (template_name == "Complete Overhaul") {
1108 project.feature_flags.kSaveDungeonMaps = true;
1109 project.feature_flags.kSaveGraphicsSheet = true;
1110 project.metadata.description = "Complete ROM overhaul project";
1111 project.metadata.tags = {"complete", "overhaul", "full-mod"};
1112 }
1113
1114 status = project.Save();
1115 if (!status.ok()) {
1116 return status;
1117 }
1118
1119 return project;
1120}
1121
1123 const std::string& directory) {
1124#ifdef __EMSCRIPTEN__
1125 (void)directory;
1126 return {};
1127#else
1128 std::vector<std::string> projects;
1129
1130 try {
1131 for (const auto& entry : std::filesystem::directory_iterator(directory)) {
1132 if (entry.is_regular_file()) {
1133 std::string filename = entry.path().filename().string();
1134 if (filename.ends_with(".yaze") || filename.ends_with(".zsproj")) {
1135 projects.push_back(entry.path().string());
1136 }
1137 }
1138 }
1139 } catch (const std::filesystem::filesystem_error& e) {
1140 // Directory doesn't exist or can't be accessed
1141 }
1142
1143 return projects;
1144#endif // __EMSCRIPTEN__
1145}
1146
1147absl::Status ProjectManager::BackupProject(const YazeProject& project) {
1148#ifdef __EMSCRIPTEN__
1149 (void)project;
1150 return absl::UnimplementedError(
1151 "Project backups are not supported in the web build");
1152#else
1153 if (project.filepath.empty()) {
1154 return absl::InvalidArgumentError("Project has no file path");
1155 }
1156
1157 std::filesystem::path project_path(project.filepath);
1158 std::filesystem::path backup_dir = project_path.parent_path() / "backups";
1159 std::filesystem::create_directories(backup_dir);
1160
1161 auto now = std::chrono::system_clock::now();
1162 auto time_t = std::chrono::system_clock::to_time_t(now);
1163 std::stringstream ss;
1164 ss << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S");
1165
1166 std::string backup_filename = project.name + "_backup_" + ss.str() + ".yaze";
1167 std::filesystem::path backup_path = backup_dir / backup_filename;
1168
1169 try {
1170 std::filesystem::copy_file(project.filepath, backup_path);
1171 } catch (const std::filesystem::filesystem_error& e) {
1172 return absl::InternalError(
1173 absl::StrFormat("Failed to backup project: %s", e.what()));
1174 }
1175
1176 return absl::OkStatus();
1177#endif
1178}
1179
1181 const YazeProject& project) {
1182 return project.Validate();
1183}
1184
1186 const YazeProject& project) {
1187 std::vector<std::string> recommendations;
1188
1189 if (project.rom_filename.empty()) {
1190 recommendations.push_back("Add a ROM file to begin editing");
1191 }
1192
1193 if (project.code_folder.empty()) {
1194 recommendations.push_back("Set up a code folder for assembly patches");
1195 }
1196
1197 if (project.labels_filename.empty()) {
1198 recommendations.push_back("Create a labels file for better organization");
1199 }
1200
1201 if (project.metadata.description.empty()) {
1202 recommendations.push_back("Add a project description for documentation");
1203 }
1204
1205 if (project.git_repository.empty() && project.track_changes) {
1206 recommendations.push_back(
1207 "Consider setting up version control for your project");
1208 }
1209
1210 auto missing_files = project.GetMissingFiles();
1211 if (!missing_files.empty()) {
1212 recommendations.push_back(
1213 "Some project files are missing - use Project > Repair to fix");
1214 }
1215
1216 return recommendations;
1217}
1218
1219// Compatibility implementations for ResourceLabelManager and related classes
1220bool ResourceLabelManager::LoadLabels(const std::string& filename) {
1221 filename_ = filename;
1222 std::ifstream file(filename);
1223 if (!file.is_open()) {
1224 labels_loaded_ = false;
1225 return false;
1226 }
1227
1228 labels_.clear();
1229 std::string line;
1230 std::string current_type = "";
1231
1232 while (std::getline(file, line)) {
1233 if (line.empty() || line[0] == '#')
1234 continue;
1235
1236 // Check for type headers [type_name]
1237 if (line[0] == '[' && line.back() == ']') {
1238 current_type = line.substr(1, line.length() - 2);
1239 continue;
1240 }
1241
1242 // Parse key=value pairs
1243 size_t eq_pos = line.find('=');
1244 if (eq_pos != std::string::npos && !current_type.empty()) {
1245 std::string key = line.substr(0, eq_pos);
1246 std::string value = line.substr(eq_pos + 1);
1247 labels_[current_type][key] = value;
1248 }
1249 }
1250
1251 file.close();
1252 labels_loaded_ = true;
1253 return true;
1254}
1255
1257 if (filename_.empty())
1258 return false;
1259
1260 std::ofstream file(filename_);
1261 if (!file.is_open())
1262 return false;
1263
1264 file << "# yaze Resource Labels\n";
1265 file << "# Format: [type] followed by key=value pairs\n\n";
1266
1267 for (const auto& [type, type_labels] : labels_) {
1268 if (!type_labels.empty()) {
1269 file << "[" << type << "]\n";
1270 for (const auto& [key, value] : type_labels) {
1271 file << key << "=" << value << "\n";
1272 }
1273 file << "\n";
1274 }
1275 }
1276
1277 file.close();
1278 return true;
1279}
1280
1282 if (!p_open || !*p_open)
1283 return;
1284
1285 // Basic implementation - can be enhanced later
1286 if (ImGui::Begin("Resource Labels", p_open)) {
1287 ImGui::Text("Resource Labels Manager");
1288 ImGui::Text("Labels loaded: %s", labels_loaded_ ? "Yes" : "No");
1289 ImGui::Text("Total types: %zu", labels_.size());
1290
1291 for (const auto& [type, type_labels] : labels_) {
1292 if (ImGui::TreeNode(type.c_str())) {
1293 ImGui::Text("Labels: %zu", type_labels.size());
1294 for (const auto& [key, value] : type_labels) {
1295 ImGui::Text("%s = %s", key.c_str(), value.c_str());
1296 }
1297 ImGui::TreePop();
1298 }
1299 }
1300 }
1301 ImGui::End();
1302}
1303
1304void ResourceLabelManager::EditLabel(const std::string& type,
1305 const std::string& key,
1306 const std::string& newValue) {
1307 labels_[type][key] = newValue;
1308}
1309
1311 bool selected, const std::string& type, const std::string& key,
1312 const std::string& defaultValue) {
1313 // Basic implementation
1314 if (ImGui::Selectable(
1315 absl::StrFormat("%s: %s", key.c_str(), GetLabel(type, key).c_str())
1316 .c_str(),
1317 selected)) {
1318 // Handle selection
1319 }
1320}
1321
1322std::string ResourceLabelManager::GetLabel(const std::string& type,
1323 const std::string& key) {
1324 auto type_it = labels_.find(type);
1325 if (type_it == labels_.end())
1326 return "";
1327
1328 auto label_it = type_it->second.find(key);
1329 if (label_it == type_it->second.end())
1330 return "";
1331
1332 return label_it->second;
1333}
1334
1336 const std::string& type, const std::string& key,
1337 const std::string& defaultValue) {
1338 auto existing = GetLabel(type, key);
1339 if (!existing.empty())
1340 return existing;
1341
1342 labels_[type][key] = defaultValue;
1343 return defaultValue;
1344}
1345
1346// ============================================================================
1347// Embedded Labels Support
1348// ============================================================================
1349
1351 const std::unordered_map<
1352 std::string, std::unordered_map<std::string, std::string>>& labels) {
1353 try {
1354 // Load all default Zelda3 resource names into resource_labels
1355 // We merge them with existing labels, prioritizing existing overrides?
1356 // Or just overwrite? The previous code was:
1357 // resource_labels = zelda3::Zelda3Labels::ToResourceLabels();
1358 // which implies overwriting. But we want to keep overrides if possible.
1359 // However, this is usually called on load.
1360
1361 // Let's overwrite for now to match previous behavior, assuming overrides
1362 // are loaded afterwards or this is initial setup.
1363 // Actually, if we load project then init embedded labels, we might lose overrides.
1364 // But typically overrides are loaded from the project file *into* resource_labels.
1365 // If we call this, we might clobber them.
1366 // The previous implementation clobbered resource_labels.
1367
1368 // However, if we want to support overrides + embedded, we should merge.
1369 // But `resource_labels` was treated as "overrides" in the old code?
1370 // No, `resource_labels` was the container for loaded labels.
1371
1372 // If I look at `LoadFromYazeFormat`:
1373 // It parses `[labels_type]` into `resource_labels`.
1374
1375 // If `use_embedded_labels` is true, `InitializeEmbeddedLabels` is called?
1376 // I need to check when `InitializeEmbeddedLabels` is called.
1377
1378 resource_labels = labels;
1379 use_embedded_labels = true;
1380
1381 LOG_DEBUG("Project", "Initialized embedded labels:");
1382 LOG_DEBUG("Project", " - %d room names", resource_labels["room"].size());
1383 LOG_DEBUG("Project", " - %d entrance names",
1384 resource_labels["entrance"].size());
1385 LOG_DEBUG("Project", " - %d sprite names",
1386 resource_labels["sprite"].size());
1387 LOG_DEBUG("Project", " - %d overlord names",
1388 resource_labels["overlord"].size());
1389 LOG_DEBUG("Project", " - %d item names", resource_labels["item"].size());
1390 LOG_DEBUG("Project", " - %d music names",
1391 resource_labels["music"].size());
1392 LOG_DEBUG("Project", " - %d graphics names",
1393 resource_labels["graphics"].size());
1394 LOG_DEBUG("Project", " - %d room effect names",
1395 resource_labels["room_effect"].size());
1396 LOG_DEBUG("Project", " - %d room tag names",
1397 resource_labels["room_tag"].size());
1398 LOG_DEBUG("Project", " - %d tile type names",
1399 resource_labels["tile_type"].size());
1400
1401 return absl::OkStatus();
1402 } catch (const std::exception& e) {
1403 return absl::InternalError(
1404 absl::StrCat("Failed to initialize embedded labels: ", e.what()));
1405 }
1406}
1407
1408std::string YazeProject::GetLabel(const std::string& resource_type, int id,
1409 const std::string& default_value) const {
1410 // First check if we have a custom label override
1411 auto type_it = resource_labels.find(resource_type);
1412 if (type_it != resource_labels.end()) {
1413 auto label_it = type_it->second.find(std::to_string(id));
1414 if (label_it != type_it->second.end()) {
1415 return label_it->second;
1416 }
1417 }
1418
1419 return default_value.empty() ? resource_type + "_" + std::to_string(id)
1420 : default_value;
1421}
1422
1423absl::Status YazeProject::ImportLabelsFromZScream(const std::string& filepath) {
1424#ifdef __EMSCRIPTEN__
1425 (void)filepath;
1426 return absl::UnimplementedError(
1427 "File-based label import is not supported in the web build");
1428#else
1429 std::ifstream file(filepath);
1430 if (!file.is_open()) {
1431 return absl::InvalidArgumentError(
1432 absl::StrFormat("Cannot open labels file: %s", filepath));
1433 }
1434
1435 std::stringstream buffer;
1436 buffer << file.rdbuf();
1437 file.close();
1438
1439 return ImportLabelsFromZScreamContent(buffer.str());
1440#endif
1441}
1442
1444 const std::string& content) {
1445 // Initialize the global provider with our labels
1446 auto& provider = zelda3::GetResourceLabels();
1447 provider.SetProjectLabels(&resource_labels);
1448 provider.SetPreferHMagicNames(workspace_settings.prefer_hmagic_names);
1449
1450 // Use the provider to parse ZScream format
1451 auto status = provider.ImportFromZScreamFormat(content);
1452 if (!status.ok()) {
1453 return status;
1454 }
1455
1456 LOG_DEBUG("Project", "Imported ZScream labels:");
1457 LOG_DEBUG("Project", " - %d sprite labels",
1458 resource_labels["sprite"].size());
1459 LOG_DEBUG("Project", " - %d room labels", resource_labels["room"].size());
1460 LOG_DEBUG("Project", " - %d item labels", resource_labels["item"].size());
1461 LOG_DEBUG("Project", " - %d room tag labels",
1462 resource_labels["room_tag"].size());
1463
1464 return absl::OkStatus();
1465}
1466
1468 auto& provider = zelda3::GetResourceLabels();
1469 provider.SetProjectLabels(&resource_labels);
1470 provider.SetPreferHMagicNames(workspace_settings.prefer_hmagic_names);
1471
1472 LOG_DEBUG("Project", "Initialized ResourceLabelProvider with project labels");
1473 LOG_DEBUG("Project", " - prefer_hmagic_names: %s",
1474 workspace_settings.prefer_hmagic_names ? "true" : "false");
1475}
1476
1477// ============================================================================
1478// JSON Format Support (Optional)
1479// ============================================================================
1480
1481#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
1482
1483absl::Status YazeProject::LoadFromJsonFormat(const std::string& project_path) {
1484#ifdef __EMSCRIPTEN__
1485 return absl::UnimplementedError(
1486 "JSON project format loading is not supported in the web build");
1487#endif
1488 std::ifstream file(project_path);
1489 if (!file.is_open()) {
1490 return absl::InvalidArgumentError(
1491 absl::StrFormat("Cannot open JSON project file: %s", project_path));
1492 }
1493
1494 try {
1495 json j;
1496 file >> j;
1497
1498 // Parse project metadata
1499 if (j.contains("yaze_project")) {
1500 auto& proj = j["yaze_project"];
1501
1502 if (proj.contains("name"))
1503 name = proj["name"].get<std::string>();
1504 if (proj.contains("description"))
1505 metadata.description = proj["description"].get<std::string>();
1506 if (proj.contains("author"))
1507 metadata.author = proj["author"].get<std::string>();
1508 if (proj.contains("version"))
1509 metadata.version = proj["version"].get<std::string>();
1510 if (proj.contains("created"))
1511 metadata.created_date = proj["created"].get<std::string>();
1512 if (proj.contains("modified"))
1513 metadata.last_modified = proj["modified"].get<std::string>();
1514 if (proj.contains("created_by"))
1515 metadata.created_by = proj["created_by"].get<std::string>();
1516
1517 // Files
1518 if (proj.contains("rom_filename"))
1519 rom_filename = proj["rom_filename"].get<std::string>();
1520 if (proj.contains("code_folder"))
1521 code_folder = proj["code_folder"].get<std::string>();
1522 if (proj.contains("assets_folder"))
1523 assets_folder = proj["assets_folder"].get<std::string>();
1524 if (proj.contains("patches_folder"))
1525 patches_folder = proj["patches_folder"].get<std::string>();
1526 if (proj.contains("labels_filename"))
1527 labels_filename = proj["labels_filename"].get<std::string>();
1528 if (proj.contains("symbols_filename"))
1529 symbols_filename = proj["symbols_filename"].get<std::string>();
1530
1531 // Embedded labels flag
1532 if (proj.contains("use_embedded_labels")) {
1533 use_embedded_labels = proj["use_embedded_labels"].get<bool>();
1534 }
1535
1536 // Feature flags
1537 if (proj.contains("feature_flags")) {
1538 auto& flags = proj["feature_flags"];
1539 // REMOVED: kLogInstructions (deprecated - DisassemblyViewer always
1540 // active)
1541 if (flags.contains("kSaveDungeonMaps"))
1543 flags["kSaveDungeonMaps"].get<bool>();
1544 if (flags.contains("kSaveGraphicsSheet"))
1546 flags["kSaveGraphicsSheet"].get<bool>();
1547 }
1548
1549 // Workspace settings
1550 if (proj.contains("workspace_settings")) {
1551 auto& ws = proj["workspace_settings"];
1552 if (ws.contains("auto_save_enabled"))
1554 ws["auto_save_enabled"].get<bool>();
1555 if (ws.contains("auto_save_interval"))
1557 ws["auto_save_interval"].get<float>();
1558 }
1559
1560 if (proj.contains("agent_settings") &&
1561 proj["agent_settings"].is_object()) {
1562 auto& agent = proj["agent_settings"];
1564 agent.value("ai_provider", agent_settings.ai_provider);
1566 agent.value("ai_model", agent_settings.ai_model);
1568 agent.value("ollama_host", agent_settings.ollama_host);
1570 agent.value("gemini_api_key", agent_settings.gemini_api_key);
1572 agent.value("use_custom_prompt", agent_settings.use_custom_prompt);
1574 "custom_system_prompt", agent_settings.custom_system_prompt);
1576 agent.value("show_reasoning", agent_settings.show_reasoning);
1577 agent_settings.verbose = agent.value("verbose", agent_settings.verbose);
1578 agent_settings.max_tool_iterations = agent.value(
1579 "max_tool_iterations", agent_settings.max_tool_iterations);
1580 agent_settings.max_retry_attempts = agent.value(
1581 "max_retry_attempts", agent_settings.max_retry_attempts);
1583 agent.value("temperature", agent_settings.temperature);
1584 agent_settings.top_p = agent.value("top_p", agent_settings.top_p);
1586 agent.value("max_output_tokens", agent_settings.max_output_tokens);
1588 agent.value("stream_responses", agent_settings.stream_responses);
1589 if (agent.contains("favorite_models") &&
1590 agent["favorite_models"].is_array()) {
1592 for (const auto& model : agent["favorite_models"]) {
1593 if (model.is_string())
1595 model.get<std::string>());
1596 }
1597 }
1598 if (agent.contains("model_chain") && agent["model_chain"].is_array()) {
1600 for (const auto& model : agent["model_chain"]) {
1601 if (model.is_string())
1602 agent_settings.model_chain.push_back(model.get<std::string>());
1603 }
1604 }
1606 agent.value("chain_mode", agent_settings.chain_mode);
1608 "enable_tool_resources", agent_settings.enable_tool_resources);
1609 agent_settings.enable_tool_dungeon = agent.value(
1610 "enable_tool_dungeon", agent_settings.enable_tool_dungeon);
1612 "enable_tool_overworld", agent_settings.enable_tool_overworld);
1614 "enable_tool_messages", agent_settings.enable_tool_messages);
1616 "enable_tool_dialogue", agent_settings.enable_tool_dialogue);
1618 agent.value("enable_tool_gui", agent_settings.enable_tool_gui);
1620 agent.value("enable_tool_music", agent_settings.enable_tool_music);
1621 agent_settings.enable_tool_sprite = agent.value(
1622 "enable_tool_sprite", agent_settings.enable_tool_sprite);
1624 "enable_tool_emulator", agent_settings.enable_tool_emulator);
1626 "builder_blueprint_path", agent_settings.builder_blueprint_path);
1627 }
1628
1629 // Build settings
1630 if (proj.contains("build_script"))
1631 build_script = proj["build_script"].get<std::string>();
1632 if (proj.contains("output_folder"))
1633 output_folder = proj["output_folder"].get<std::string>();
1634 if (proj.contains("git_repository"))
1635 git_repository = proj["git_repository"].get<std::string>();
1636 if (proj.contains("track_changes"))
1637 track_changes = proj["track_changes"].get<bool>();
1638 }
1639
1640 return absl::OkStatus();
1641 } catch (const json::exception& e) {
1642 return absl::InvalidArgumentError(
1643 absl::StrFormat("JSON parse error: %s", e.what()));
1644 }
1645}
1646
1647absl::Status YazeProject::SaveToJsonFormat() {
1648#ifdef __EMSCRIPTEN__
1649 return absl::UnimplementedError(
1650 "JSON project format saving is not supported in the web build");
1651#endif
1652 json j;
1653 auto& proj = j["yaze_project"];
1654
1655 // Metadata
1656 proj["version"] = metadata.version;
1657 proj["name"] = name;
1658 proj["author"] = metadata.author;
1659 proj["created_by"] = metadata.created_by;
1660 proj["description"] = metadata.description;
1661 proj["created"] = metadata.created_date;
1662 proj["modified"] = metadata.last_modified;
1663
1664 // Files
1665 proj["rom_filename"] = rom_filename;
1666 proj["code_folder"] = code_folder;
1667 proj["assets_folder"] = assets_folder;
1668 proj["patches_folder"] = patches_folder;
1669 proj["labels_filename"] = labels_filename;
1670 proj["symbols_filename"] = symbols_filename;
1671 proj["output_folder"] = output_folder;
1672
1673 // Embedded labels
1674 proj["use_embedded_labels"] = use_embedded_labels;
1675
1676 // Feature flags
1677 // REMOVED: kLogInstructions (deprecated)
1678 proj["feature_flags"]["kSaveDungeonMaps"] = feature_flags.kSaveDungeonMaps;
1679 proj["feature_flags"]["kSaveGraphicsSheet"] =
1681
1682 // Workspace settings
1683 proj["workspace_settings"]["auto_save_enabled"] =
1685 proj["workspace_settings"]["auto_save_interval"] =
1687
1688 auto& agent = proj["agent_settings"];
1689 agent["ai_provider"] = agent_settings.ai_provider;
1690 agent["ai_model"] = agent_settings.ai_model;
1691 agent["ollama_host"] = agent_settings.ollama_host;
1692 agent["gemini_api_key"] = agent_settings.gemini_api_key;
1693 agent["use_custom_prompt"] = agent_settings.use_custom_prompt;
1694 agent["custom_system_prompt"] = agent_settings.custom_system_prompt;
1695 agent["show_reasoning"] = agent_settings.show_reasoning;
1696 agent["verbose"] = agent_settings.verbose;
1697 agent["max_tool_iterations"] = agent_settings.max_tool_iterations;
1698 agent["max_retry_attempts"] = agent_settings.max_retry_attempts;
1699 agent["temperature"] = agent_settings.temperature;
1700 agent["top_p"] = agent_settings.top_p;
1701 agent["max_output_tokens"] = agent_settings.max_output_tokens;
1702 agent["stream_responses"] = agent_settings.stream_responses;
1703 agent["favorite_models"] = agent_settings.favorite_models;
1704 agent["model_chain"] = agent_settings.model_chain;
1705 agent["chain_mode"] = agent_settings.chain_mode;
1706 agent["enable_tool_resources"] = agent_settings.enable_tool_resources;
1707 agent["enable_tool_dungeon"] = agent_settings.enable_tool_dungeon;
1708 agent["enable_tool_overworld"] = agent_settings.enable_tool_overworld;
1709 agent["enable_tool_messages"] = agent_settings.enable_tool_messages;
1710 agent["enable_tool_dialogue"] = agent_settings.enable_tool_dialogue;
1711 agent["enable_tool_gui"] = agent_settings.enable_tool_gui;
1712 agent["enable_tool_music"] = agent_settings.enable_tool_music;
1713 agent["enable_tool_sprite"] = agent_settings.enable_tool_sprite;
1714 agent["enable_tool_emulator"] = agent_settings.enable_tool_emulator;
1715 agent["builder_blueprint_path"] = agent_settings.builder_blueprint_path;
1716
1717 // Build settings
1718 proj["build_script"] = build_script;
1719 proj["git_repository"] = git_repository;
1720 proj["track_changes"] = track_changes;
1721
1722 // Write to file
1723 std::ofstream file(filepath);
1724 if (!file.is_open()) {
1725 return absl::InvalidArgumentError(
1726 absl::StrFormat("Cannot write JSON project file: %s", filepath));
1727 }
1728
1729 file << j.dump(2); // Pretty print with 2-space indent
1730 return absl::OkStatus();
1731}
1732
1733#endif // YAZE_ENABLE_JSON_PROJECT_FORMAT
1734
1735// RecentFilesManager implementation
1737 auto config_dir = util::PlatformPaths::GetConfigDirectory();
1738 if (!config_dir.ok()) {
1739 return ""; // Or handle error appropriately
1740 }
1741 return (*config_dir / kRecentFilesFilename).string();
1742}
1743
1745#ifdef __EMSCRIPTEN__
1746 auto status = platform::WasmStorage::SaveProject(
1747 kRecentFilesFilename, absl::StrJoin(recent_files_, "\n"));
1748 if (!status.ok()) {
1749 LOG_WARN("RecentFilesManager", "Could not persist recent files: %s",
1750 status.ToString().c_str());
1751 }
1752 return;
1753#endif
1754 // Ensure config directory exists
1755 auto config_dir_status = util::PlatformPaths::GetConfigDirectory();
1756 if (!config_dir_status.ok()) {
1757 LOG_ERROR("Project", "Failed to get or create config directory: %s",
1758 config_dir_status.status().ToString().c_str());
1759 return;
1760 }
1761
1762 std::string filepath = GetFilePath();
1763 std::ofstream file(filepath);
1764 if (!file.is_open()) {
1765 LOG_WARN("RecentFilesManager", "Could not save recent files to %s",
1766 filepath.c_str());
1767 return;
1768 }
1769
1770 for (const auto& file_path : recent_files_) {
1771 file << file_path << std::endl;
1772 }
1773}
1774
1776#ifdef __EMSCRIPTEN__
1777 auto storage_or = platform::WasmStorage::LoadProject(kRecentFilesFilename);
1778 if (!storage_or.ok()) {
1779 return;
1780 }
1781 recent_files_.clear();
1782 std::istringstream stream(storage_or.value());
1783 std::string line;
1784 while (std::getline(stream, line)) {
1785 if (!line.empty()) {
1786 recent_files_.push_back(line);
1787 }
1788 }
1789#else
1790 std::string filepath = GetFilePath();
1791 std::ifstream file(filepath);
1792 if (!file.is_open()) {
1793 // File doesn't exist yet, which is fine
1794 return;
1795 }
1796
1797 recent_files_.clear();
1798 std::string line;
1799 while (std::getline(file, line)) {
1800 if (!line.empty()) {
1801 recent_files_.push_back(line);
1802 }
1803 }
1804#endif
1805}
1806
1807} // namespace project
1808} // namespace yaze
static std::vector< std::string > FindProjectsInDirectory(const std::string &directory)
Definition project.cc:1122
static absl::Status ValidateProjectStructure(const YazeProject &project)
Definition project.cc:1180
static absl::StatusOr< YazeProject > CreateFromTemplate(const std::string &template_name, const std::string &project_name, const std::string &base_path)
Definition project.cc:1080
static std::vector< std::string > GetRecommendedFixesForProject(const YazeProject &project)
Definition project.cc:1185
static std::vector< ProjectTemplate > GetProjectTemplates()
Definition project.cc:933
static absl::Status BackupProject(const YazeProject &project)
Definition project.cc:1147
std::string GetFilePath() const
Definition project.cc:1736
std::vector< std::string > recent_files_
Definition project.h:360
static absl::StatusOr< std::filesystem::path > GetConfigDirectory()
Get the user-specific configuration directory for YAZE.
#define ICON_MD_SHUFFLE
Definition icons.h:1738
#define ICON_MD_TERRAIN
Definition icons.h:1952
#define ICON_MD_MAP
Definition icons.h:1173
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2076
#define ICON_MD_DOMAIN
Definition icons.h:603
#define ICON_MD_BUILD
Definition icons.h:328
#define ICON_MD_PALETTE
Definition icons.h:1370
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
float ParseFloat(const std::string &value)
Definition project.cc:58
bool ParseBool(const std::string &value)
Definition project.cc:54
std::string SanitizeStorageKey(absl::string_view input)
Definition project.cc:83
std::vector< std::string > ParseStringList(const std::string &value)
Definition project.cc:66
const std::string kRecentFilesFilename
Definition project.h:307
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
struct yaze::core::FeatureFlags::Flags::Overworld overworld
std::vector< std::string > tags
Definition project.h:39
std::string CreateOrGetLabel(const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:1335
std::string GetLabel(const std::string &type, const std::string &key)
Definition project.cc:1322
void EditLabel(const std::string &type, const std::string &key, const std::string &newValue)
Definition project.cc:1304
bool LoadLabels(const std::string &filename)
Definition project.cc:1220
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:1310
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
Definition project.h:303
std::map< std::string, std::string > custom_keybindings
Definition project.h:75
std::vector< std::string > saved_layouts
Definition project.h:61
std::map< std::string, bool > editor_visibility
Definition project.h:77
std::vector< std::string > recent_files
Definition project.h:76
std::vector< std::string > favorite_models
Definition project.h:147
std::vector< std::string > model_chain
Definition project.h:148
Modern project structure with comprehensive settings consolidation.
Definition project.h:84
std::string rom_backup_folder
Definition project.h:93
absl::Status ResetToDefaults()
Definition project.cc:733
std::string custom_objects_folder
Definition project.h:103
absl::Status RepairProject()
Definition project.cc:794
std::string MakeStorageKey(absl::string_view suffix) const
Definition project.cc:209
struct yaze::project::YazeProject::MusicPersistence music_persistence
absl::StatusOr< std::string > SerializeToString() const
Definition project.cc:225
std::string zscream_project_file
Definition project.h:163
absl::Status ExportForZScream(const std::string &target_path)
Definition project.cc:700
ProjectMetadata metadata
Definition project.h:86
absl::Status ImportZScreamProject(const std::string &zscream_project_path)
Definition project.cc:677
absl::Status SaveAllSettings()
Definition project.cc:728
absl::Status ImportLabelsFromZScreamContent(const std::string &content)
Import labels from ZScream format content directly.
Definition project.cc:1443
std::string git_repository
Definition project.h:124
void InitializeResourceLabelProvider()
Initialize the global ResourceLabelProvider with this project's labels.
Definition project.cc:1467
std::string rom_filename
Definition project.h:92
absl::Status ParseFromString(const std::string &content)
Definition project.cc:413
std::vector< std::string > additional_roms
Definition project.h:94
std::string patches_folder
Definition project.h:99
absl::Status LoadFromYazeFormat(const std::string &project_path)
Definition project.cc:622
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
Definition project.h:109
std::string GenerateProjectId() const
Definition project.cc:924
absl::Status Create(const std::string &project_name, const std::string &base_path)
Definition project.cc:98
std::string assets_folder
Definition project.h:98
absl::Status SaveToYazeFormat()
Definition project.cc:643
absl::Status LoadAllSettings()
Definition project.cc:722
std::string labels_filename
Definition project.h:100
std::vector< std::string > asm_sources
Definition project.h:121
std::string GetDisplayName() const
Definition project.cc:828
std::vector< std::string > GetMissingFiles() const
Definition project.cc:773
WorkspaceSettings workspace_settings
Definition project.h:107
std::string output_folder
Definition project.h:117
std::string asm_entry_point
Definition project.h:120
std::string GetRelativePath(const std::string &absolute_path) const
Definition project.cc:835
absl::Status InitializeEmbeddedLabels(const std::unordered_map< std::string, std::unordered_map< std::string, std::string > > &labels)
Definition project.cc:1350
absl::Status SaveAs(const std::string &new_path)
Definition project.cc:197
struct yaze::project::YazeProject::AgentSettings agent_settings
absl::Status ImportFromZScreamFormat(const std::string &project_path)
Definition project.cc:869
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:853
std::string GetLabel(const std::string &resource_type, int id, const std::string &default_value="") const
Definition project.cc:1408
absl::Status Open(const std::string &project_path)
Definition project.cc:150
absl::Status ImportLabelsFromZScream(const std::string &filepath)
Import labels from a ZScream DefaultNames.txt file.
Definition project.cc:1423
std::string last_build_hash
Definition project.h:126
ProjectFormat format
Definition project.h:89
std::map< std::string, std::string > zscream_mappings
Definition project.h:164
std::string code_folder
Definition project.h:97
absl::Status Validate() const
Definition project.cc:738
core::FeatureFlags::Flags feature_flags
Definition project.h:106
std::vector< std::string > build_configurations
Definition project.h:118
std::string symbols_filename
Definition project.h:101