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 "project.h"
2
3#include <chrono>
4#include <filesystem>
5#include <fstream>
6#include <iomanip>
7#include <sstream>
8
9#include "absl/strings/str_format.h"
10#include "absl/strings/str_join.h"
11#include "absl/strings/str_split.h"
12#include "util/file_util.h"
13#include "util/platform_paths.h"
14#include "app/gui/icons.h"
15#include "util/log.h"
17#include "imgui/imgui.h"
18#include "yaze_config.h"
19
20#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
21#include "nlohmann/json.hpp"
22using json = nlohmann::json;
23#endif
24
25namespace yaze {
26namespace core {
27
28namespace {
29 // Helper functions for parsing key-value pairs
30 std::pair<std::string, std::string> ParseKeyValue(const std::string& line) {
31 size_t eq_pos = line.find('=');
32 if (eq_pos == std::string::npos) return {"", ""};
33
34 std::string key = line.substr(0, eq_pos);
35 std::string value = line.substr(eq_pos + 1);
36
37 // Trim whitespace
38 key.erase(0, key.find_first_not_of(" \t"));
39 key.erase(key.find_last_not_of(" \t") + 1);
40 value.erase(0, value.find_first_not_of(" \t"));
41 value.erase(value.find_last_not_of(" \t") + 1);
42
43 return {key, value};
44 }
45
46 bool ParseBool(const std::string& value) {
47 return value == "true" || value == "1" || value == "yes";
48 }
49
50 float ParseFloat(const std::string& value) {
51 try {
52 return std::stof(value);
53 } catch (...) {
54 return 0.0f;
55 }
56 }
57
58 std::vector<std::string> ParseStringList(const std::string& value) {
59 std::vector<std::string> result;
60 if (value.empty()) return result;
61
62 std::vector<std::string> parts = absl::StrSplit(value, ',');
63 for (const auto& part : parts) {
64 std::string trimmed = std::string(part);
65 trimmed.erase(0, trimmed.find_first_not_of(" \t"));
66 trimmed.erase(trimmed.find_last_not_of(" \t") + 1);
67 if (!trimmed.empty()) {
68 result.push_back(trimmed);
69 }
70 }
71 return result;
72 }
73}
74
75// YazeProject Implementation
76absl::Status YazeProject::Create(const std::string& project_name, const std::string& base_path) {
77 name = project_name;
78 filepath = base_path + "/" + project_name + ".yaze";
79
80 // Initialize metadata
81 auto now = std::chrono::system_clock::now();
82 auto time_t = std::chrono::system_clock::to_time_t(now);
83 std::stringstream ss;
84 ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
85
86 metadata.created_date = ss.str();
87 metadata.last_modified = ss.str();
88 metadata.yaze_version = "0.3.2"; // TODO: Get from version header
89 metadata.version = "2.0";
90 metadata.created_by = "YAZE";
91
93
94 // Create project directory structure
95 std::filesystem::path project_dir(base_path + "/" + project_name);
96 std::filesystem::create_directories(project_dir);
97 std::filesystem::create_directories(project_dir / "code");
98 std::filesystem::create_directories(project_dir / "assets");
99 std::filesystem::create_directories(project_dir / "patches");
100 std::filesystem::create_directories(project_dir / "backups");
101 std::filesystem::create_directories(project_dir / "output");
102
103 // Set folder paths
104 code_folder = (project_dir / "code").string();
105 assets_folder = (project_dir / "assets").string();
106 patches_folder = (project_dir / "patches").string();
107 rom_backup_folder = (project_dir / "backups").string();
108 output_folder = (project_dir / "output").string();
109 labels_filename = (project_dir / "labels.txt").string();
110 symbols_filename = (project_dir / "symbols.txt").string();
111
112 return Save();
113}
114
115absl::Status YazeProject::Open(const std::string& project_path) {
116 filepath = project_path;
117
118 // Determine format and load accordingly
119 if (project_path.ends_with(".yaze")) {
121
122 // Try to detect if it's JSON format by peeking at first character
123 std::ifstream file(project_path);
124 if (file.is_open()) {
125 char first_char;
126 file.get(first_char);
127 file.close();
128
129#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
130 if (first_char == '{') {
131 LOG_DEBUG("Project", "Detected JSON format project file");
132 return LoadFromJsonFormat(project_path);
133 }
134#endif
135
136 // Default to INI format
137 return LoadFromYazeFormat(project_path);
138 }
139
140 return absl::InvalidArgumentError(
141 absl::StrFormat("Cannot open project file: %s", project_path));
142 } else if (project_path.ends_with(".zsproj")) {
144 return ImportFromZScreamFormat(project_path);
145 }
146
147 return absl::InvalidArgumentError("Unsupported project file format");
148}
149
150absl::Status YazeProject::Save() {
151 return SaveToYazeFormat();
152}
153
154absl::Status YazeProject::SaveAs(const std::string& new_path) {
155 std::string old_filepath = filepath;
156 filepath = new_path;
157
158 auto status = Save();
159 if (!status.ok()) {
160 filepath = old_filepath; // Restore on failure
161 }
162
163 return status;
164}
165
166absl::Status YazeProject::LoadFromYazeFormat(const std::string& project_path) {
167 std::ifstream file(project_path);
168 if (!file.is_open()) {
169 return absl::InvalidArgumentError(absl::StrFormat("Cannot open project file: %s", project_path));
170 }
171
172 std::string line;
173 std::string current_section = "";
174
175 while (std::getline(file, line)) {
176 // Skip empty lines and comments
177 if (line.empty() || line[0] == '#') continue;
178
179 // Check for section headers [section_name]
180 if (line[0] == '[' && line.back() == ']') {
181 current_section = line.substr(1, line.length() - 2);
182 continue;
183 }
184
185 auto [key, value] = ParseKeyValue(line);
186 if (key.empty()) continue;
187
188 // Parse based on current section
189 if (current_section == "project") {
190 if (key == "name") name = value;
191 else if (key == "description") metadata.description = value;
192 else if (key == "author") metadata.author = value;
193 else if (key == "license") metadata.license = value;
194 else if (key == "version") metadata.version = value;
195 else if (key == "created_date") metadata.created_date = value;
196 else if (key == "last_modified") metadata.last_modified = value;
197 else if (key == "yaze_version") metadata.yaze_version = value;
198 else if (key == "tags") metadata.tags = ParseStringList(value);
199 }
200 else if (current_section == "files") {
201 if (key == "rom_filename") rom_filename = value;
202 else if (key == "rom_backup_folder") rom_backup_folder = value;
203 else if (key == "code_folder") code_folder = value;
204 else if (key == "assets_folder") assets_folder = value;
205 else if (key == "patches_folder") patches_folder = value;
206 else if (key == "labels_filename") labels_filename = value;
207 else if (key == "symbols_filename") symbols_filename = value;
208 else if (key == "output_folder") output_folder = value;
209 else if (key == "additional_roms") additional_roms = ParseStringList(value);
210 }
211 else if (current_section == "feature_flags") {
212 if (key == "load_custom_overworld") feature_flags.overworld.kLoadCustomOverworld = ParseBool(value);
213 else if (key == "apply_zs_custom_overworld_asm") feature_flags.overworld.kApplyZSCustomOverworldASM = ParseBool(value);
214 else if (key == "save_dungeon_maps") feature_flags.kSaveDungeonMaps = ParseBool(value);
215 else if (key == "save_graphics_sheet") feature_flags.kSaveGraphicsSheet = ParseBool(value);
216 // REMOVED: log_instructions (deprecated - DisassemblyViewer always active)
217 }
218 else if (current_section == "workspace") {
219 if (key == "font_global_scale") workspace_settings.font_global_scale = ParseFloat(value);
220 else if (key == "dark_mode") workspace_settings.dark_mode = ParseBool(value);
221 else if (key == "ui_theme") workspace_settings.ui_theme = value;
222 else if (key == "autosave_enabled") workspace_settings.autosave_enabled = ParseBool(value);
223 else if (key == "autosave_interval_secs") workspace_settings.autosave_interval_secs = ParseFloat(value);
224 else if (key == "backup_on_save") workspace_settings.backup_on_save = ParseBool(value);
225 else if (key == "show_grid") workspace_settings.show_grid = ParseBool(value);
226 else if (key == "show_collision") workspace_settings.show_collision = ParseBool(value);
227 else if (key == "last_layout_preset") workspace_settings.last_layout_preset = value;
228 else if (key == "saved_layouts") workspace_settings.saved_layouts = ParseStringList(value);
229 else if (key == "recent_files") workspace_settings.recent_files = ParseStringList(value);
230 }
231 else if (current_section == "agent_settings") {
232 if (key == "ai_provider") agent_settings.ai_provider = value;
233 else if (key == "ai_model") agent_settings.ai_model = value;
234 else if (key == "ollama_host") agent_settings.ollama_host = value;
235 else if (key == "gemini_api_key") agent_settings.gemini_api_key = value;
236 else if (key == "custom_system_prompt") agent_settings.custom_system_prompt = value;
237 else if (key == "use_custom_prompt") agent_settings.use_custom_prompt = ParseBool(value);
238 else if (key == "show_reasoning") agent_settings.show_reasoning = ParseBool(value);
239 else if (key == "verbose") agent_settings.verbose = ParseBool(value);
240 else if (key == "max_tool_iterations") agent_settings.max_tool_iterations = std::stoi(value);
241 else if (key == "max_retry_attempts") agent_settings.max_retry_attempts = std::stoi(value);
242 }
243 else if (current_section == "build") {
244 if (key == "build_script") build_script = value;
245 else if (key == "output_folder") output_folder = value;
246 else if (key == "git_repository") git_repository = value;
247 else if (key == "track_changes") track_changes = ParseBool(value);
248 else if (key == "build_configurations") build_configurations = ParseStringList(value);
249 }
250 else if (current_section.starts_with("labels_")) {
251 // Resource labels: [labels_type_name] followed by key=value pairs
252 std::string label_type = current_section.substr(7); // Remove "labels_" prefix
253 resource_labels[label_type][key] = value;
254 }
255 else if (current_section == "keybindings") {
257 }
258 else if (current_section == "editor_visibility") {
259 workspace_settings.editor_visibility[key] = ParseBool(value);
260 }
261 else if (current_section == "zscream_compatibility") {
262 if (key == "original_project_file") zscream_project_file = value;
263 else zscream_mappings[key] = value;
264 }
265 }
266
267 file.close();
268 return absl::OkStatus();
269}
270
272 // Update last modified timestamp
273 auto now = std::chrono::system_clock::now();
274 auto time_t = std::chrono::system_clock::to_time_t(now);
275 std::stringstream ss;
276 ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
277 metadata.last_modified = ss.str();
278
279 std::ofstream file(filepath);
280 if (!file.is_open()) {
281 return absl::InvalidArgumentError(absl::StrFormat("Cannot create project file: %s", filepath));
282 }
283
284 // Write header comment
285 file << "# yaze Project File\n";
286 file << "# Format Version: 2.0\n";
287 file << "# Generated by YAZE " << metadata.yaze_version << "\n";
288 file << "# Last Modified: " << metadata.last_modified << "\n\n";
289
290 // Project section
291 file << "[project]\n";
292 file << "name=" << name << "\n";
293 file << "description=" << metadata.description << "\n";
294 file << "author=" << metadata.author << "\n";
295 file << "license=" << metadata.license << "\n";
296 file << "version=" << metadata.version << "\n";
297 file << "created_date=" << metadata.created_date << "\n";
298 file << "last_modified=" << metadata.last_modified << "\n";
299 file << "yaze_version=" << metadata.yaze_version << "\n";
300 file << "tags=" << absl::StrJoin(metadata.tags, ",") << "\n\n";
301
302 // Files section
303 file << "[files]\n";
304 file << "rom_filename=" << GetRelativePath(rom_filename) << "\n";
305 file << "rom_backup_folder=" << GetRelativePath(rom_backup_folder) << "\n";
306 file << "code_folder=" << GetRelativePath(code_folder) << "\n";
307 file << "assets_folder=" << GetRelativePath(assets_folder) << "\n";
308 file << "patches_folder=" << GetRelativePath(patches_folder) << "\n";
309 file << "labels_filename=" << GetRelativePath(labels_filename) << "\n";
310 file << "symbols_filename=" << GetRelativePath(symbols_filename) << "\n";
311 file << "output_folder=" << GetRelativePath(output_folder) << "\n";
312 file << "additional_roms=" << absl::StrJoin(additional_roms, ",") << "\n\n";
313
314 // Feature flags section
315 file << "[feature_flags]\n";
316 file << "load_custom_overworld=" << (feature_flags.overworld.kLoadCustomOverworld ? "true" : "false") << "\n";
317 file << "apply_zs_custom_overworld_asm=" << (feature_flags.overworld.kApplyZSCustomOverworldASM ? "true" : "false") << "\n";
318 file << "save_dungeon_maps=" << (feature_flags.kSaveDungeonMaps ? "true" : "false") << "\n";
319 file << "save_graphics_sheet=" << (feature_flags.kSaveGraphicsSheet ? "true" : "false") << "\n";
320 // REMOVED: log_instructions (deprecated)\n\n";
321
322 // Workspace settings section
323 file << "[workspace]\n";
324 file << "font_global_scale=" << workspace_settings.font_global_scale << "\n";
325 file << "dark_mode=" << (workspace_settings.dark_mode ? "true" : "false") << "\n";
326 file << "ui_theme=" << workspace_settings.ui_theme << "\n";
327 file << "autosave_enabled=" << (workspace_settings.autosave_enabled ? "true" : "false") << "\n";
328 file << "autosave_interval_secs=" << workspace_settings.autosave_interval_secs << "\n";
329 file << "backup_on_save=" << (workspace_settings.backup_on_save ? "true" : "false") << "\n";
330 file << "show_grid=" << (workspace_settings.show_grid ? "true" : "false") << "\n";
331 file << "show_collision=" << (workspace_settings.show_collision ? "true" : "false") << "\n";
332 file << "last_layout_preset=" << workspace_settings.last_layout_preset << "\n";
333 file << "saved_layouts=" << absl::StrJoin(workspace_settings.saved_layouts, ",") << "\n";
334 file << "recent_files=" << absl::StrJoin(workspace_settings.recent_files, ",") << "\n\n";
335
336 // AI Agent settings section
337 file << "[agent_settings]\n";
338 file << "ai_provider=" << agent_settings.ai_provider << "\n";
339 file << "ai_model=" << agent_settings.ai_model << "\n";
340 file << "ollama_host=" << agent_settings.ollama_host << "\n";
341 file << "gemini_api_key=" << agent_settings.gemini_api_key << "\n";
342 file << "custom_system_prompt=" << GetRelativePath(agent_settings.custom_system_prompt) << "\n";
343 file << "use_custom_prompt=" << (agent_settings.use_custom_prompt ? "true" : "false") << "\n";
344 file << "show_reasoning=" << (agent_settings.show_reasoning ? "true" : "false") << "\n";
345 file << "verbose=" << (agent_settings.verbose ? "true" : "false") << "\n";
346 file << "max_tool_iterations=" << agent_settings.max_tool_iterations << "\n";
347 file << "max_retry_attempts=" << agent_settings.max_retry_attempts << "\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, ",") << "\n\n";
385
386 // ZScream compatibility section
387 if (!zscream_project_file.empty()) {
388 file << "[zscream_compatibility]\n";
389 file << "original_project_file=" << zscream_project_file << "\n";
390 for (const auto& [key, value] : zscream_mappings) {
391 file << key << "=" << value << "\n";
392 }
393 file << "\n";
394 }
395
396 file << "# End of YAZE Project File\n";
397 file.close();
398
399 return absl::OkStatus();
400}
401
402absl::Status YazeProject::ImportZScreamProject(const std::string& zscream_project_path) {
403 // Basic ZScream project import (to be expanded based on ZScream format)
404 zscream_project_file = zscream_project_path;
406
407 // Extract project name from path
408 std::filesystem::path zs_path(zscream_project_path);
409 name = zs_path.stem().string() + "_imported";
410
411 // Set up basic mapping for common fields
412 zscream_mappings["rom_file"] = "rom_filename";
413 zscream_mappings["source_code"] = "code_folder";
414 zscream_mappings["project_name"] = "name";
415
417
418 // TODO: Implement actual ZScream format parsing when format is known
419 // For now, just create a project structure that can be manually configured
420
421 return absl::OkStatus();
422}
423
424absl::Status YazeProject::ExportForZScream(const std::string& target_path) {
425 // Create a simplified project file that ZScream might understand
426 std::ofstream file(target_path);
427 if (!file.is_open()) {
428 return absl::InvalidArgumentError(absl::StrFormat("Cannot create ZScream project file: %s", target_path));
429 }
430
431 // Write in a simple format that ZScream might understand
432 file << "# ZScream Compatible Project File\n";
433 file << "# Exported from YAZE " << metadata.yaze_version << "\n\n";
434 file << "name=" << name << "\n";
435 file << "rom_file=" << rom_filename << "\n";
436 file << "source_code=" << code_folder << "\n";
437 file << "description=" << metadata.description << "\n";
438 file << "author=" << metadata.author << "\n";
439 file << "created_with=YAZE " << metadata.yaze_version << "\n";
440
441 file.close();
442 return absl::OkStatus();
443}
444
446 // Consolidated loading of all settings from project file
447 // This replaces scattered config loading throughout the application
449}
450
452 // Consolidated saving of all settings to project file
453 return SaveToYazeFormat();
454}
455
458 return Save();
459}
460
461absl::Status YazeProject::Validate() const {
462 std::vector<std::string> errors;
463
464 if (name.empty()) errors.push_back("Project name is required");
465 if (filepath.empty()) errors.push_back("Project file path is required");
466 if (rom_filename.empty()) errors.push_back("ROM file is required");
467
468 // Check if files exist
469 if (!rom_filename.empty() && !std::filesystem::exists(GetAbsolutePath(rom_filename))) {
470 errors.push_back("ROM file does not exist: " + rom_filename);
471 }
472
473 if (!code_folder.empty() && !std::filesystem::exists(GetAbsolutePath(code_folder))) {
474 errors.push_back("Code folder does not exist: " + code_folder);
475 }
476
477 if (!labels_filename.empty() && !std::filesystem::exists(GetAbsolutePath(labels_filename))) {
478 errors.push_back("Labels file does not exist: " + labels_filename);
479 }
480
481 if (!errors.empty()) {
482 return absl::InvalidArgumentError(absl::StrJoin(errors, "; "));
483 }
484
485 return absl::OkStatus();
486}
487
488std::vector<std::string> YazeProject::GetMissingFiles() const {
489 std::vector<std::string> missing;
490
491 if (!rom_filename.empty() && !std::filesystem::exists(GetAbsolutePath(rom_filename))) {
492 missing.push_back(rom_filename);
493 }
494 if (!labels_filename.empty() && !std::filesystem::exists(GetAbsolutePath(labels_filename))) {
495 missing.push_back(labels_filename);
496 }
497 if (!symbols_filename.empty() && !std::filesystem::exists(GetAbsolutePath(symbols_filename))) {
498 missing.push_back(symbols_filename);
499 }
500
501 return missing;
502}
503
505 // Create missing directories
506 std::vector<std::string> folders = {code_folder, assets_folder, patches_folder,
508
509 for (const auto& folder : folders) {
510 if (!folder.empty()) {
511 std::filesystem::path abs_path = GetAbsolutePath(folder);
512 if (!std::filesystem::exists(abs_path)) {
513 std::filesystem::create_directories(abs_path);
514 }
515 }
516 }
517
518 // Create missing files with defaults
519 if (!labels_filename.empty()) {
520 std::filesystem::path abs_labels = GetAbsolutePath(labels_filename);
521 if (!std::filesystem::exists(abs_labels)) {
522 std::ofstream labels_file(abs_labels);
523 labels_file << "# yaze Resource Labels\n";
524 labels_file << "# Format: [type] key=value\n\n";
525 labels_file.close();
526 }
527 }
528
529 return absl::OkStatus();
530}
531
532std::string YazeProject::GetDisplayName() const {
533 if (!metadata.description.empty()) {
534 return metadata.description;
535 }
536 return name.empty() ? "Untitled Project" : name;
537}
538
539std::string YazeProject::GetRelativePath(const std::string& absolute_path) const {
540 if (absolute_path.empty() || filepath.empty()) return absolute_path;
541
542 std::filesystem::path project_dir = std::filesystem::path(filepath).parent_path();
543 std::filesystem::path abs_path(absolute_path);
544
545 try {
546 std::filesystem::path relative = std::filesystem::relative(abs_path, project_dir);
547 return relative.string();
548 } catch (...) {
549 return absolute_path; // Return absolute path if relative conversion fails
550 }
551}
552
553std::string YazeProject::GetAbsolutePath(const std::string& relative_path) const {
554 if (relative_path.empty() || filepath.empty()) return relative_path;
555
556 std::filesystem::path project_dir = std::filesystem::path(filepath).parent_path();
557 std::filesystem::path abs_path = project_dir / relative_path;
558
559 return abs_path.string();
560}
561
563 return name.empty() && rom_filename.empty() && code_folder.empty();
564}
565
566absl::Status YazeProject::ImportFromZScreamFormat(const std::string& project_path) {
567 // TODO: Implement ZScream format parsing when format specification is available
568 // For now, create a basic project that can be manually configured
569
570 std::filesystem::path zs_path(project_path);
571 name = zs_path.stem().string() + "_imported";
572 zscream_project_file = project_path;
573
575
576 return absl::OkStatus();
577}
578
580 // Initialize default feature flags
585 // REMOVED: kLogInstructions (deprecated)
586
587 // Initialize default workspace settings
590 workspace_settings.ui_theme = "default";
592 workspace_settings.autosave_interval_secs = 300.0f; // 5 minutes
596
597 // Initialize default build configurations
598 build_configurations = {"Debug", "Release", "Distribution"};
599
600 track_changes = true;
601}
602
604 auto now = std::chrono::system_clock::now().time_since_epoch();
605 auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
606 return absl::StrFormat("yaze_project_%lld", timestamp);
607}
608
609// ProjectManager Implementation
610std::vector<ProjectManager::ProjectTemplate> ProjectManager::GetProjectTemplates() {
611 return {
612 {
613 "Basic ROM Hack",
614 "Simple project for modifying an existing ROM with basic tools",
616 {} // Basic defaults
617 },
618 {
619 "Full Overworld Mod",
620 "Complete overworld modification with custom graphics and maps",
622 {} // Overworld-focused settings
623 },
624 {
625 "Dungeon Designer",
626 "Focused on dungeon creation and modification",
628 {} // Dungeon-focused settings
629 },
630 {
631 "Graphics Pack",
632 "Project focused on graphics, sprites, and visual modifications",
634 {} // Graphics-focused settings
635 },
636 {
637 "Complete Overhaul",
638 "Full-scale ROM hack with all features enabled",
640 {} // All features enabled
641 }
642 };
643}
644
645absl::StatusOr<YazeProject> ProjectManager::CreateFromTemplate(
646 const std::string& template_name,
647 const std::string& project_name,
648 const std::string& base_path) {
649
650 YazeProject project;
651 auto status = project.Create(project_name, base_path);
652 if (!status.ok()) {
653 return status;
654 }
655
656 // Customize based on template
657 if (template_name == "Full Overworld Mod") {
660 project.metadata.description = "Overworld modification project";
661 project.metadata.tags = {"overworld", "maps", "graphics"};
662 } else if (template_name == "Dungeon Designer") {
663 project.feature_flags.kSaveDungeonMaps = true;
664 project.workspace_settings.show_grid = true;
665 project.metadata.description = "Dungeon design and modification project";
666 project.metadata.tags = {"dungeons", "rooms", "design"};
667 } else if (template_name == "Graphics Pack") {
668 project.feature_flags.kSaveGraphicsSheet = true;
669 project.workspace_settings.show_grid = true;
670 project.metadata.description = "Graphics and sprite modification project";
671 project.metadata.tags = {"graphics", "sprites", "palettes"};
672 } else if (template_name == "Complete Overhaul") {
675 project.feature_flags.kSaveDungeonMaps = true;
676 project.feature_flags.kSaveGraphicsSheet = true;
677 project.metadata.description = "Complete ROM overhaul project";
678 project.metadata.tags = {"complete", "overhaul", "full-mod"};
679 }
680
681 status = project.Save();
682 if (!status.ok()) {
683 return status;
684 }
685
686 return project;
687}
688
689std::vector<std::string> ProjectManager::FindProjectsInDirectory(const std::string& directory) {
690 std::vector<std::string> projects;
691
692 try {
693 for (const auto& entry : std::filesystem::directory_iterator(directory)) {
694 if (entry.is_regular_file()) {
695 std::string filename = entry.path().filename().string();
696 if (filename.ends_with(".yaze") || filename.ends_with(".zsproj")) {
697 projects.push_back(entry.path().string());
698 }
699 }
700 }
701 } catch (const std::filesystem::filesystem_error& e) {
702 // Directory doesn't exist or can't be accessed
703 }
704
705 return projects;
706}
707
708absl::Status ProjectManager::BackupProject(const YazeProject& project) {
709 if (project.filepath.empty()) {
710 return absl::InvalidArgumentError("Project has no file path");
711 }
712
713 std::filesystem::path project_path(project.filepath);
714 std::filesystem::path backup_dir = project_path.parent_path() / "backups";
715 std::filesystem::create_directories(backup_dir);
716
717 auto now = std::chrono::system_clock::now();
718 auto time_t = std::chrono::system_clock::to_time_t(now);
719 std::stringstream ss;
720 ss << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S");
721
722 std::string backup_filename = project.name + "_backup_" + ss.str() + ".yaze";
723 std::filesystem::path backup_path = backup_dir / backup_filename;
724
725 try {
726 std::filesystem::copy_file(project.filepath, backup_path);
727 } catch (const std::filesystem::filesystem_error& e) {
728 return absl::InternalError(absl::StrFormat("Failed to backup project: %s", e.what()));
729 }
730
731 return absl::OkStatus();
732}
733
735 return project.Validate();
736}
737
738std::vector<std::string> ProjectManager::GetRecommendedFixesForProject(const YazeProject& project) {
739 std::vector<std::string> recommendations;
740
741 if (project.rom_filename.empty()) {
742 recommendations.push_back("Add a ROM file to begin editing");
743 }
744
745 if (project.code_folder.empty()) {
746 recommendations.push_back("Set up a code folder for assembly patches");
747 }
748
749 if (project.labels_filename.empty()) {
750 recommendations.push_back("Create a labels file for better organization");
751 }
752
753 if (project.metadata.description.empty()) {
754 recommendations.push_back("Add a project description for documentation");
755 }
756
757 if (project.git_repository.empty() && project.track_changes) {
758 recommendations.push_back("Consider setting up version control for your project");
759 }
760
761 auto missing_files = project.GetMissingFiles();
762 if (!missing_files.empty()) {
763 recommendations.push_back("Some project files are missing - use Project > Repair to fix");
764 }
765
766 return recommendations;
767}
768
769// Compatibility implementations for ResourceLabelManager and related classes
770bool ResourceLabelManager::LoadLabels(const std::string& filename) {
771 filename_ = filename;
772 std::ifstream file(filename);
773 if (!file.is_open()) {
774 labels_loaded_ = false;
775 return false;
776 }
777
778 labels_.clear();
779 std::string line;
780 std::string current_type = "";
781
782 while (std::getline(file, line)) {
783 if (line.empty() || line[0] == '#') continue;
784
785 // Check for type headers [type_name]
786 if (line[0] == '[' && line.back() == ']') {
787 current_type = line.substr(1, line.length() - 2);
788 continue;
789 }
790
791 // Parse key=value pairs
792 size_t eq_pos = line.find('=');
793 if (eq_pos != std::string::npos && !current_type.empty()) {
794 std::string key = line.substr(0, eq_pos);
795 std::string value = line.substr(eq_pos + 1);
796 labels_[current_type][key] = value;
797 }
798 }
799
800 file.close();
801 labels_loaded_ = true;
802 return true;
803}
804
806 if (filename_.empty()) return false;
807
808 std::ofstream file(filename_);
809 if (!file.is_open()) return false;
810
811 file << "# yaze Resource Labels\n";
812 file << "# Format: [type] followed by key=value pairs\n\n";
813
814 for (const auto& [type, type_labels] : labels_) {
815 if (!type_labels.empty()) {
816 file << "[" << type << "]\n";
817 for (const auto& [key, value] : type_labels) {
818 file << key << "=" << value << "\n";
819 }
820 file << "\n";
821 }
822 }
823
824 file.close();
825 return true;
826}
827
829 if (!p_open || !*p_open) return;
830
831 // Basic implementation - can be enhanced later
832 if (ImGui::Begin("Resource Labels", p_open)) {
833 ImGui::Text("Resource Labels Manager");
834 ImGui::Text("Labels loaded: %s", labels_loaded_ ? "Yes" : "No");
835 ImGui::Text("Total types: %zu", labels_.size());
836
837 for (const auto& [type, type_labels] : labels_) {
838 if (ImGui::TreeNode(type.c_str())) {
839 ImGui::Text("Labels: %zu", type_labels.size());
840 for (const auto& [key, value] : type_labels) {
841 ImGui::Text("%s = %s", key.c_str(), value.c_str());
842 }
843 ImGui::TreePop();
844 }
845 }
846 }
847 ImGui::End();
848}
849
850void ResourceLabelManager::EditLabel(const std::string& type, const std::string& key,
851 const std::string& newValue) {
852 labels_[type][key] = newValue;
853}
854
855void ResourceLabelManager::SelectableLabelWithNameEdit(bool selected, const std::string& type,
856 const std::string& key,
857 const std::string& defaultValue) {
858 // Basic implementation
859 if (ImGui::Selectable(absl::StrFormat("%s: %s", key.c_str(), GetLabel(type, key).c_str()).c_str(), selected)) {
860 // Handle selection
861 }
862}
863
864std::string ResourceLabelManager::GetLabel(const std::string& type, const std::string& key) {
865 auto type_it = labels_.find(type);
866 if (type_it == labels_.end()) return "";
867
868 auto label_it = type_it->second.find(key);
869 if (label_it == type_it->second.end()) return "";
870
871 return label_it->second;
872}
873
874std::string ResourceLabelManager::CreateOrGetLabel(const std::string& type, const std::string& key,
875 const std::string& defaultValue) {
876 auto existing = GetLabel(type, key);
877 if (!existing.empty()) return existing;
878
879 labels_[type][key] = defaultValue;
880 return defaultValue;
881}
882
883// ============================================================================
884// Embedded Labels Support
885// ============================================================================
886
888 try {
889 // Load all default Zelda3 resource names into resource_labels
891 use_embedded_labels = true;
892
893 LOG_DEBUG("Project", "Initialized embedded labels:");
894 LOG_DEBUG("Project", " - %d room names", resource_labels["room"].size());
895 LOG_DEBUG("Project", " - %d entrance names", resource_labels["entrance"].size());
896 LOG_DEBUG("Project", " - %d sprite names", resource_labels["sprite"].size());
897 LOG_DEBUG("Project", " - %d overlord names", resource_labels["overlord"].size());
898 LOG_DEBUG("Project", " - %d item names", resource_labels["item"].size());
899 LOG_DEBUG("Project", " - %d music names", resource_labels["music"].size());
900 LOG_DEBUG("Project", " - %d graphics names", resource_labels["graphics"].size());
901 LOG_DEBUG("Project", " - %d room effect names", resource_labels["room_effect"].size());
902 LOG_DEBUG("Project", " - %d room tag names", resource_labels["room_tag"].size());
903 LOG_DEBUG("Project", " - %d tile type names", resource_labels["tile_type"].size());
904 LOG_DEBUG("Project", " - %d overlord names", resource_labels["overlord"].size());
905 LOG_DEBUG("Project", " - %d item names", resource_labels["item"].size());
906
907 return absl::OkStatus();
908 } catch (const std::exception& e) {
909 return absl::InternalError(
910 absl::StrCat("Failed to initialize embedded labels: ", e.what()));
911 }
912}
913
914std::string YazeProject::GetLabel(const std::string& resource_type, int id,
915 const std::string& default_value) const {
916 // First check if we have a custom label override
917 auto type_it = resource_labels.find(resource_type);
918 if (type_it != resource_labels.end()) {
919 auto label_it = type_it->second.find(std::to_string(id));
920 if (label_it != type_it->second.end()) {
921 return label_it->second;
922 }
923 }
924
925 // If using embedded labels, fall back to Zelda3 defaults
927 return zelda3::Zelda3Labels::GetLabel(resource_type, id, default_value);
928 }
929
930 return default_value.empty()
931 ? resource_type + "_" + std::to_string(id)
932 : default_value;
933}
934
935// ============================================================================
936// JSON Format Support (Optional)
937// ============================================================================
938
939#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
940
941absl::Status YazeProject::LoadFromJsonFormat(const std::string& project_path) {
942 std::ifstream file(project_path);
943 if (!file.is_open()) {
944 return absl::InvalidArgumentError(
945 absl::StrFormat("Cannot open JSON project file: %s", project_path));
946 }
947
948 try {
949 json j;
950 file >> j;
951
952 // Parse project metadata
953 if (j.contains("yaze_project")) {
954 auto& proj = j["yaze_project"];
955
956 if (proj.contains("name")) name = proj["name"].get<std::string>();
957 if (proj.contains("description")) metadata.description = proj["description"].get<std::string>();
958 if (proj.contains("author")) metadata.author = proj["author"].get<std::string>();
959 if (proj.contains("version")) metadata.version = proj["version"].get<std::string>();
960 if (proj.contains("created")) metadata.created_date = proj["created"].get<std::string>();
961 if (proj.contains("modified")) metadata.last_modified = proj["modified"].get<std::string>();
962
963 // Files
964 if (proj.contains("rom_filename")) rom_filename = proj["rom_filename"].get<std::string>();
965 if (proj.contains("code_folder")) code_folder = proj["code_folder"].get<std::string>();
966 if (proj.contains("assets_folder")) assets_folder = proj["assets_folder"].get<std::string>();
967 if (proj.contains("patches_folder")) patches_folder = proj["patches_folder"].get<std::string>();
968 if (proj.contains("labels_filename")) labels_filename = proj["labels_filename"].get<std::string>();
969 if (proj.contains("symbols_filename")) symbols_filename = proj["symbols_filename"].get<std::string>();
970
971 // Embedded labels flag
972 if (proj.contains("use_embedded_labels")) {
973 use_embedded_labels = proj["use_embedded_labels"].get<bool>();
974 }
975
976 // Feature flags
977 if (proj.contains("feature_flags")) {
978 auto& flags = proj["feature_flags"];
979 // REMOVED: kLogInstructions (deprecated - DisassemblyViewer always active)
980 if (flags.contains("kSaveDungeonMaps"))
981 feature_flags.kSaveDungeonMaps = flags["kSaveDungeonMaps"].get<bool>();
982 if (flags.contains("kSaveGraphicsSheet"))
983 feature_flags.kSaveGraphicsSheet = flags["kSaveGraphicsSheet"].get<bool>();
984 }
985
986 // Workspace settings
987 if (proj.contains("workspace_settings")) {
988 auto& ws = proj["workspace_settings"];
989 if (ws.contains("auto_save_enabled"))
990 workspace_settings.autosave_enabled = ws["auto_save_enabled"].get<bool>();
991 if (ws.contains("auto_save_interval"))
992 workspace_settings.autosave_interval_secs = ws["auto_save_interval"].get<float>();
993 }
994
995 // Build settings
996 if (proj.contains("build_script")) build_script = proj["build_script"].get<std::string>();
997 if (proj.contains("output_folder")) output_folder = proj["output_folder"].get<std::string>();
998 if (proj.contains("git_repository")) git_repository = proj["git_repository"].get<std::string>();
999 if (proj.contains("track_changes")) track_changes = proj["track_changes"].get<bool>();
1000 }
1001
1002 return absl::OkStatus();
1003 } catch (const json::exception& e) {
1004 return absl::InvalidArgumentError(
1005 absl::StrFormat("JSON parse error: %s", e.what()));
1006 }
1007}
1008
1009absl::Status YazeProject::SaveToJsonFormat() {
1010 json j;
1011 auto& proj = j["yaze_project"];
1012
1013 // Metadata
1014 proj["version"] = metadata.version;
1015 proj["name"] = name;
1016 proj["author"] = metadata.author;
1017 proj["description"] = metadata.description;
1018 proj["created"] = metadata.created_date;
1019 proj["modified"] = metadata.last_modified;
1020
1021 // Files
1022 proj["rom_filename"] = rom_filename;
1023 proj["code_folder"] = code_folder;
1024 proj["assets_folder"] = assets_folder;
1025 proj["patches_folder"] = patches_folder;
1026 proj["labels_filename"] = labels_filename;
1027 proj["symbols_filename"] = symbols_filename;
1028 proj["output_folder"] = output_folder;
1029
1030 // Embedded labels
1031 proj["use_embedded_labels"] = use_embedded_labels;
1032
1033 // Feature flags
1034 // REMOVED: kLogInstructions (deprecated)
1035 proj["feature_flags"]["kSaveDungeonMaps"] = feature_flags.kSaveDungeonMaps;
1036 proj["feature_flags"]["kSaveGraphicsSheet"] = feature_flags.kSaveGraphicsSheet;
1037
1038 // Workspace settings
1039 proj["workspace_settings"]["auto_save_enabled"] = workspace_settings.autosave_enabled;
1040 proj["workspace_settings"]["auto_save_interval"] = workspace_settings.autosave_interval_secs;
1041
1042 // Build settings
1043 proj["build_script"] = build_script;
1044 proj["git_repository"] = git_repository;
1045 proj["track_changes"] = track_changes;
1046
1047 // Write to file
1048 std::ofstream file(filepath);
1049 if (!file.is_open()) {
1050 return absl::InvalidArgumentError(
1051 absl::StrFormat("Cannot write JSON project file: %s", filepath));
1052 }
1053
1054 file << j.dump(2); // Pretty print with 2-space indent
1055 return absl::OkStatus();
1056}
1057
1058#endif // YAZE_ENABLE_JSON_PROJECT_FORMAT
1059
1060// RecentFilesManager implementation
1062 auto config_dir = util::PlatformPaths::GetConfigDirectory();
1063 if (!config_dir.ok()) {
1064 return ""; // Or handle error appropriately
1065 }
1066 return (*config_dir / kRecentFilesFilename).string();
1067}
1068
1070 // Ensure config directory exists
1071 auto config_dir_status = util::PlatformPaths::GetConfigDirectory();
1072 if (!config_dir_status.ok()) {
1073 LOG_ERROR("Project", "Failed to get or create config directory: %s",
1074 config_dir_status.status().ToString().c_str());
1075 return;
1076 }
1077
1078 std::string filepath = GetFilePath();
1079 std::ofstream file(filepath);
1080 if (!file.is_open()) {
1081 LOG_WARN("RecentFilesManager", "Could not save recent files to %s", filepath.c_str());
1082 return;
1083 }
1084
1085 for (const auto& file_path : recent_files_) {
1086 file << file_path << std::endl;
1087 }
1088}
1089
1091 std::string filepath = GetFilePath();
1092 std::ifstream file(filepath);
1093 if (!file.is_open()) {
1094 // File doesn't exist yet, which is fine
1095 return;
1096 }
1097
1098 recent_files_.clear();
1099 std::string line;
1100 while (std::getline(file, line)) {
1101 if (!line.empty()) {
1102 recent_files_.push_back(line);
1103 }
1104 }
1105}
1106
1107} // namespace core
1108} // namespace yaze
static std::vector< ProjectTemplate > GetProjectTemplates()
Definition project.cc:610
static absl::Status ValidateProjectStructure(const YazeProject &project)
Definition project.cc:734
static std::vector< std::string > GetRecommendedFixesForProject(const YazeProject &project)
Definition project.cc:738
static absl::StatusOr< YazeProject > CreateFromTemplate(const std::string &template_name, const std::string &project_name, const std::string &base_path)
Definition project.cc:645
static absl::Status BackupProject(const YazeProject &project)
Definition project.cc:708
static std::vector< std::string > FindProjectsInDirectory(const std::string &directory)
Definition project.cc:689
std::string GetFilePath() const
Definition project.cc:1061
std::vector< std::string > recent_files_
Definition project.h:287
static absl::StatusOr< std::filesystem::path > GetConfigDirectory()
Get the user-specific configuration directory for YAZE.
#define ICON_MD_MAP
Definition icons.h:1171
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2074
#define ICON_MD_DOMAIN
Definition icons.h:601
#define ICON_MD_BUILD
Definition icons.h:326
#define ICON_MD_PALETTE
Definition icons.h:1368
#define LOG_DEBUG(category, format,...)
Definition log.h:104
#define LOG_ERROR(category, format,...)
Definition log.h:110
#define LOG_WARN(category, format,...)
Definition log.h:108
float ParseFloat(const std::string &value)
Definition project.cc:50
std::vector< std::string > ParseStringList(const std::string &value)
Definition project.cc:58
bool ParseBool(const std::string &value)
Definition project.cc:46
std::pair< std::string, std::string > ParseKeyValue(const std::string &line)
Definition project.cc:30
const std::string kRecentFilesFilename
Definition project.h:239
Main namespace for the application.
struct yaze::core::FeatureFlags::Flags::Overworld overworld
std::string last_modified
Definition project.h:34
std::vector< std::string > tags
Definition project.h:37
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:855
bool LoadLabels(const std::string &filename)
Definition project.cc:770
void DisplayLabels(bool *p_open)
Definition project.cc:828
std::string GetLabel(const std::string &type, const std::string &key)
Definition project.cc:864
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
Definition project.h:235
void EditLabel(const std::string &type, const std::string &key, const std::string &newValue)
Definition project.cc:850
std::string CreateOrGetLabel(const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:874
std::vector< std::string > recent_files
Definition project.h:70
std::map< std::string, std::string > custom_keybindings
Definition project.h:69
std::vector< std::string > saved_layouts
Definition project.h:58
std::string last_layout_preset
Definition project.h:57
std::map< std::string, bool > editor_visibility
Definition project.h:71
Modern project structure with comprehensive settings consolidation.
Definition project.h:78
absl::Status Open(const std::string &project_path)
Definition project.cc:115
absl::Status SaveToYazeFormat()
Definition project.cc:271
absl::Status LoadAllSettings()
Definition project.cc:445
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
Definition project.h:100
std::map< std::string, std::string > zscream_mappings
Definition project.h:130
std::vector< std::string > additional_roms
Definition project.h:88
WorkspaceSettings workspace_settings
Definition project.h:99
std::string assets_folder
Definition project.h:92
std::string code_folder
Definition project.h:91
struct yaze::core::YazeProject::AgentSettings agent_settings
absl::Status Validate() const
Definition project.cc:461
std::string symbols_filename
Definition project.h:95
std::string build_script
Definition project.h:106
absl::Status LoadFromYazeFormat(const std::string &project_path)
Definition project.cc:166
FeatureFlags::Flags feature_flags
Definition project.h:98
std::string GenerateProjectId() const
Definition project.cc:603
std::string GetLabel(const std::string &resource_type, int id, const std::string &default_value="") const
Definition project.cc:914
std::string GetRelativePath(const std::string &absolute_path) const
Definition project.cc:539
std::string labels_filename
Definition project.h:94
std::string output_folder
Definition project.h:107
std::string git_repository
Definition project.h:111
absl::Status RepairProject()
Definition project.cc:504
bool IsEmpty() const
Definition project.cc:562
absl::Status Save()
Definition project.cc:150
std::string GetDisplayName() const
Definition project.cc:532
absl::Status InitializeEmbeddedLabels()
Definition project.cc:887
absl::Status ImportFromZScreamFormat(const std::string &project_path)
Definition project.cc:566
std::vector< std::string > build_configurations
Definition project.h:108
absl::Status Create(const std::string &project_name, const std::string &base_path)
Definition project.cc:76
std::vector< std::string > GetMissingFiles() const
Definition project.cc:488
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:553
std::string zscream_project_file
Definition project.h:129
std::string filepath
Definition project.h:82
ProjectFormat format
Definition project.h:83
std::string rom_filename
Definition project.h:86
absl::Status ResetToDefaults()
Definition project.cc:456
absl::Status SaveAllSettings()
Definition project.cc:451
std::string rom_backup_folder
Definition project.h:87
ProjectMetadata metadata
Definition project.h:80
absl::Status ImportZScreamProject(const std::string &zscream_project_path)
Definition project.cc:402
absl::Status ExportForZScream(const std::string &target_path)
Definition project.cc:424
absl::Status SaveAs(const std::string &new_path)
Definition project.cc:154
std::string patches_folder
Definition project.h:93
static std::unordered_map< std::string, std::unordered_map< std::string, std::string > > ToResourceLabels()
Convert all labels to a structured map for project embedding.
static std::string GetLabel(const std::string &resource_type, int id, const std::string &default_value="")
Get a label by resource type and ID.