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 <algorithm>
4#include <cctype>
5#include <chrono>
6#include <filesystem>
7#include <fstream>
8#include <iomanip>
9#include <sstream>
10
11#include "absl/strings/match.h"
12#include "absl/strings/str_format.h"
13#include "absl/strings/str_join.h"
14#include "absl/strings/str_split.h"
15#include "app/gui/core/icons.h"
16#include "imgui/imgui.h"
17#include "util/file_util.h"
18#include "util/json.h"
19#include "util/log.h"
20#include "util/macro.h"
21#include "util/platform_paths.h"
22#include "yaze_config.h"
24
25#ifdef __EMSCRIPTEN__
27#endif
28
29// #ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
30// #include "nlohmann/json.hpp"
31// using json = nlohmann::json;
32// #endif
33
34namespace yaze {
35namespace project {
36
37namespace {
38std::string ToLowerCopy(std::string value) {
39 std::transform(
40 value.begin(), value.end(), value.begin(),
41 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
42 return value;
43}
44
45// Helper functions for parsing key-value pairs
46std::pair<std::string, std::string> ParseKeyValue(const std::string& line) {
47 size_t eq_pos = line.find('=');
48 if (eq_pos == std::string::npos)
49 return {"", ""};
50
51 std::string key = line.substr(0, eq_pos);
52 std::string value = line.substr(eq_pos + 1);
53
54 // Trim whitespace
55 key.erase(0, key.find_first_not_of(" \t"));
56 key.erase(key.find_last_not_of(" \t") + 1);
57 value.erase(0, value.find_first_not_of(" \t"));
58 value.erase(value.find_last_not_of(" \t") + 1);
59
60 return {key, value};
61}
62
63bool ParseBool(const std::string& value) {
64 return value == "true" || value == "1" || value == "yes";
65}
66
67float ParseFloat(const std::string& value) {
68 try {
69 return std::stof(value);
70 } catch (...) {
71 return 0.0f;
72 }
73}
74
75std::vector<std::string> ParseStringList(const std::string& value) {
76 std::vector<std::string> result;
77 if (value.empty())
78 return result;
79
80 std::vector<std::string> parts = absl::StrSplit(value, ',');
81 for (const auto& part : parts) {
82 std::string trimmed = std::string(part);
83 trimmed.erase(0, trimmed.find_first_not_of(" \t"));
84 trimmed.erase(trimmed.find_last_not_of(" \t") + 1);
85 if (!trimmed.empty()) {
86 result.push_back(trimmed);
87 }
88 }
89 return result;
90}
91
92std::vector<uint16_t> ParseHexUintList(const std::string& value) {
93 std::vector<uint16_t> result;
94 if (value.empty()) {
95 return result;
96 }
97
98 auto parts = ParseStringList(value);
99 result.reserve(parts.size());
100 for (const auto& part : parts) {
101 std::string token = part;
102 if (token.rfind("0x", 0) == 0 || token.rfind("0X", 0) == 0) {
103 token = token.substr(2);
104 try {
105 result.push_back(static_cast<uint16_t>(std::stoul(token, nullptr, 16)));
106 } catch (...) {
107 // Ignore malformed entries
108 }
109 } else {
110 try {
111 result.push_back(static_cast<uint16_t>(std::stoul(token, nullptr, 10)));
112 } catch (...) {
113 // Ignore malformed entries
114 }
115 }
116 }
117 return result;
118}
119
120std::optional<uint32_t> ParseHexUint32(const std::string& value) {
121 if (value.empty()) {
122 return std::nullopt;
123 }
124 std::string token = value;
125 if (token.rfind("0x", 0) == 0 || token.rfind("0X", 0) == 0) {
126 token = token.substr(2);
127 try {
128 return static_cast<uint32_t>(std::stoul(token, nullptr, 16));
129 } catch (...) {
130 return std::nullopt;
131 }
132 }
133 try {
134 return static_cast<uint32_t>(std::stoul(token, nullptr, 10));
135 } catch (...) {
136 return std::nullopt;
137 }
138}
139
140std::string FormatHexUintList(const std::vector<uint16_t>& values) {
141 return absl::StrJoin(values, ",", [](std::string* out, uint16_t value) {
142 out->append(absl::StrFormat("0x%02X", value));
143 });
144}
145
146std::string FormatHexUint32(uint32_t value) {
147 return absl::StrFormat("0x%06X", value);
148}
149
150std::string SanitizeStorageKey(absl::string_view input) {
151 std::string key(input);
152 for (char& c : key) {
153 if (!std::isalnum(static_cast<unsigned char>(c))) {
154 c = '_';
155 }
156 }
157 if (key.empty()) {
158 key = "project";
159 }
160 return key;
161}
162} // namespace
163
164std::string RomRoleToString(RomRole role) {
165 switch (role) {
166 case RomRole::kBase:
167 return "base";
169 return "patched";
171 return "release";
172 case RomRole::kDev:
173 default:
174 return "dev";
175 }
176}
177
178RomRole ParseRomRole(absl::string_view value) {
179 std::string lower = ToLowerCopy(std::string(value));
180 if (lower == "base") {
181 return RomRole::kBase;
182 }
183 if (lower == "patched") {
184 return RomRole::kPatched;
185 }
186 if (lower == "release") {
187 return RomRole::kRelease;
188 }
189 return RomRole::kDev;
190}
191
193 switch (policy) {
195 return "allow";
197 return "block";
199 default:
200 return "warn";
201 }
202}
203
204RomWritePolicy ParseRomWritePolicy(absl::string_view value) {
205 std::string lower = ToLowerCopy(std::string(value));
206 if (lower == "allow") {
208 }
209 if (lower == "block") {
211 }
213}
214
215// YazeProject Implementation
216absl::Status YazeProject::Create(const std::string& project_name,
217 const std::string& base_path) {
218 name = project_name;
219 filepath = base_path + "/" + project_name + ".yaze";
220
221 // Initialize metadata
222 auto now = std::chrono::system_clock::now();
223 auto time_t = std::chrono::system_clock::to_time_t(now);
224 std::stringstream ss;
225 ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
226
227 metadata.created_date = ss.str();
228 metadata.last_modified = ss.str();
230 metadata.version = "2.0";
231 metadata.created_by = "YAZE";
233
235
236#ifndef __EMSCRIPTEN__
237 // Create project directory structure
238 std::filesystem::path project_dir(base_path + "/" + project_name);
239 std::filesystem::create_directories(project_dir);
240 std::filesystem::create_directories(project_dir / "code");
241 std::filesystem::create_directories(project_dir / "assets");
242 std::filesystem::create_directories(project_dir / "patches");
243 std::filesystem::create_directories(project_dir / "backups");
244 std::filesystem::create_directories(project_dir / "output");
245
246 // Set folder paths
247 code_folder = (project_dir / "code").string();
248 assets_folder = (project_dir / "assets").string();
249 patches_folder = (project_dir / "patches").string();
250 rom_backup_folder = (project_dir / "backups").string();
251 output_folder = (project_dir / "output").string();
252 labels_filename = (project_dir / "labels.txt").string();
253 symbols_filename = (project_dir / "symbols.txt").string();
254#else
255 // WASM: keep paths relative; persistence handled by WasmStorage/IDBFS
256 code_folder = "code";
257 assets_folder = "assets";
258 patches_folder = "patches";
259 rom_backup_folder = "backups";
260 output_folder = "output";
261 labels_filename = "labels.txt";
262 symbols_filename = "symbols.txt";
263#endif
264
265 return Save();
266}
267
268// static
269std::string YazeProject::ResolveBundleRoot(const std::string& path) {
270 if (path.empty()) {
271 return {};
272 }
273
274 // Walk up the path hierarchy looking for a directory whose filename
275 // (extension) is ".yazeproj". The first match from the leaf upward wins.
276 std::error_code ec;
277 auto current = std::filesystem::path(path).lexically_normal();
278
279 for (; !current.empty(); current = current.parent_path()) {
280 if (current.extension() == ".yazeproj") {
281 // Must be an existing directory to count as a valid bundle root.
282 if (std::filesystem::is_directory(current, ec) && !ec) {
283 return current.string();
284 }
285 }
286 // Guard against infinite loop at the filesystem root.
287 if (current == current.parent_path()) {
288 break;
289 }
290 }
291
292 return {};
293}
294
295absl::Status YazeProject::Open(const std::string& project_path) {
296 // Resolve bundle root: if the user opened a file *inside* a .yazeproj
297 // bundle, normalize to the bundle root directory so the existing
298 // .yazeproj handling takes over.
299 std::string resolved_path = project_path;
300 const std::string bundle_root = ResolveBundleRoot(project_path);
301 if (!bundle_root.empty() && bundle_root != project_path) {
302 // The user pointed at a file inside a bundle; redirect to the root.
303 resolved_path = bundle_root;
304 }
305
306 filepath = resolved_path;
307
308#ifdef __EMSCRIPTEN__
309 // Prefer persistent storage in WASM builds
310 auto storage_key = MakeStorageKey("project");
311 auto storage_or = platform::WasmStorage::LoadProject(storage_key);
312 if (storage_or.ok()) {
313 return ParseFromString(storage_or.value());
314 }
315#endif
316
317 // Determine format and load accordingly
318 absl::Status load_status;
319 if (resolved_path.ends_with(".yazeproj")) {
321
322 const std::filesystem::path bundle_path(resolved_path);
323 std::error_code ec;
324 if (!std::filesystem::exists(bundle_path, ec) || ec ||
325 !std::filesystem::is_directory(bundle_path, ec) || ec) {
326 return absl::InvalidArgumentError(
327 absl::StrFormat("Project bundle does not exist: %s", resolved_path));
328 }
329
330 // Bundle convention: store the actual project config at the root so both
331 // desktop and iOS can open the same `.yazeproj` directory.
332 const std::filesystem::path project_file = bundle_path / "project.yaze";
333 filepath = project_file.string();
334
335 if (!std::filesystem::exists(project_file, ec) || ec) {
336 // Create a minimal portable project file for the bundle if missing.
338 name = bundle_path.stem().string();
339
340 // Initialize metadata timestamps (Create() normally does this).
341 auto now = std::chrono::system_clock::now();
342 auto time_t = std::chrono::system_clock::to_time_t(now);
343 std::stringstream ss;
344 ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
345 if (metadata.created_date.empty()) {
346 metadata.created_date = ss.str();
347 }
348 metadata.last_modified = ss.str();
349 if (metadata.yaze_version.empty()) {
351 }
352 if (metadata.version.empty()) {
353 metadata.version = "2.0";
354 }
355 if (metadata.created_by.empty()) {
356 metadata.created_by = "YAZE";
357 }
358 if (metadata.project_id.empty()) {
360 }
361
362 // Bundle layout defaults (paths stored as absolute; serializer writes
363 // relative values for portability).
364 const std::filesystem::path rom_candidate = bundle_path / "rom";
365 if (std::filesystem::exists(rom_candidate, ec) &&
366 std::filesystem::is_regular_file(rom_candidate, ec) && !ec) {
367 rom_filename = rom_candidate.string();
368 }
369
370 const std::filesystem::path project_dir = bundle_path / "project";
371 const std::filesystem::path code_dir = bundle_path / "code";
372 if (std::filesystem::exists(project_dir, ec) &&
373 std::filesystem::is_directory(project_dir, ec) && !ec) {
374 code_folder = project_dir.string();
375 } else if (std::filesystem::exists(code_dir, ec) &&
376 std::filesystem::is_directory(code_dir, ec) && !ec) {
377 code_folder = code_dir.string();
378 }
379
380 assets_folder = (bundle_path / "assets").string();
381 patches_folder = (bundle_path / "patches").string();
382 rom_backup_folder = (bundle_path / "backups").string();
383 output_folder = (bundle_path / "output").string();
384 labels_filename = (bundle_path / "labels.txt").string();
385 symbols_filename = (bundle_path / "symbols.txt").string();
386
387 load_status = SaveToYazeFormat();
388 } else {
389 load_status = LoadFromYazeFormat(project_file.string());
390 }
391 } else if (resolved_path.ends_with(".yaze")) {
393
394 // Try to detect if it's JSON format by peeking at first character
395 std::ifstream file(resolved_path);
396 if (file.is_open()) {
397 std::stringstream buffer;
398 buffer << file.rdbuf();
399 std::string content = buffer.str();
400
401#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
402 if (!content.empty() && content.front() == '{') {
403 LOG_DEBUG("Project", "Detected JSON format project file");
404 load_status = LoadFromJsonFormat(resolved_path);
405 } else {
406 load_status = ParseFromString(content);
407 }
408#else
409 load_status = ParseFromString(content);
410#endif
411 } else {
412 return absl::InvalidArgumentError(
413 absl::StrFormat("Cannot open project file: %s", resolved_path));
414 }
415 } else if (resolved_path.ends_with(".zsproj")) {
417 load_status = ImportFromZScreamFormat(resolved_path);
418 } else {
419 return absl::InvalidArgumentError("Unsupported project file format");
420 }
421
422 if (!load_status.ok()) {
423 return load_status;
424 }
425
426 // Normalize project-relative paths so downstream code never depends on the
427 // process working directory (important for iOS and portable bundles).
429
430 // Auto-load hack manifest if configured or discoverable
432
433 return absl::OkStatus();
434}
435
436absl::Status YazeProject::Save() {
437 return SaveToYazeFormat();
438}
439
440absl::Status YazeProject::SaveAs(const std::string& new_path) {
441 std::string old_filepath = filepath;
442 filepath = new_path;
443
444 auto status = Save();
445 if (!status.ok()) {
446 filepath = old_filepath; // Restore on failure
447 }
448
449 return status;
450}
451
452std::string YazeProject::MakeStorageKey(absl::string_view suffix) const {
453 std::string base;
454 if (!metadata.project_id.empty()) {
455 base = metadata.project_id;
456 } else if (!name.empty()) {
457 base = name;
458 } else if (!filepath.empty()) {
459 base = std::filesystem::path(filepath).stem().string();
460 }
461 base = SanitizeStorageKey(base);
462 if (suffix.empty()) {
463 return base;
464 }
465 return absl::StrFormat("%s_%s", base, suffix);
466}
467
468absl::StatusOr<std::string> YazeProject::SerializeToString() const {
469 std::ostringstream file;
470
471 // Write header comment
472 file << "# yaze Project File\n";
473 file << "# Format Version: 2.0\n";
474 file << "# Generated by YAZE " << metadata.yaze_version << "\n";
475 file << "# Last Modified: " << metadata.last_modified << "\n\n";
476
477 // Project section
478 file << "[project]\n";
479 file << "name=" << name << "\n";
480 file << "description=" << metadata.description << "\n";
481 file << "author=" << metadata.author << "\n";
482 file << "license=" << metadata.license << "\n";
483 file << "version=" << metadata.version << "\n";
484 file << "created_date=" << metadata.created_date << "\n";
485 file << "last_modified=" << metadata.last_modified << "\n";
486 file << "yaze_version=" << metadata.yaze_version << "\n";
487 file << "created_by=" << metadata.created_by << "\n";
488 file << "project_id=" << metadata.project_id << "\n";
489 file << "tags=" << absl::StrJoin(metadata.tags, ",") << "\n\n";
490
491 // Files section
492 file << "[files]\n";
493 file << "rom_filename=" << GetRelativePath(rom_filename) << "\n";
494 file << "rom_backup_folder=" << GetRelativePath(rom_backup_folder) << "\n";
495 file << "code_folder=" << GetRelativePath(code_folder) << "\n";
496 file << "assets_folder=" << GetRelativePath(assets_folder) << "\n";
497 file << "patches_folder=" << GetRelativePath(patches_folder) << "\n";
498 file << "labels_filename=" << GetRelativePath(labels_filename) << "\n";
499 file << "symbols_filename=" << GetRelativePath(symbols_filename) << "\n";
500 file << "output_folder=" << GetRelativePath(output_folder) << "\n";
501 file << "custom_objects_folder=" << GetRelativePath(custom_objects_folder)
502 << "\n";
503 file << "hack_manifest_file=" << GetRelativePath(hack_manifest_file) << "\n";
504 file << "additional_roms=" << absl::StrJoin(additional_roms, ",") << "\n\n";
505
506 // ROM metadata section
507 file << "[rom]\n";
508 file << "role=" << RomRoleToString(rom_metadata.role) << "\n";
509 file << "expected_hash=" << rom_metadata.expected_hash << "\n";
510 file << "write_policy=" << RomWritePolicyToString(rom_metadata.write_policy)
511 << "\n\n";
512
513 // Feature flags section
514 file << "[feature_flags]\n";
515 file << "load_custom_overworld="
516 << (feature_flags.overworld.kLoadCustomOverworld ? "true" : "false")
517 << "\n";
518 file << "apply_zs_custom_overworld_asm="
520 : "false")
521 << "\n";
522 file << "save_dungeon_maps="
523 << (feature_flags.kSaveDungeonMaps ? "true" : "false") << "\n";
524 file << "save_overworld_maps="
525 << (feature_flags.overworld.kSaveOverworldMaps ? "true" : "false")
526 << "\n";
527 file << "save_overworld_entrances="
528 << (feature_flags.overworld.kSaveOverworldEntrances ? "true" : "false")
529 << "\n";
530 file << "save_overworld_exits="
531 << (feature_flags.overworld.kSaveOverworldExits ? "true" : "false")
532 << "\n";
533 file << "save_overworld_items="
534 << (feature_flags.overworld.kSaveOverworldItems ? "true" : "false")
535 << "\n";
536 file << "save_overworld_properties="
537 << (feature_flags.overworld.kSaveOverworldProperties ? "true" : "false")
538 << "\n";
539 file << "save_dungeon_objects="
540 << (feature_flags.dungeon.kSaveObjects ? "true" : "false") << "\n";
541 file << "save_dungeon_sprites="
542 << (feature_flags.dungeon.kSaveSprites ? "true" : "false") << "\n";
543 file << "save_dungeon_room_headers="
544 << (feature_flags.dungeon.kSaveRoomHeaders ? "true" : "false") << "\n";
545 file << "save_dungeon_torches="
546 << (feature_flags.dungeon.kSaveTorches ? "true" : "false") << "\n";
547 file << "save_dungeon_pits="
548 << (feature_flags.dungeon.kSavePits ? "true" : "false") << "\n";
549 file << "save_dungeon_blocks="
550 << (feature_flags.dungeon.kSaveBlocks ? "true" : "false") << "\n";
551 file << "save_dungeon_collision="
552 << (feature_flags.dungeon.kSaveCollision ? "true" : "false") << "\n";
553 file << "save_dungeon_chests="
554 << (feature_flags.dungeon.kSaveChests ? "true" : "false") << "\n";
555 file << "save_dungeon_pot_items="
556 << (feature_flags.dungeon.kSavePotItems ? "true" : "false") << "\n";
557 file << "save_dungeon_palettes="
558 << (feature_flags.dungeon.kSavePalettes ? "true" : "false") << "\n";
559 file << "save_graphics_sheet="
560 << (feature_flags.kSaveGraphicsSheet ? "true" : "false") << "\n";
561 file << "save_all_palettes="
562 << (feature_flags.kSaveAllPalettes ? "true" : "false") << "\n";
563 file << "save_gfx_groups="
564 << (feature_flags.kSaveGfxGroups ? "true" : "false") << "\n";
565 file << "save_messages=" << (feature_flags.kSaveMessages ? "true" : "false")
566 << "\n";
567 file << "enable_custom_objects="
568 << (feature_flags.kEnableCustomObjects ? "true" : "false") << "\n\n";
569
570 // Workspace settings section
571 file << "[workspace]\n";
572 file << "font_global_scale=" << workspace_settings.font_global_scale << "\n";
573 file << "dark_mode=" << (workspace_settings.dark_mode ? "true" : "false")
574 << "\n";
575 file << "ui_theme=" << workspace_settings.ui_theme << "\n";
576 file << "autosave_enabled="
577 << (workspace_settings.autosave_enabled ? "true" : "false") << "\n";
578 file << "autosave_interval_secs=" << workspace_settings.autosave_interval_secs
579 << "\n";
580 file << "backup_on_save="
581 << (workspace_settings.backup_on_save ? "true" : "false") << "\n";
582 file << "backup_retention_count=" << workspace_settings.backup_retention_count
583 << "\n";
584 file << "backup_keep_daily="
585 << (workspace_settings.backup_keep_daily ? "true" : "false") << "\n";
586 file << "backup_keep_daily_days=" << workspace_settings.backup_keep_daily_days
587 << "\n";
588 file << "show_grid=" << (workspace_settings.show_grid ? "true" : "false")
589 << "\n";
590 file << "show_collision="
591 << (workspace_settings.show_collision ? "true" : "false") << "\n";
592 file << "prefer_hmagic_names="
593 << (workspace_settings.prefer_hmagic_names ? "true" : "false") << "\n";
594 file << "last_layout_preset=" << workspace_settings.last_layout_preset
595 << "\n";
596 file << "saved_layouts="
597 << absl::StrJoin(workspace_settings.saved_layouts, ",") << "\n";
598 file << "recent_files=" << absl::StrJoin(workspace_settings.recent_files, ",")
599 << "\n\n";
600
601 // Dungeon overlay settings section
602 auto track_tiles = dungeon_overlay.track_tiles;
603 if (track_tiles.empty()) {
604 for (uint16_t tile = 0xB0; tile <= 0xBE; ++tile) {
605 track_tiles.push_back(tile);
606 }
607 }
608 auto track_stop_tiles = dungeon_overlay.track_stop_tiles;
609 if (track_stop_tiles.empty()) {
610 track_stop_tiles = {0xB7, 0xB8, 0xB9, 0xBA};
611 }
612 auto track_switch_tiles = dungeon_overlay.track_switch_tiles;
613 if (track_switch_tiles.empty()) {
614 track_switch_tiles = {0xD0, 0xD1, 0xD2, 0xD3};
615 }
616 auto track_object_ids = dungeon_overlay.track_object_ids;
617 if (track_object_ids.empty()) {
618 track_object_ids = {0x31};
619 }
620 auto minecart_sprite_ids = dungeon_overlay.minecart_sprite_ids;
621 if (minecart_sprite_ids.empty()) {
622 minecart_sprite_ids = {0xA3};
623 }
624 file << "[dungeon_overlay]\n";
625 file << "track_tiles=" << FormatHexUintList(track_tiles) << "\n";
626 file << "track_stop_tiles=" << FormatHexUintList(track_stop_tiles) << "\n";
627 file << "track_switch_tiles=" << FormatHexUintList(track_switch_tiles)
628 << "\n";
629 file << "track_object_ids=" << FormatHexUintList(track_object_ids) << "\n";
630 file << "minecart_sprite_ids=" << FormatHexUintList(minecart_sprite_ids)
631 << "\n\n";
632
633 if (!rom_address_overrides.addresses.empty()) {
634 file << "[rom_addresses]\n";
635 for (const auto& [key, value] : rom_address_overrides.addresses) {
636 file << key << "=" << FormatHexUint32(value) << "\n";
637 }
638 file << "\n";
639 }
640
641 if (!custom_object_files.empty()) {
642 file << "[custom_objects]\n";
643 for (const auto& [object_id, files] : custom_object_files) {
644 file << absl::StrFormat("object_0x%X", object_id) << "="
645 << absl::StrJoin(files, ",") << "\n";
646 }
647 file << "\n";
648 }
649
650 // AI Agent settings section
651 file << "[agent_settings]\n";
652 file << "ai_provider=" << agent_settings.ai_provider << "\n";
653 file << "ai_model=" << agent_settings.ai_model << "\n";
654 file << "ollama_host=" << agent_settings.ollama_host << "\n";
655 file << "gemini_api_key=" << agent_settings.gemini_api_key << "\n";
656 file << "custom_system_prompt="
658 file << "use_custom_prompt="
659 << (agent_settings.use_custom_prompt ? "true" : "false") << "\n";
660 file << "show_reasoning="
661 << (agent_settings.show_reasoning ? "true" : "false") << "\n";
662 file << "verbose=" << (agent_settings.verbose ? "true" : "false") << "\n";
663 file << "max_tool_iterations=" << agent_settings.max_tool_iterations << "\n";
664 file << "max_retry_attempts=" << agent_settings.max_retry_attempts << "\n";
665 file << "temperature=" << agent_settings.temperature << "\n";
666 file << "top_p=" << agent_settings.top_p << "\n";
667 file << "max_output_tokens=" << agent_settings.max_output_tokens << "\n";
668 file << "stream_responses="
669 << (agent_settings.stream_responses ? "true" : "false") << "\n";
670 file << "favorite_models="
671 << absl::StrJoin(agent_settings.favorite_models, ",") << "\n";
672 file << "model_chain=" << absl::StrJoin(agent_settings.model_chain, ",")
673 << "\n";
674 file << "chain_mode=" << agent_settings.chain_mode << "\n";
675 file << "enable_tool_resources="
676 << (agent_settings.enable_tool_resources ? "true" : "false") << "\n";
677 file << "enable_tool_dungeon="
678 << (agent_settings.enable_tool_dungeon ? "true" : "false") << "\n";
679 file << "enable_tool_overworld="
680 << (agent_settings.enable_tool_overworld ? "true" : "false") << "\n";
681 file << "enable_tool_messages="
682 << (agent_settings.enable_tool_messages ? "true" : "false") << "\n";
683 file << "enable_tool_dialogue="
684 << (agent_settings.enable_tool_dialogue ? "true" : "false") << "\n";
685 file << "enable_tool_gui="
686 << (agent_settings.enable_tool_gui ? "true" : "false") << "\n";
687 file << "enable_tool_music="
688 << (agent_settings.enable_tool_music ? "true" : "false") << "\n";
689 file << "enable_tool_sprite="
690 << (agent_settings.enable_tool_sprite ? "true" : "false") << "\n";
691 file << "enable_tool_emulator="
692 << (agent_settings.enable_tool_emulator ? "true" : "false") << "\n";
693 file << "enable_tool_memory_inspector="
694 << (agent_settings.enable_tool_memory_inspector ? "true" : "false")
695 << "\n";
696 file << "builder_blueprint_path=" << agent_settings.builder_blueprint_path
697 << "\n\n";
698
699 // Custom keybindings section
701 file << "[keybindings]\n";
702 for (const auto& [key, value] : workspace_settings.custom_keybindings) {
703 file << key << "=" << value << "\n";
704 }
705 file << "\n";
706 }
707
708 // Editor visibility section
710 file << "[editor_visibility]\n";
711 for (const auto& [key, value] : workspace_settings.editor_visibility) {
712 file << key << "=" << (value ? "true" : "false") << "\n";
713 }
714 file << "\n";
715 }
716
717 // Resource labels sections
718 for (const auto& [type, labels] : resource_labels) {
719 if (!labels.empty()) {
720 file << "[labels_" << type << "]\n";
721 for (const auto& [key, value] : labels) {
722 file << key << "=" << value << "\n";
723 }
724 file << "\n";
725 }
726 }
727
728 // Build settings section
729 file << "[build]\n";
730 file << "build_script=" << build_script << "\n";
731 file << "output_folder=" << GetRelativePath(output_folder) << "\n";
732 file << "git_repository=" << git_repository << "\n";
733 file << "track_changes=" << (track_changes ? "true" : "false") << "\n";
734 file << "build_configurations=" << absl::StrJoin(build_configurations, ",")
735 << "\n";
736 file << "build_target=" << build_target << "\n";
737 file << "asm_entry_point=" << asm_entry_point << "\n";
738 file << "asm_sources=" << absl::StrJoin(asm_sources, ",") << "\n";
739 file << "last_build_hash=" << last_build_hash << "\n";
740 file << "build_number=" << build_number << "\n\n";
741
742 // Music persistence section (for WASM/offline state)
743 file << "[music]\n";
744 file << "persist_custom_music="
745 << (music_persistence.persist_custom_music ? "true" : "false") << "\n";
746 file << "storage_key=" << music_persistence.storage_key << "\n";
747 file << "last_saved_at=" << music_persistence.last_saved_at << "\n\n";
748
749 // ZScream compatibility section
750 if (!zscream_project_file.empty()) {
751 file << "[zscream_compatibility]\n";
752 file << "original_project_file=" << zscream_project_file << "\n";
753 for (const auto& [key, value] : zscream_mappings) {
754 file << key << "=" << value << "\n";
755 }
756 file << "\n";
757 }
758
759 file << "# End of YAZE Project File\n";
760 return file.str();
761}
762
763absl::Status YazeProject::ParseFromString(const std::string& content) {
764 std::istringstream stream(content);
765 std::string line;
766 std::string current_section;
767
768 while (std::getline(stream, line)) {
769 if (line.empty() || line[0] == '#')
770 continue;
771
772 if (line.front() == '[' && line.back() == ']') {
773 current_section = line.substr(1, line.length() - 2);
774 continue;
775 }
776
777 auto [key, value] = ParseKeyValue(line);
778 if (key.empty())
779 continue;
780
781 if (current_section == "project") {
782 if (key == "name")
783 name = value;
784 else if (key == "description")
785 metadata.description = value;
786 else if (key == "author")
787 metadata.author = value;
788 else if (key == "license")
789 metadata.license = value;
790 else if (key == "version")
791 metadata.version = value;
792 else if (key == "created_date")
793 metadata.created_date = value;
794 else if (key == "last_modified")
795 metadata.last_modified = value;
796 else if (key == "yaze_version")
797 metadata.yaze_version = value;
798 else if (key == "created_by")
799 metadata.created_by = value;
800 else if (key == "tags")
801 metadata.tags = ParseStringList(value);
802 else if (key == "project_id")
803 metadata.project_id = value;
804 } else if (current_section == "files") {
805 if (key == "rom_filename")
806 rom_filename = value;
807 else if (key == "rom_backup_folder")
808 rom_backup_folder = value;
809 else if (key == "code_folder")
810 code_folder = value;
811 else if (key == "assets_folder")
812 assets_folder = value;
813 else if (key == "patches_folder")
814 patches_folder = value;
815 else if (key == "labels_filename")
816 labels_filename = value;
817 else if (key == "symbols_filename")
818 symbols_filename = value;
819 else if (key == "output_folder")
820 output_folder = value;
821 else if (key == "custom_objects_folder")
822 custom_objects_folder = value;
823 else if (key == "hack_manifest_file")
824 hack_manifest_file = value;
825 else if (key == "additional_roms")
826 additional_roms = ParseStringList(value);
827 } else if (current_section == "rom") {
828 if (key == "role")
830 else if (key == "expected_hash")
832 else if (key == "write_policy")
834 } else if (current_section == "feature_flags") {
835 if (key == "load_custom_overworld")
837 else if (key == "apply_zs_custom_overworld_asm")
839 else if (key == "save_dungeon_maps")
840 feature_flags.kSaveDungeonMaps = ParseBool(value);
841 else if (key == "save_overworld_maps")
842 feature_flags.overworld.kSaveOverworldMaps = ParseBool(value);
843 else if (key == "save_overworld_entrances")
845 else if (key == "save_overworld_exits")
847 else if (key == "save_overworld_items")
849 else if (key == "save_overworld_properties")
851 else if (key == "save_dungeon_objects")
852 feature_flags.dungeon.kSaveObjects = ParseBool(value);
853 else if (key == "save_dungeon_sprites")
854 feature_flags.dungeon.kSaveSprites = ParseBool(value);
855 else if (key == "save_dungeon_room_headers")
856 feature_flags.dungeon.kSaveRoomHeaders = ParseBool(value);
857 else if (key == "save_dungeon_torches")
858 feature_flags.dungeon.kSaveTorches = ParseBool(value);
859 else if (key == "save_dungeon_pits")
860 feature_flags.dungeon.kSavePits = ParseBool(value);
861 else if (key == "save_dungeon_blocks")
862 feature_flags.dungeon.kSaveBlocks = ParseBool(value);
863 else if (key == "save_dungeon_collision")
864 feature_flags.dungeon.kSaveCollision = ParseBool(value);
865 else if (key == "save_dungeon_chests")
866 feature_flags.dungeon.kSaveChests = ParseBool(value);
867 else if (key == "save_dungeon_pot_items")
868 feature_flags.dungeon.kSavePotItems = ParseBool(value);
869 else if (key == "save_dungeon_palettes")
870 feature_flags.dungeon.kSavePalettes = ParseBool(value);
871 else if (key == "save_graphics_sheet")
872 feature_flags.kSaveGraphicsSheet = ParseBool(value);
873 else if (key == "save_all_palettes")
874 feature_flags.kSaveAllPalettes = ParseBool(value);
875 else if (key == "save_gfx_groups")
876 feature_flags.kSaveGfxGroups = ParseBool(value);
877 else if (key == "save_messages")
878 feature_flags.kSaveMessages = ParseBool(value);
879 else if (key == "enable_custom_objects")
880 feature_flags.kEnableCustomObjects = ParseBool(value);
881 } else if (current_section == "workspace") {
882 if (key == "font_global_scale")
883 workspace_settings.font_global_scale = ParseFloat(value);
884 else if (key == "dark_mode")
885 workspace_settings.dark_mode = ParseBool(value);
886 else if (key == "ui_theme")
888 else if (key == "autosave_enabled")
889 workspace_settings.autosave_enabled = ParseBool(value);
890 else if (key == "autosave_interval_secs")
891 workspace_settings.autosave_interval_secs = ParseFloat(value);
892 else if (key == "backup_on_save")
893 workspace_settings.backup_on_save = ParseBool(value);
894 else if (key == "backup_retention_count")
896 else if (key == "backup_keep_daily")
897 workspace_settings.backup_keep_daily = ParseBool(value);
898 else if (key == "backup_keep_daily_days")
900 else if (key == "show_grid")
901 workspace_settings.show_grid = ParseBool(value);
902 else if (key == "show_collision")
903 workspace_settings.show_collision = ParseBool(value);
904 else if (key == "prefer_hmagic_names")
905 workspace_settings.prefer_hmagic_names = ParseBool(value);
906 else if (key == "last_layout_preset")
908 else if (key == "saved_layouts")
909 workspace_settings.saved_layouts = ParseStringList(value);
910 else if (key == "recent_files")
911 workspace_settings.recent_files = ParseStringList(value);
912 } else if (current_section == "dungeon_overlay") {
913 if (key == "track_tiles")
914 dungeon_overlay.track_tiles = ParseHexUintList(value);
915 else if (key == "track_stop_tiles")
916 dungeon_overlay.track_stop_tiles = ParseHexUintList(value);
917 else if (key == "track_switch_tiles")
918 dungeon_overlay.track_switch_tiles = ParseHexUintList(value);
919 else if (key == "track_object_ids")
920 dungeon_overlay.track_object_ids = ParseHexUintList(value);
921 else if (key == "minecart_sprite_ids")
922 dungeon_overlay.minecart_sprite_ids = ParseHexUintList(value);
923 } else if (current_section == "rom_addresses") {
924 auto parsed = ParseHexUint32(value);
925 if (parsed.has_value()) {
926 rom_address_overrides.addresses[key] = *parsed;
927 }
928 } else if (current_section == "custom_objects") {
929 std::string id_token = key;
930 if (absl::StartsWith(id_token, "object_")) {
931 id_token = id_token.substr(7);
932 }
933 auto parsed = ParseHexUint32(id_token);
934 if (parsed.has_value()) {
935 custom_object_files[static_cast<int>(*parsed)] = ParseStringList(value);
936 }
937 } else if (current_section == "agent_settings") {
938 if (key == "ai_provider")
940 else if (key == "ai_model")
941 agent_settings.ai_model = value;
942 else if (key == "ollama_host")
944 else if (key == "gemini_api_key")
946 else if (key == "custom_system_prompt")
948 else if (key == "use_custom_prompt")
949 agent_settings.use_custom_prompt = ParseBool(value);
950 else if (key == "show_reasoning")
951 agent_settings.show_reasoning = ParseBool(value);
952 else if (key == "verbose")
953 agent_settings.verbose = ParseBool(value);
954 else if (key == "max_tool_iterations")
955 agent_settings.max_tool_iterations = std::stoi(value);
956 else if (key == "max_retry_attempts")
957 agent_settings.max_retry_attempts = std::stoi(value);
958 else if (key == "temperature")
959 agent_settings.temperature = ParseFloat(value);
960 else if (key == "top_p")
961 agent_settings.top_p = ParseFloat(value);
962 else if (key == "max_output_tokens")
963 agent_settings.max_output_tokens = std::stoi(value);
964 else if (key == "stream_responses")
965 agent_settings.stream_responses = ParseBool(value);
966 else if (key == "favorite_models")
967 agent_settings.favorite_models = ParseStringList(value);
968 else if (key == "model_chain")
969 agent_settings.model_chain = ParseStringList(value);
970 else if (key == "chain_mode")
971 agent_settings.chain_mode = std::stoi(value);
972 else if (key == "enable_tool_resources")
973 agent_settings.enable_tool_resources = ParseBool(value);
974 else if (key == "enable_tool_dungeon")
975 agent_settings.enable_tool_dungeon = ParseBool(value);
976 else if (key == "enable_tool_overworld")
977 agent_settings.enable_tool_overworld = ParseBool(value);
978 else if (key == "enable_tool_messages")
979 agent_settings.enable_tool_messages = ParseBool(value);
980 else if (key == "enable_tool_dialogue")
981 agent_settings.enable_tool_dialogue = ParseBool(value);
982 else if (key == "enable_tool_gui")
983 agent_settings.enable_tool_gui = ParseBool(value);
984 else if (key == "enable_tool_music")
985 agent_settings.enable_tool_music = ParseBool(value);
986 else if (key == "enable_tool_sprite")
987 agent_settings.enable_tool_sprite = ParseBool(value);
988 else if (key == "enable_tool_emulator")
989 agent_settings.enable_tool_emulator = ParseBool(value);
990 else if (key == "enable_tool_memory_inspector")
992 else if (key == "builder_blueprint_path")
994 } else if (current_section == "build") {
995 if (key == "build_script")
996 build_script = value;
997 else if (key == "output_folder")
998 output_folder = value;
999 else if (key == "git_repository")
1000 git_repository = value;
1001 else if (key == "track_changes")
1002 track_changes = ParseBool(value);
1003 else if (key == "build_configurations")
1004 build_configurations = ParseStringList(value);
1005 else if (key == "build_target")
1006 build_target = value;
1007 else if (key == "asm_entry_point")
1008 asm_entry_point = value;
1009 else if (key == "asm_sources")
1010 asm_sources = ParseStringList(value);
1011 else if (key == "last_build_hash")
1012 last_build_hash = value;
1013 else if (key == "build_number")
1014 build_number = std::stoi(value);
1015 } else if (current_section.rfind("labels_", 0) == 0) {
1016 std::string label_type = current_section.substr(7);
1017 resource_labels[label_type][key] = value;
1018 } else if (current_section == "keybindings") {
1020 } else if (current_section == "editor_visibility") {
1021 workspace_settings.editor_visibility[key] = ParseBool(value);
1022 } else if (current_section == "zscream_compatibility") {
1023 if (key == "original_project_file")
1024 zscream_project_file = value;
1025 else
1026 zscream_mappings[key] = value;
1027 } else if (current_section == "music") {
1028 if (key == "persist_custom_music")
1029 music_persistence.persist_custom_music = ParseBool(value);
1030 else if (key == "storage_key")
1032 else if (key == "last_saved_at")
1034 }
1035 }
1036
1037 if (metadata.project_id.empty()) {
1039 }
1040 if (metadata.created_by.empty()) {
1041 metadata.created_by = "YAZE";
1042 }
1043 if (music_persistence.storage_key.empty()) {
1045 }
1046
1047 return absl::OkStatus();
1048}
1049
1050absl::Status YazeProject::LoadFromYazeFormat(const std::string& project_path) {
1051#ifdef __EMSCRIPTEN__
1052 auto storage_key = MakeStorageKey("project");
1053 auto storage_or = platform::WasmStorage::LoadProject(storage_key);
1054 if (storage_or.ok()) {
1055 return ParseFromString(storage_or.value());
1056 }
1057#endif // __EMSCRIPTEN__
1058
1059 std::ifstream file(project_path);
1060 if (!file.is_open()) {
1061 return absl::InvalidArgumentError(
1062 absl::StrFormat("Cannot open project file: %s", project_path));
1063 }
1064
1065 std::stringstream buffer;
1066 buffer << file.rdbuf();
1067 file.close();
1068 return ParseFromString(buffer.str());
1069}
1070
1072 // Update last modified timestamp
1073 auto now = std::chrono::system_clock::now();
1074 auto time_t = std::chrono::system_clock::to_time_t(now);
1075 std::stringstream ss;
1076 ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
1077 metadata.last_modified = ss.str();
1078 if (music_persistence.storage_key.empty()) {
1080 }
1081
1082 // Ensure we serialize clean relative paths even if the user edited fields
1083 // into relative form (and avoid relying on cwd when opening later).
1085
1086 ASSIGN_OR_RETURN(auto serialized, SerializeToString());
1087
1088#ifdef __EMSCRIPTEN__
1089 auto storage_status =
1090 platform::WasmStorage::SaveProject(MakeStorageKey("project"), serialized);
1091 if (!storage_status.ok()) {
1092 return storage_status;
1093 }
1094#else
1095 if (!filepath.empty()) {
1096 std::ofstream file(filepath);
1097 if (!file.is_open()) {
1098 return absl::InvalidArgumentError(
1099 absl::StrFormat("Cannot create project file: %s", filepath));
1100 }
1101 file << serialized;
1102 file.close();
1103 }
1104#endif
1105
1106 return absl::OkStatus();
1107}
1108
1110 const std::string& zscream_project_path) {
1111 // Basic ZScream project import (to be expanded based on ZScream format)
1112 zscream_project_file = zscream_project_path;
1114
1115 // Extract project name from path
1116 std::filesystem::path zs_path(zscream_project_path);
1117 name = zs_path.stem().string() + "_imported";
1118
1119 // Set up basic mapping for common fields
1120 zscream_mappings["rom_file"] = "rom_filename";
1121 zscream_mappings["source_code"] = "code_folder";
1122 zscream_mappings["project_name"] = "name";
1123
1125
1126 // TODO: Implement actual ZScream format parsing when format is known
1127 // For now, just create a project structure that can be manually configured
1128
1129 return absl::OkStatus();
1130}
1131
1132absl::Status YazeProject::ExportForZScream(const std::string& target_path) {
1133 // Create a simplified project file that ZScream might understand
1134 std::ofstream file(target_path);
1135 if (!file.is_open()) {
1136 return absl::InvalidArgumentError(
1137 absl::StrFormat("Cannot create ZScream project file: %s", target_path));
1138 }
1139
1140 // Write in a simple format that ZScream might understand
1141 file << "# ZScream Compatible Project File\n";
1142 file << "# Exported from YAZE " << metadata.yaze_version << "\n\n";
1143 file << "name=" << name << "\n";
1144 file << "rom_file=" << rom_filename << "\n";
1145 file << "source_code=" << code_folder << "\n";
1146 file << "description=" << metadata.description << "\n";
1147 file << "author=" << metadata.author << "\n";
1148 file << "created_with=YAZE " << metadata.yaze_version << "\n";
1149
1150 file.close();
1151 return absl::OkStatus();
1152}
1153
1155 // Consolidated loading of all settings from project file
1156 // This replaces scattered config loading throughout the application
1158}
1159
1161 // Consolidated saving of all settings to project file
1162 return SaveToYazeFormat();
1163}
1164
1167 return Save();
1168}
1169
1170absl::Status YazeProject::Validate() const {
1171 std::vector<std::string> errors;
1172
1173 if (name.empty())
1174 errors.push_back("Project name is required");
1175 if (filepath.empty())
1176 errors.push_back("Project file path is required");
1177 if (rom_filename.empty())
1178 errors.push_back("ROM file is required");
1179
1180#ifndef __EMSCRIPTEN__
1181 // Check if files exist
1182 if (!rom_filename.empty() &&
1183 !std::filesystem::exists(GetAbsolutePath(rom_filename))) {
1184 errors.push_back("ROM file does not exist: " + rom_filename);
1185 }
1186
1187 if (!code_folder.empty() &&
1188 !std::filesystem::exists(GetAbsolutePath(code_folder))) {
1189 errors.push_back("Code folder does not exist: " + code_folder);
1190 }
1191
1192 if (!labels_filename.empty() &&
1193 !std::filesystem::exists(GetAbsolutePath(labels_filename))) {
1194 errors.push_back("Labels file does not exist: " + labels_filename);
1195 }
1196#endif // __EMSCRIPTEN__
1197
1198 if (!errors.empty()) {
1199 return absl::InvalidArgumentError(absl::StrJoin(errors, "; "));
1200 }
1201
1202 return absl::OkStatus();
1203}
1204
1205std::vector<std::string> YazeProject::GetMissingFiles() const {
1206 std::vector<std::string> missing;
1207
1208#ifndef __EMSCRIPTEN__
1209 if (!rom_filename.empty() &&
1210 !std::filesystem::exists(GetAbsolutePath(rom_filename))) {
1211 missing.push_back(rom_filename);
1212 }
1213 if (!labels_filename.empty() &&
1214 !std::filesystem::exists(GetAbsolutePath(labels_filename))) {
1215 missing.push_back(labels_filename);
1216 }
1217 if (!symbols_filename.empty() &&
1218 !std::filesystem::exists(GetAbsolutePath(symbols_filename))) {
1219 missing.push_back(symbols_filename);
1220 }
1221#endif // __EMSCRIPTEN__
1222
1223 return missing;
1224}
1225
1227#ifdef __EMSCRIPTEN__
1228 // In the web build, filesystem layout is virtual; nothing to repair eagerly.
1229 return absl::OkStatus();
1230#else
1231 // Create missing directories
1232 std::vector<std::string> folders = {code_folder, assets_folder,
1235
1236 for (const auto& folder : folders) {
1237 if (!folder.empty()) {
1238 std::filesystem::path abs_path = GetAbsolutePath(folder);
1239 if (!std::filesystem::exists(abs_path)) {
1240 std::filesystem::create_directories(abs_path);
1241 }
1242 }
1243 }
1244
1245 // Create missing files with defaults
1246 if (!labels_filename.empty()) {
1247 std::filesystem::path abs_labels = GetAbsolutePath(labels_filename);
1248 if (!std::filesystem::exists(abs_labels)) {
1249 std::ofstream labels_file(abs_labels);
1250 labels_file << "# yaze Resource Labels\n";
1251 labels_file << "# Format: [type] key=value\n\n";
1252 labels_file.close();
1253 }
1254 }
1255
1256 return absl::OkStatus();
1257#endif
1258}
1259
1260std::string YazeProject::GetDisplayName() const {
1261 if (!metadata.description.empty()) {
1262 return metadata.description;
1263 }
1264 return name.empty() ? "Untitled Project" : name;
1265}
1266
1268 const std::string& absolute_path) const {
1269 if (absolute_path.empty() || filepath.empty())
1270 return absolute_path;
1271
1272 std::filesystem::path project_dir =
1273 std::filesystem::path(filepath).parent_path();
1274 std::filesystem::path abs_path(absolute_path);
1275
1276 try {
1277 std::filesystem::path relative =
1278 std::filesystem::relative(abs_path.lexically_normal(), project_dir);
1279 // Persist relative paths in a platform-neutral format for project files.
1280 return relative.generic_string();
1281 } catch (...) {
1282 // Return normalized absolute path if relative conversion fails.
1283 return abs_path.lexically_normal().generic_string();
1284 }
1285}
1286
1288 const std::string& relative_path) const {
1289 if (relative_path.empty() || filepath.empty())
1290 return relative_path;
1291
1292 std::filesystem::path project_dir =
1293 std::filesystem::path(filepath).parent_path();
1294 std::filesystem::path abs_path(relative_path);
1295 if (abs_path.is_absolute()) {
1296 abs_path = abs_path.lexically_normal();
1297 abs_path.make_preferred();
1298 return abs_path.string();
1299 }
1300 abs_path = (project_dir / abs_path).lexically_normal();
1301 abs_path.make_preferred();
1302
1303 return abs_path.string();
1304}
1305
1307#ifdef __EMSCRIPTEN__
1308 // Web builds rely on a virtual filesystem and often use relative paths.
1309 return;
1310#endif
1311 if (filepath.empty()) {
1312 return;
1313 }
1314
1315 auto normalize = [this](std::string* path) {
1316 if (!path || path->empty()) {
1317 return;
1318 }
1319 *path = GetAbsolutePath(*path);
1320 };
1321
1322 normalize(&rom_filename);
1323 normalize(&rom_backup_folder);
1324 normalize(&code_folder);
1325 normalize(&assets_folder);
1326 normalize(&patches_folder);
1327 normalize(&labels_filename);
1328 normalize(&symbols_filename);
1329 normalize(&custom_objects_folder);
1330 normalize(&hack_manifest_file);
1331 normalize(&output_folder);
1332
1333 for (auto& rom_path : additional_roms) {
1334 if (!rom_path.empty()) {
1335 rom_path = GetAbsolutePath(rom_path);
1336 }
1337 }
1338}
1339
1341 return name.empty() && rom_filename.empty() && code_folder.empty();
1342}
1343
1345 const std::string& project_path) {
1346 // TODO: Implement ZScream format parsing when format specification is
1347 // available For now, create a basic project that can be manually configured
1348
1349 std::filesystem::path zs_path(project_path);
1350 name = zs_path.stem().string() + "_imported";
1351 zscream_project_file = project_path;
1352
1354
1355 return absl::OkStatus();
1356}
1357
1361
1363#ifdef __EMSCRIPTEN__
1366 return; // Hack manifests not supported in web builds
1367#endif
1368
1369 // Clear previous state so we never keep a stale manifest across project loads.
1372
1373 std::filesystem::path loaded_manifest_path;
1374 auto load_manifest = [&](const std::filesystem::path& candidate,
1375 bool update_project_setting) -> bool {
1376 if (candidate.empty() || !std::filesystem::exists(candidate)) {
1377 return false;
1378 }
1379 auto status = hack_manifest.LoadFromFile(candidate.string());
1380 if (!status.ok()) {
1381 LOG_WARN("Project", "Failed to load hack manifest %s: %s",
1382 candidate.string().c_str(),
1383 std::string(status.message()).c_str());
1384 return false;
1385 }
1386 loaded_manifest_path = candidate;
1387 if (update_project_setting) {
1388 hack_manifest_file = GetRelativePath(candidate.string());
1389 }
1390 LOG_DEBUG("Project", "Loaded hack manifest: %s",
1391 candidate.string().c_str());
1393 return true;
1394 };
1395
1396 // Priority 1: Explicit hack_manifest_file setting from project.
1397 if (!hack_manifest_file.empty()) {
1398 (void)load_manifest(GetAbsolutePath(hack_manifest_file), false);
1399 }
1400
1401 // Priority 2: Auto-discover hack_manifest.json in code_folder.
1402 if (!hack_manifest.loaded() && !code_folder.empty()) {
1403 auto code_path = GetAbsolutePath(code_folder);
1404 auto candidate = std::filesystem::path(code_path) / "hack_manifest.json";
1405 (void)load_manifest(candidate, true);
1406 }
1407
1408 // Priority 3: Fallback to the project file directory (or its parent).
1409 if (!hack_manifest.loaded() && !filepath.empty()) {
1410 const std::filesystem::path project_dir =
1411 std::filesystem::path(filepath).parent_path();
1412 (void)load_manifest(project_dir / "hack_manifest.json", true);
1413 if (!hack_manifest.loaded() && project_dir.has_parent_path()) {
1414 (void)load_manifest(project_dir.parent_path() / "hack_manifest.json",
1415 true);
1416 }
1417 }
1418
1419 if (!hack_manifest.loaded()) {
1420 return;
1421 }
1422
1424
1425 auto try_load_registry = [&](const std::filesystem::path& base) -> bool {
1426 if (base.empty()) {
1427 return false;
1428 }
1429 const auto planning = base / "Docs" / "Dev" / "Planning";
1430 if (!std::filesystem::exists(planning)) {
1431 return false;
1432 }
1433 auto status = hack_manifest.LoadProjectRegistry(base.string());
1434 if (!status.ok()) {
1435 LOG_WARN("Project", "Failed to load project registry from %s: %s",
1436 base.string().c_str(), std::string(status.message()).c_str());
1437 return false;
1438 }
1440 };
1441
1442 bool registry_loaded = false;
1443
1444 // Prefer configured code_folder when valid.
1445 if (!code_folder.empty()) {
1446 registry_loaded =
1447 try_load_registry(std::filesystem::path(GetAbsolutePath(code_folder)));
1448 }
1449
1450 // Fallback to the manifest directory if code_folder is stale/misconfigured.
1451 if (!registry_loaded && !loaded_manifest_path.empty()) {
1452 registry_loaded = try_load_registry(loaded_manifest_path.parent_path());
1453 }
1454
1455 // Last fallback: project directory.
1456 if (!registry_loaded && !filepath.empty()) {
1457 registry_loaded =
1458 try_load_registry(std::filesystem::path(filepath).parent_path());
1459 }
1460
1461 if (!registry_loaded) {
1462 LOG_WARN("Project",
1463 "Hack manifest loaded but project registry was not found "
1464 "(code_folder='%s', manifest='%s')",
1465 code_folder.c_str(), loaded_manifest_path.string().c_str());
1466 return;
1467 }
1468
1469 // Inject all Oracle resource labels into project resource_labels.
1470 size_t injected = 0;
1471 for (const auto& [type_key, labels] :
1473 for (const auto& [id_str, label] : labels) {
1474 resource_labels[type_key][id_str] = label;
1475 ++injected;
1476 }
1477 }
1478 LOG_DEBUG("Project", "Loaded project registry: %zu resource labels injected",
1479 injected);
1480}
1481
1483 if (metadata.project_id.empty()) {
1485 }
1486
1487 // Initialize default feature flags
1502 // REMOVED: kLogInstructions (deprecated)
1503
1504 // Initialize default workspace settings
1507 workspace_settings.ui_theme = "default";
1509 workspace_settings.autosave_interval_secs = 300.0f; // 5 minutes
1516
1517 // Initialize default dungeon overlay settings (minecart tracks)
1519 for (uint16_t tile = 0xB0; tile <= 0xBE; ++tile) {
1520 dungeon_overlay.track_tiles.push_back(tile);
1521 }
1522 dungeon_overlay.track_stop_tiles = {0xB7, 0xB8, 0xB9, 0xBA};
1523 dungeon_overlay.track_switch_tiles = {0xD0, 0xD1, 0xD2, 0xD3};
1526
1530
1531 // Initialize default build configurations
1532 build_configurations = {"Debug", "Release", "Distribution"};
1533 build_target.clear();
1534 asm_entry_point = "asm/main.asm";
1535 asm_sources = {"asm"};
1536 last_build_hash.clear();
1537 build_number = 0;
1538
1539 track_changes = true;
1540
1544
1545 if (metadata.created_by.empty()) {
1546 metadata.created_by = "YAZE";
1547 }
1548}
1549
1551 auto now = std::chrono::system_clock::now().time_since_epoch();
1552 auto timestamp =
1553 std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
1554 return absl::StrFormat("yaze_project_%lld", timestamp);
1555}
1556
1557// ProjectManager Implementation
1558std::vector<ProjectManager::ProjectTemplate>
1560 std::vector<ProjectTemplate> templates;
1561
1562 // ==========================================================================
1563 // ZSCustomOverworld Templates (Recommended)
1564 // ==========================================================================
1565
1566 // Vanilla ROM Hack - no ZSO
1567 {
1569 t.name = "Vanilla ROM Hack";
1570 t.description =
1571 "Standard ROM editing without custom ASM. Limited to vanilla features.";
1575 false;
1580 templates.push_back(t);
1581 }
1582
1583 // ZSCustomOverworld v2 - Basic expansion
1584 {
1586 t.name = "ZSCustomOverworld v2";
1587 t.description =
1588 "Basic overworld expansion: custom BG colors, main palettes, parent "
1589 "system.";
1590 t.icon = ICON_MD_MAP;
1593 true;
1600 t.template_project.metadata.tags = {"zso_v2", "overworld", "expansion"};
1601 templates.push_back(t);
1602 }
1603
1604 // ZSCustomOverworld v3 - Full features (Recommended)
1605 {
1607 t.name = "ZSCustomOverworld v3 (Recommended)";
1608 t.description =
1609 "Full overworld expansion: wide/tall areas, animated GFX, overlays, "
1610 "all features.";
1614 true;
1624 t.template_project.metadata.tags = {"zso_v3", "overworld", "full",
1625 "recommended"};
1626 templates.push_back(t);
1627 }
1628
1629 // Randomizer Compatible
1630 {
1632 t.name = "Randomizer Compatible";
1633 t.description =
1634 "Compatible with ALttP Randomizer. Minimal custom features to avoid "
1635 "conflicts.";
1639 false;
1642 t.template_project.metadata.tags = {"randomizer", "compatible", "minimal"};
1643 templates.push_back(t);
1644 }
1645
1646 // ==========================================================================
1647 // Editor-Focused Templates
1648 // ==========================================================================
1649
1650 // Dungeon Designer
1651 {
1653 t.name = "Dungeon Designer";
1654 t.description = "Focused on dungeon creation and modification.";
1655 t.icon = ICON_MD_DOMAIN;
1659 "dungeon_default";
1660 t.template_project.metadata.tags = {"dungeons", "rooms", "design"};
1661 templates.push_back(t);
1662 }
1663
1664 // Graphics Pack
1665 {
1667 t.name = "Graphics Pack";
1668 t.description =
1669 "Project focused on graphics, sprites, and visual modifications.";
1676 "graphics_default";
1677 t.template_project.metadata.tags = {"graphics", "sprites", "palettes"};
1678 templates.push_back(t);
1679 }
1680
1681 // Complete Overhaul
1682 {
1684 t.name = "Complete Overhaul";
1685 t.description = "Full-scale ROM hack with all features enabled.";
1686 t.icon = ICON_MD_BUILD;
1689 true;
1699 t.template_project.metadata.tags = {"complete", "overhaul", "full-mod"};
1700 templates.push_back(t);
1701 }
1702
1703 return templates;
1704}
1705
1706absl::StatusOr<YazeProject> ProjectManager::CreateFromTemplate(
1707 const std::string& template_name, const std::string& project_name,
1708 const std::string& base_path) {
1709 YazeProject project;
1710 auto status = project.Create(project_name, base_path);
1711 if (!status.ok()) {
1712 return status;
1713 }
1714
1715 // Customize based on template
1716 if (template_name == "Full Overworld Mod") {
1719 project.metadata.description = "Overworld modification project";
1720 project.metadata.tags = {"overworld", "maps", "graphics"};
1721 } else if (template_name == "Dungeon Designer") {
1722 project.feature_flags.kSaveDungeonMaps = true;
1723 project.workspace_settings.show_grid = true;
1724 project.metadata.description = "Dungeon design and modification project";
1725 project.metadata.tags = {"dungeons", "rooms", "design"};
1726 } else if (template_name == "Graphics Pack") {
1727 project.feature_flags.kSaveGraphicsSheet = true;
1728 project.workspace_settings.show_grid = true;
1729 project.metadata.description = "Graphics and sprite modification project";
1730 project.metadata.tags = {"graphics", "sprites", "palettes"};
1731 } else if (template_name == "Complete Overhaul") {
1734 project.feature_flags.kSaveDungeonMaps = true;
1735 project.feature_flags.kSaveGraphicsSheet = true;
1736 project.metadata.description = "Complete ROM overhaul project";
1737 project.metadata.tags = {"complete", "overhaul", "full-mod"};
1738 }
1739
1740 status = project.Save();
1741 if (!status.ok()) {
1742 return status;
1743 }
1744
1745 return project;
1746}
1747
1749 const std::string& directory) {
1750#ifdef __EMSCRIPTEN__
1751 (void)directory;
1752 return {};
1753#else
1754 std::vector<std::string> projects;
1755
1756 try {
1757 for (const auto& entry : std::filesystem::directory_iterator(directory)) {
1758 if (entry.is_regular_file()) {
1759 std::string filename = entry.path().filename().string();
1760 if (filename.ends_with(".yaze") || filename.ends_with(".zsproj")) {
1761 projects.push_back(entry.path().string());
1762 }
1763 } else if (entry.is_directory()) {
1764 std::string filename = entry.path().filename().string();
1765 if (filename.ends_with(".yazeproj")) {
1766 projects.push_back(entry.path().string());
1767 }
1768 }
1769 }
1770 } catch (const std::filesystem::filesystem_error& e) {
1771 // Directory doesn't exist or can't be accessed
1772 }
1773
1774 return projects;
1775#endif // __EMSCRIPTEN__
1776}
1777
1778absl::Status ProjectManager::BackupProject(const YazeProject& project) {
1779#ifdef __EMSCRIPTEN__
1780 (void)project;
1781 return absl::UnimplementedError(
1782 "Project backups are not supported in the web build");
1783#else
1784 if (project.filepath.empty()) {
1785 return absl::InvalidArgumentError("Project has no file path");
1786 }
1787
1788 std::filesystem::path project_path(project.filepath);
1789 std::filesystem::path backup_dir = project_path.parent_path() / "backups";
1790 std::filesystem::create_directories(backup_dir);
1791
1792 auto now = std::chrono::system_clock::now();
1793 auto time_t = std::chrono::system_clock::to_time_t(now);
1794 std::stringstream ss;
1795 ss << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S");
1796
1797 std::string backup_filename = project.name + "_backup_" + ss.str() + ".yaze";
1798 std::filesystem::path backup_path = backup_dir / backup_filename;
1799
1800 try {
1801 std::filesystem::copy_file(project.filepath, backup_path);
1802 } catch (const std::filesystem::filesystem_error& e) {
1803 return absl::InternalError(
1804 absl::StrFormat("Failed to backup project: %s", e.what()));
1805 }
1806
1807 return absl::OkStatus();
1808#endif
1809}
1810
1812 const YazeProject& project) {
1813 return project.Validate();
1814}
1815
1817 const YazeProject& project) {
1818 std::vector<std::string> recommendations;
1819
1820 if (project.rom_filename.empty()) {
1821 recommendations.push_back("Add a ROM file to begin editing");
1822 }
1823
1824 if (project.code_folder.empty()) {
1825 recommendations.push_back("Set up a code folder for assembly patches");
1826 }
1827
1828 if (project.labels_filename.empty()) {
1829 recommendations.push_back("Create a labels file for better organization");
1830 }
1831
1832 if (project.metadata.description.empty()) {
1833 recommendations.push_back("Add a project description for documentation");
1834 }
1835
1836 if (project.git_repository.empty() && project.track_changes) {
1837 recommendations.push_back(
1838 "Consider setting up version control for your project");
1839 }
1840
1841 auto missing_files = project.GetMissingFiles();
1842 if (!missing_files.empty()) {
1843 recommendations.push_back(
1844 "Some project files are missing - use Project > Repair to fix");
1845 }
1846
1847 return recommendations;
1848}
1849
1850// Compatibility implementations for ResourceLabelManager and related classes
1851bool ResourceLabelManager::LoadLabels(const std::string& filename) {
1852 filename_ = filename;
1853 std::ifstream file(filename);
1854 if (!file.is_open()) {
1855 labels_loaded_ = false;
1856 return false;
1857 }
1858
1859 labels_.clear();
1860 std::string line;
1861 std::string current_type = "";
1862
1863 while (std::getline(file, line)) {
1864 if (line.empty() || line[0] == '#')
1865 continue;
1866
1867 // Check for type headers [type_name]
1868 if (line[0] == '[' && line.back() == ']') {
1869 current_type = line.substr(1, line.length() - 2);
1870 continue;
1871 }
1872
1873 // Parse key=value pairs
1874 size_t eq_pos = line.find('=');
1875 if (eq_pos != std::string::npos && !current_type.empty()) {
1876 std::string key = line.substr(0, eq_pos);
1877 std::string value = line.substr(eq_pos + 1);
1878 labels_[current_type][key] = value;
1879 }
1880 }
1881
1882 file.close();
1883 labels_loaded_ = true;
1884 return true;
1885}
1886
1888 if (filename_.empty())
1889 return false;
1890
1891 std::ofstream file(filename_);
1892 if (!file.is_open())
1893 return false;
1894
1895 file << "# yaze Resource Labels\n";
1896 file << "# Format: [type] followed by key=value pairs\n\n";
1897
1898 for (const auto& [type, type_labels] : labels_) {
1899 if (!type_labels.empty()) {
1900 file << "[" << type << "]\n";
1901 for (const auto& [key, value] : type_labels) {
1902 file << key << "=" << value << "\n";
1903 }
1904 file << "\n";
1905 }
1906 }
1907
1908 file.close();
1909 return true;
1910}
1911
1913 if (!p_open || !*p_open)
1914 return;
1915
1916 // Basic implementation - can be enhanced later
1917 if (ImGui::Begin("Resource Labels", p_open)) {
1918 ImGui::Text("Resource Labels Manager");
1919 ImGui::Text("Labels loaded: %s", labels_loaded_ ? "Yes" : "No");
1920 ImGui::Text("Total types: %zu", labels_.size());
1921
1922 for (const auto& [type, type_labels] : labels_) {
1923 if (ImGui::TreeNode(type.c_str())) {
1924 ImGui::Text("Labels: %zu", type_labels.size());
1925 for (const auto& [key, value] : type_labels) {
1926 ImGui::Text("%s = %s", key.c_str(), value.c_str());
1927 }
1928 ImGui::TreePop();
1929 }
1930 }
1931 }
1932 ImGui::End();
1933}
1934
1935void ResourceLabelManager::EditLabel(const std::string& type,
1936 const std::string& key,
1937 const std::string& newValue) {
1938 labels_[type][key] = newValue;
1939}
1940
1942 bool selected, const std::string& type, const std::string& key,
1943 const std::string& defaultValue) {
1944 // Basic implementation
1945 if (ImGui::Selectable(
1946 absl::StrFormat("%s: %s", key.c_str(), GetLabel(type, key).c_str())
1947 .c_str(),
1948 selected)) {
1949 // Handle selection
1950 }
1951}
1952
1953std::string ResourceLabelManager::GetLabel(const std::string& type,
1954 const std::string& key) {
1955 auto type_it = labels_.find(type);
1956 if (type_it == labels_.end())
1957 return "";
1958
1959 auto label_it = type_it->second.find(key);
1960 if (label_it == type_it->second.end())
1961 return "";
1962
1963 return label_it->second;
1964}
1965
1967 const std::string& type, const std::string& key,
1968 const std::string& defaultValue) {
1969 auto existing = GetLabel(type, key);
1970 if (!existing.empty())
1971 return existing;
1972
1973 labels_[type][key] = defaultValue;
1974 return defaultValue;
1975}
1976
1977// ============================================================================
1978// Embedded Labels Support
1979// ============================================================================
1980
1982 const std::unordered_map<
1983 std::string, std::unordered_map<std::string, std::string>>& labels) {
1984 try {
1985 // Load all default Zelda3 resource names into resource_labels
1986 // We merge them with existing labels, prioritizing existing overrides?
1987 // Or just overwrite? The previous code was:
1988 // resource_labels = zelda3::Zelda3Labels::ToResourceLabels();
1989 // which implies overwriting. But we want to keep overrides if possible.
1990 // However, this is usually called on load.
1991
1992 // Let's overwrite for now to match previous behavior, assuming overrides
1993 // are loaded afterwards or this is initial setup.
1994 // Actually, if we load project then init embedded labels, we might lose overrides.
1995 // But typically overrides are loaded from the project file *into* resource_labels.
1996 // If we call this, we might clobber them.
1997 // The previous implementation clobbered resource_labels.
1998
1999 // However, if we want to support overrides + embedded, we should merge.
2000 // But `resource_labels` was treated as "overrides" in the old code?
2001 // No, `resource_labels` was the container for loaded labels.
2002
2003 // If I look at `LoadFromYazeFormat`:
2004 // It parses `[labels_type]` into `resource_labels`.
2005
2006 // If `use_embedded_labels` is true, `InitializeEmbeddedLabels` is called?
2007 // I need to check when `InitializeEmbeddedLabels` is called.
2008
2009 resource_labels = labels;
2010 use_embedded_labels = true;
2011
2012 LOG_DEBUG("Project", "Initialized embedded labels:");
2013 LOG_DEBUG("Project", " - %d room names", resource_labels["room"].size());
2014 LOG_DEBUG("Project", " - %d entrance names",
2015 resource_labels["entrance"].size());
2016 LOG_DEBUG("Project", " - %d sprite names",
2017 resource_labels["sprite"].size());
2018 LOG_DEBUG("Project", " - %d overlord names",
2019 resource_labels["overlord"].size());
2020 LOG_DEBUG("Project", " - %d item names", resource_labels["item"].size());
2021 LOG_DEBUG("Project", " - %d music names",
2022 resource_labels["music"].size());
2023 LOG_DEBUG("Project", " - %d graphics names",
2024 resource_labels["graphics"].size());
2025 LOG_DEBUG("Project", " - %d room effect names",
2026 resource_labels["room_effect"].size());
2027 LOG_DEBUG("Project", " - %d room tag names",
2028 resource_labels["room_tag"].size());
2029 LOG_DEBUG("Project", " - %d tile type names",
2030 resource_labels["tile_type"].size());
2031
2032 return absl::OkStatus();
2033 } catch (const std::exception& e) {
2034 return absl::InternalError(
2035 absl::StrCat("Failed to initialize embedded labels: ", e.what()));
2036 }
2037}
2038
2039std::string YazeProject::GetLabel(const std::string& resource_type, int id,
2040 const std::string& default_value) const {
2041 // First check if we have a custom label override
2042 auto type_it = resource_labels.find(resource_type);
2043 if (type_it != resource_labels.end()) {
2044 auto label_it = type_it->second.find(std::to_string(id));
2045 if (label_it != type_it->second.end()) {
2046 return label_it->second;
2047 }
2048 }
2049
2050 return default_value.empty() ? resource_type + "_" + std::to_string(id)
2051 : default_value;
2052}
2053
2054absl::Status YazeProject::ImportLabelsFromZScream(const std::string& filepath) {
2055#ifdef __EMSCRIPTEN__
2056 (void)filepath;
2057 return absl::UnimplementedError(
2058 "File-based label import is not supported in the web build");
2059#else
2060 std::ifstream file(filepath);
2061 if (!file.is_open()) {
2062 return absl::InvalidArgumentError(
2063 absl::StrFormat("Cannot open labels file: %s", filepath));
2064 }
2065
2066 std::stringstream buffer;
2067 buffer << file.rdbuf();
2068 file.close();
2069
2070 return ImportLabelsFromZScreamContent(buffer.str());
2071#endif
2072}
2073
2075 const std::string& content) {
2076 // Initialize the global provider with our labels
2077 auto& provider = zelda3::GetResourceLabels();
2078 provider.SetProjectLabels(&resource_labels);
2079 provider.SetPreferHMagicNames(workspace_settings.prefer_hmagic_names);
2080
2081 // Use the provider to parse ZScream format
2082 auto status = provider.ImportFromZScreamFormat(content);
2083 if (!status.ok()) {
2084 return status;
2085 }
2086
2087 LOG_DEBUG("Project", "Imported ZScream labels:");
2088 LOG_DEBUG("Project", " - %d sprite labels",
2089 resource_labels["sprite"].size());
2090 LOG_DEBUG("Project", " - %d room labels", resource_labels["room"].size());
2091 LOG_DEBUG("Project", " - %d item labels", resource_labels["item"].size());
2092 LOG_DEBUG("Project", " - %d room tag labels",
2093 resource_labels["room_tag"].size());
2094
2095 return absl::OkStatus();
2096}
2097
2099 auto& provider = zelda3::GetResourceLabels();
2100 provider.SetProjectLabels(&resource_labels);
2101 provider.SetPreferHMagicNames(workspace_settings.prefer_hmagic_names);
2102 provider.SetHackManifest(hack_manifest.loaded() ? &hack_manifest : nullptr);
2103
2104 LOG_DEBUG("Project", "Initialized ResourceLabelProvider with project labels");
2105 LOG_DEBUG("Project", " - prefer_hmagic_names: %s",
2106 workspace_settings.prefer_hmagic_names ? "true" : "false");
2107 LOG_DEBUG("Project", " - hack_manifest: %s",
2108 hack_manifest.loaded() ? "loaded" : "not loaded");
2109}
2110
2111// ============================================================================
2112// JSON Format Support (Optional)
2113// ============================================================================
2114
2115#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
2116
2117absl::Status YazeProject::LoadFromJsonFormat(const std::string& project_path) {
2118#ifdef __EMSCRIPTEN__
2119 return absl::UnimplementedError(
2120 "JSON project format loading is not supported in the web build");
2121#endif
2122 std::ifstream file(project_path);
2123 if (!file.is_open()) {
2124 return absl::InvalidArgumentError(
2125 absl::StrFormat("Cannot open JSON project file: %s", project_path));
2126 }
2127
2128 try {
2129 json j;
2130 file >> j;
2131
2132 // Parse project metadata
2133 if (j.contains("yaze_project")) {
2134 auto& proj = j["yaze_project"];
2135
2136 if (proj.contains("name"))
2137 name = proj["name"].get<std::string>();
2138 if (proj.contains("description"))
2139 metadata.description = proj["description"].get<std::string>();
2140 if (proj.contains("author"))
2141 metadata.author = proj["author"].get<std::string>();
2142 if (proj.contains("version"))
2143 metadata.version = proj["version"].get<std::string>();
2144 if (proj.contains("created"))
2145 metadata.created_date = proj["created"].get<std::string>();
2146 if (proj.contains("modified"))
2147 metadata.last_modified = proj["modified"].get<std::string>();
2148 if (proj.contains("created_by"))
2149 metadata.created_by = proj["created_by"].get<std::string>();
2150
2151 // Files
2152 if (proj.contains("rom_filename"))
2153 rom_filename = proj["rom_filename"].get<std::string>();
2154 if (proj.contains("rom_backup_folder"))
2155 rom_backup_folder = proj["rom_backup_folder"].get<std::string>();
2156 if (proj.contains("code_folder"))
2157 code_folder = proj["code_folder"].get<std::string>();
2158 if (proj.contains("assets_folder"))
2159 assets_folder = proj["assets_folder"].get<std::string>();
2160 if (proj.contains("patches_folder"))
2161 patches_folder = proj["patches_folder"].get<std::string>();
2162 if (proj.contains("labels_filename"))
2163 labels_filename = proj["labels_filename"].get<std::string>();
2164 if (proj.contains("symbols_filename"))
2165 symbols_filename = proj["symbols_filename"].get<std::string>();
2166 if (proj.contains("hack_manifest_file"))
2167 hack_manifest_file = proj["hack_manifest_file"].get<std::string>();
2168
2169 if (proj.contains("rom") && proj["rom"].is_object()) {
2170 auto& rom = proj["rom"];
2171 if (rom.contains("role"))
2172 rom_metadata.role = ParseRomRole(rom["role"].get<std::string>());
2173 if (rom.contains("expected_hash"))
2174 rom_metadata.expected_hash = rom["expected_hash"].get<std::string>();
2175 if (rom.contains("write_policy"))
2177 ParseRomWritePolicy(rom["write_policy"].get<std::string>());
2178 }
2179
2180 // Embedded labels flag
2181 if (proj.contains("use_embedded_labels")) {
2182 use_embedded_labels = proj["use_embedded_labels"].get<bool>();
2183 }
2184
2185 // Feature flags
2186 if (proj.contains("feature_flags")) {
2187 auto& flags = proj["feature_flags"];
2188 // REMOVED: kLogInstructions (deprecated - DisassemblyViewer always
2189 // active)
2190 if (flags.contains("kSaveDungeonMaps"))
2192 flags["kSaveDungeonMaps"].get<bool>();
2193 if (flags.contains("kSaveOverworldMaps"))
2195 flags["kSaveOverworldMaps"].get<bool>();
2196 if (flags.contains("kSaveOverworldEntrances"))
2198 flags["kSaveOverworldEntrances"].get<bool>();
2199 if (flags.contains("kSaveOverworldExits"))
2201 flags["kSaveOverworldExits"].get<bool>();
2202 if (flags.contains("kSaveOverworldItems"))
2204 flags["kSaveOverworldItems"].get<bool>();
2205 if (flags.contains("kSaveOverworldProperties"))
2207 flags["kSaveOverworldProperties"].get<bool>();
2208 if (flags.contains("kSaveDungeonObjects"))
2210 flags["kSaveDungeonObjects"].get<bool>();
2211 if (flags.contains("kSaveDungeonSprites"))
2213 flags["kSaveDungeonSprites"].get<bool>();
2214 if (flags.contains("kSaveDungeonRoomHeaders"))
2216 flags["kSaveDungeonRoomHeaders"].get<bool>();
2217 if (flags.contains("kSaveDungeonTorches"))
2219 flags["kSaveDungeonTorches"].get<bool>();
2220 if (flags.contains("kSaveDungeonPits"))
2222 flags["kSaveDungeonPits"].get<bool>();
2223 if (flags.contains("kSaveDungeonBlocks"))
2225 flags["kSaveDungeonBlocks"].get<bool>();
2226 if (flags.contains("kSaveDungeonCollision"))
2228 flags["kSaveDungeonCollision"].get<bool>();
2229 if (flags.contains("kSaveDungeonWaterFillZones"))
2231 flags["kSaveDungeonWaterFillZones"].get<bool>();
2232 if (flags.contains("kSaveDungeonChests"))
2234 flags["kSaveDungeonChests"].get<bool>();
2235 if (flags.contains("kSaveDungeonPotItems"))
2237 flags["kSaveDungeonPotItems"].get<bool>();
2238 if (flags.contains("kSaveDungeonPalettes"))
2240 flags["kSaveDungeonPalettes"].get<bool>();
2241 if (flags.contains("kSaveGraphicsSheet"))
2243 flags["kSaveGraphicsSheet"].get<bool>();
2244 if (flags.contains("kSaveAllPalettes"))
2246 flags["kSaveAllPalettes"].get<bool>();
2247 if (flags.contains("kSaveGfxGroups"))
2248 feature_flags.kSaveGfxGroups = flags["kSaveGfxGroups"].get<bool>();
2249 if (flags.contains("kSaveMessages"))
2250 feature_flags.kSaveMessages = flags["kSaveMessages"].get<bool>();
2251 }
2252
2253 // Workspace settings
2254 if (proj.contains("workspace_settings")) {
2255 auto& ws = proj["workspace_settings"];
2256 if (ws.contains("auto_save_enabled"))
2258 ws["auto_save_enabled"].get<bool>();
2259 if (ws.contains("auto_save_interval"))
2261 ws["auto_save_interval"].get<float>();
2262 if (ws.contains("backup_on_save"))
2263 workspace_settings.backup_on_save = ws["backup_on_save"].get<bool>();
2264 if (ws.contains("backup_retention_count"))
2266 ws["backup_retention_count"].get<int>();
2267 if (ws.contains("backup_keep_daily"))
2269 ws["backup_keep_daily"].get<bool>();
2270 if (ws.contains("backup_keep_daily_days"))
2272 ws["backup_keep_daily_days"].get<int>();
2273 }
2274
2275 if (proj.contains("rom_addresses") && proj["rom_addresses"].is_object()) {
2277 for (auto it = proj["rom_addresses"].begin();
2278 it != proj["rom_addresses"].end(); ++it) {
2279 if (it.value().is_number_unsigned()) {
2281 it.value().get<uint32_t>();
2282 } else if (it.value().is_string()) {
2283 auto parsed = ParseHexUint32(it.value().get<std::string>());
2284 if (parsed.has_value()) {
2285 rom_address_overrides.addresses[it.key()] = *parsed;
2286 }
2287 }
2288 }
2289 }
2290
2291 if (proj.contains("custom_objects") &&
2292 proj["custom_objects"].is_object()) {
2293 custom_object_files.clear();
2294 for (auto it = proj["custom_objects"].begin();
2295 it != proj["custom_objects"].end(); ++it) {
2296 if (!it.value().is_array())
2297 continue;
2298 auto parsed = ParseHexUint32(it.key());
2299 if (!parsed.has_value()) {
2300 continue;
2301 }
2302 std::vector<std::string> files;
2303 for (const auto& entry : it.value()) {
2304 if (entry.is_string()) {
2305 files.push_back(entry.get<std::string>());
2306 }
2307 }
2308 if (!files.empty()) {
2309 custom_object_files[static_cast<int>(*parsed)] = std::move(files);
2310 }
2311 }
2312 }
2313
2314 if (proj.contains("agent_settings") &&
2315 proj["agent_settings"].is_object()) {
2316 auto& agent = proj["agent_settings"];
2318 agent.value("ai_provider", agent_settings.ai_provider);
2320 agent.value("ai_model", agent_settings.ai_model);
2322 agent.value("ollama_host", agent_settings.ollama_host);
2324 agent.value("gemini_api_key", agent_settings.gemini_api_key);
2326 agent.value("use_custom_prompt", agent_settings.use_custom_prompt);
2328 "custom_system_prompt", agent_settings.custom_system_prompt);
2330 agent.value("show_reasoning", agent_settings.show_reasoning);
2331 agent_settings.verbose = agent.value("verbose", agent_settings.verbose);
2332 agent_settings.max_tool_iterations = agent.value(
2333 "max_tool_iterations", agent_settings.max_tool_iterations);
2334 agent_settings.max_retry_attempts = agent.value(
2335 "max_retry_attempts", agent_settings.max_retry_attempts);
2337 agent.value("temperature", agent_settings.temperature);
2338 agent_settings.top_p = agent.value("top_p", agent_settings.top_p);
2340 agent.value("max_output_tokens", agent_settings.max_output_tokens);
2342 agent.value("stream_responses", agent_settings.stream_responses);
2343 if (agent.contains("favorite_models") &&
2344 agent["favorite_models"].is_array()) {
2346 for (const auto& model : agent["favorite_models"]) {
2347 if (model.is_string())
2349 model.get<std::string>());
2350 }
2351 }
2352 if (agent.contains("model_chain") && agent["model_chain"].is_array()) {
2354 for (const auto& model : agent["model_chain"]) {
2355 if (model.is_string())
2356 agent_settings.model_chain.push_back(model.get<std::string>());
2357 }
2358 }
2360 agent.value("chain_mode", agent_settings.chain_mode);
2362 "enable_tool_resources", agent_settings.enable_tool_resources);
2363 agent_settings.enable_tool_dungeon = agent.value(
2364 "enable_tool_dungeon", agent_settings.enable_tool_dungeon);
2366 "enable_tool_overworld", agent_settings.enable_tool_overworld);
2368 "enable_tool_messages", agent_settings.enable_tool_messages);
2370 "enable_tool_dialogue", agent_settings.enable_tool_dialogue);
2372 agent.value("enable_tool_gui", agent_settings.enable_tool_gui);
2374 agent.value("enable_tool_music", agent_settings.enable_tool_music);
2375 agent_settings.enable_tool_sprite = agent.value(
2376 "enable_tool_sprite", agent_settings.enable_tool_sprite);
2378 "enable_tool_emulator", agent_settings.enable_tool_emulator);
2380 agent.value("enable_tool_memory_inspector",
2383 "builder_blueprint_path", agent_settings.builder_blueprint_path);
2384 }
2385
2386 // Build settings
2387 if (proj.contains("build_script"))
2388 build_script = proj["build_script"].get<std::string>();
2389 if (proj.contains("output_folder"))
2390 output_folder = proj["output_folder"].get<std::string>();
2391 if (proj.contains("git_repository"))
2392 git_repository = proj["git_repository"].get<std::string>();
2393 if (proj.contains("track_changes"))
2394 track_changes = proj["track_changes"].get<bool>();
2395 }
2396
2397 return absl::OkStatus();
2398 } catch (const json::exception& e) {
2399 return absl::InvalidArgumentError(
2400 absl::StrFormat("JSON parse error: %s", e.what()));
2401 }
2402}
2403
2404absl::Status YazeProject::SaveToJsonFormat() {
2405#ifdef __EMSCRIPTEN__
2406 return absl::UnimplementedError(
2407 "JSON project format saving is not supported in the web build");
2408#endif
2409 json j;
2410 auto& proj = j["yaze_project"];
2411
2412 // Metadata
2413 proj["version"] = metadata.version;
2414 proj["name"] = name;
2415 proj["author"] = metadata.author;
2416 proj["created_by"] = metadata.created_by;
2417 proj["description"] = metadata.description;
2418 proj["created"] = metadata.created_date;
2419 proj["modified"] = metadata.last_modified;
2420
2421 // Files
2422 proj["rom_filename"] = rom_filename;
2423 proj["rom_backup_folder"] = rom_backup_folder;
2424 proj["code_folder"] = code_folder;
2425 proj["assets_folder"] = assets_folder;
2426 proj["patches_folder"] = patches_folder;
2427 proj["labels_filename"] = labels_filename;
2428 proj["symbols_filename"] = symbols_filename;
2429 proj["hack_manifest_file"] = hack_manifest_file;
2430 proj["output_folder"] = output_folder;
2431
2432 proj["rom"]["role"] = RomRoleToString(rom_metadata.role);
2433 proj["rom"]["expected_hash"] = rom_metadata.expected_hash;
2434 proj["rom"]["write_policy"] =
2436
2437 // Embedded labels
2438 proj["use_embedded_labels"] = use_embedded_labels;
2439
2440 // Feature flags
2441 // REMOVED: kLogInstructions (deprecated)
2442 proj["feature_flags"]["kSaveDungeonMaps"] = feature_flags.kSaveDungeonMaps;
2443 proj["feature_flags"]["kSaveOverworldMaps"] =
2445 proj["feature_flags"]["kSaveOverworldEntrances"] =
2447 proj["feature_flags"]["kSaveOverworldExits"] =
2449 proj["feature_flags"]["kSaveOverworldItems"] =
2451 proj["feature_flags"]["kSaveOverworldProperties"] =
2453 proj["feature_flags"]["kSaveDungeonObjects"] =
2455 proj["feature_flags"]["kSaveDungeonSprites"] =
2457 proj["feature_flags"]["kSaveDungeonRoomHeaders"] =
2459 proj["feature_flags"]["kSaveDungeonTorches"] =
2461 proj["feature_flags"]["kSaveDungeonPits"] = feature_flags.dungeon.kSavePits;
2462 proj["feature_flags"]["kSaveDungeonBlocks"] =
2464 proj["feature_flags"]["kSaveDungeonCollision"] =
2466 proj["feature_flags"]["kSaveDungeonWaterFillZones"] =
2468 proj["feature_flags"]["kSaveDungeonChests"] =
2470 proj["feature_flags"]["kSaveDungeonPotItems"] =
2472 proj["feature_flags"]["kSaveDungeonPalettes"] =
2474 proj["feature_flags"]["kSaveGraphicsSheet"] =
2476 proj["feature_flags"]["kSaveAllPalettes"] = feature_flags.kSaveAllPalettes;
2477 proj["feature_flags"]["kSaveGfxGroups"] = feature_flags.kSaveGfxGroups;
2478 proj["feature_flags"]["kSaveMessages"] = feature_flags.kSaveMessages;
2479
2480 // Workspace settings
2481 proj["workspace_settings"]["auto_save_enabled"] =
2483 proj["workspace_settings"]["auto_save_interval"] =
2485 proj["workspace_settings"]["backup_on_save"] =
2487 proj["workspace_settings"]["backup_retention_count"] =
2489 proj["workspace_settings"]["backup_keep_daily"] =
2491 proj["workspace_settings"]["backup_keep_daily_days"] =
2493
2494 auto& agent = proj["agent_settings"];
2495 agent["ai_provider"] = agent_settings.ai_provider;
2496 agent["ai_model"] = agent_settings.ai_model;
2497 agent["ollama_host"] = agent_settings.ollama_host;
2498 agent["gemini_api_key"] = agent_settings.gemini_api_key;
2499 agent["use_custom_prompt"] = agent_settings.use_custom_prompt;
2500 agent["custom_system_prompt"] = agent_settings.custom_system_prompt;
2501 agent["show_reasoning"] = agent_settings.show_reasoning;
2502 agent["verbose"] = agent_settings.verbose;
2503 agent["max_tool_iterations"] = agent_settings.max_tool_iterations;
2504 agent["max_retry_attempts"] = agent_settings.max_retry_attempts;
2505 agent["temperature"] = agent_settings.temperature;
2506 agent["top_p"] = agent_settings.top_p;
2507 agent["max_output_tokens"] = agent_settings.max_output_tokens;
2508 agent["stream_responses"] = agent_settings.stream_responses;
2509 agent["favorite_models"] = agent_settings.favorite_models;
2510 agent["model_chain"] = agent_settings.model_chain;
2511 agent["chain_mode"] = agent_settings.chain_mode;
2512 agent["enable_tool_resources"] = agent_settings.enable_tool_resources;
2513 agent["enable_tool_dungeon"] = agent_settings.enable_tool_dungeon;
2514 agent["enable_tool_overworld"] = agent_settings.enable_tool_overworld;
2515 agent["enable_tool_messages"] = agent_settings.enable_tool_messages;
2516 agent["enable_tool_dialogue"] = agent_settings.enable_tool_dialogue;
2517 agent["enable_tool_gui"] = agent_settings.enable_tool_gui;
2518 agent["enable_tool_music"] = agent_settings.enable_tool_music;
2519 agent["enable_tool_sprite"] = agent_settings.enable_tool_sprite;
2520 agent["enable_tool_emulator"] = agent_settings.enable_tool_emulator;
2521 agent["enable_tool_memory_inspector"] =
2523 agent["builder_blueprint_path"] = agent_settings.builder_blueprint_path;
2524
2525 if (!rom_address_overrides.addresses.empty()) {
2526 auto& addrs = proj["rom_addresses"];
2527 for (const auto& [key, value] : rom_address_overrides.addresses) {
2528 addrs[key] = value;
2529 }
2530 }
2531
2532 if (!custom_object_files.empty()) {
2533 auto& objs = proj["custom_objects"];
2534 for (const auto& [object_id, files] : custom_object_files) {
2535 objs[absl::StrFormat("0x%X", object_id)] = files;
2536 }
2537 }
2538
2539 // Build settings
2540 proj["build_script"] = build_script;
2541 proj["git_repository"] = git_repository;
2542 proj["track_changes"] = track_changes;
2543
2544 // Write to file
2545 std::ofstream file(filepath);
2546 if (!file.is_open()) {
2547 return absl::InvalidArgumentError(
2548 absl::StrFormat("Cannot write JSON project file: %s", filepath));
2549 }
2550
2551 file << j.dump(2); // Pretty print with 2-space indent
2552 return absl::OkStatus();
2553}
2554
2555#endif // YAZE_ENABLE_JSON_PROJECT_FORMAT
2556
2557// RecentFilesManager implementation
2559 auto config_dir = util::PlatformPaths::GetConfigDirectory();
2560 if (!config_dir.ok()) {
2561 return ""; // Or handle error appropriately
2562 }
2563 return (*config_dir / kRecentFilesFilename).string();
2564}
2565
2567#ifdef __EMSCRIPTEN__
2568 auto status = platform::WasmStorage::SaveProject(
2569 kRecentFilesFilename, absl::StrJoin(recent_files_, "\n"));
2570 if (!status.ok()) {
2571 LOG_WARN("RecentFilesManager", "Could not persist recent files: %s",
2572 status.ToString().c_str());
2573 }
2574 return;
2575#endif
2576 // Ensure config directory exists
2577 auto config_dir_status = util::PlatformPaths::GetConfigDirectory();
2578 if (!config_dir_status.ok()) {
2579 LOG_ERROR("Project", "Failed to get or create config directory: %s",
2580 config_dir_status.status().ToString().c_str());
2581 return;
2582 }
2583
2584 std::string filepath = GetFilePath();
2585 std::ofstream file(filepath);
2586 if (!file.is_open()) {
2587 LOG_WARN("RecentFilesManager", "Could not save recent files to %s",
2588 filepath.c_str());
2589 return;
2590 }
2591
2592 for (const auto& file_path : recent_files_) {
2593 file << file_path << std::endl;
2594 }
2595}
2596
2598#ifdef __EMSCRIPTEN__
2599 auto storage_or = platform::WasmStorage::LoadProject(kRecentFilesFilename);
2600 if (!storage_or.ok()) {
2601 return;
2602 }
2603 recent_files_.clear();
2604 std::istringstream stream(storage_or.value());
2605 std::string line;
2606 while (std::getline(stream, line)) {
2607 if (!line.empty()) {
2608 recent_files_.push_back(line);
2609 }
2610 }
2611#else
2612 std::string filepath = GetFilePath();
2613 std::ifstream file(filepath);
2614 if (!file.is_open()) {
2615 // File doesn't exist yet, which is fine
2616 return;
2617 }
2618
2619 recent_files_.clear();
2620 std::string line;
2621 while (std::getline(file, line)) {
2622 if (!line.empty()) {
2623 recent_files_.push_back(line);
2624 }
2625 }
2626#endif
2627}
2628
2629} // namespace project
2630} // namespace yaze
void Clear()
Clear any loaded manifest state.
const ProjectRegistry & project_registry() const
bool HasProjectRegistry() const
absl::Status LoadProjectRegistry(const std::string &code_folder)
Load project registry data from the code folder.
absl::Status LoadFromFile(const std::string &filepath)
Load manifest from a JSON file path.
bool loaded() const
Check if the manifest has been loaded.
static std::vector< std::string > FindProjectsInDirectory(const std::string &directory)
Definition project.cc:1748
static absl::Status ValidateProjectStructure(const YazeProject &project)
Definition project.cc:1811
static absl::StatusOr< YazeProject > CreateFromTemplate(const std::string &template_name, const std::string &project_name, const std::string &base_path)
Definition project.cc:1706
static std::vector< std::string > GetRecommendedFixesForProject(const YazeProject &project)
Definition project.cc:1816
static std::vector< ProjectTemplate > GetProjectTemplates()
Definition project.cc:1559
static absl::Status BackupProject(const YazeProject &project)
Definition project.cc:1778
std::string GetFilePath() const
Definition project.cc:2558
std::vector< std::string > recent_files_
Definition project.h:422
static absl::StatusOr< std::filesystem::path > GetConfigDirectory()
Get the user-specific configuration directory for YAZE.
void SetHackManifest(const core::HackManifest *manifest)
Set the hack manifest reference for ASM-defined labels.
#define YAZE_VERSION_STRING
#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:67
std::vector< uint16_t > ParseHexUintList(const std::string &value)
Definition project.cc:92
std::string ToLowerCopy(std::string value)
Definition project.cc:38
bool ParseBool(const std::string &value)
Definition project.cc:63
std::optional< uint32_t > ParseHexUint32(const std::string &value)
Definition project.cc:120
std::string FormatHexUint32(uint32_t value)
Definition project.cc:146
std::string SanitizeStorageKey(absl::string_view input)
Definition project.cc:150
std::string FormatHexUintList(const std::vector< uint16_t > &values)
Definition project.cc:140
std::vector< std::string > ParseStringList(const std::string &value)
Definition project.cc:75
std::string RomRoleToString(RomRole role)
Definition project.cc:164
RomRole ParseRomRole(absl::string_view value)
Definition project.cc:178
const std::string kRecentFilesFilename
Definition project.h:369
RomWritePolicy ParseRomWritePolicy(absl::string_view value)
Definition project.cc:204
std::string RomWritePolicyToString(RomWritePolicy policy)
Definition project.cc:192
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
struct yaze::core::FeatureFlags::Flags::Dungeon dungeon
struct yaze::core::FeatureFlags::Flags::Overworld overworld
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > all_resource_labels
std::unordered_map< std::string, uint32_t > addresses
std::vector< uint16_t > track_object_ids
Definition project.h:98
std::vector< uint16_t > minecart_sprite_ids
Definition project.h:99
std::vector< uint16_t > track_stop_tiles
Definition project.h:94
std::vector< uint16_t > track_tiles
Definition project.h:93
std::vector< uint16_t > track_switch_tiles
Definition project.h:95
std::vector< std::string > tags
Definition project.h:41
std::string CreateOrGetLabel(const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:1966
std::string GetLabel(const std::string &type, const std::string &key)
Definition project.cc:1953
void EditLabel(const std::string &type, const std::string &key, const std::string &newValue)
Definition project.cc:1935
bool LoadLabels(const std::string &filename)
Definition project.cc:1851
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:1941
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
Definition project.h:365
std::string expected_hash
Definition project.h:107
RomWritePolicy write_policy
Definition project.h:108
std::map< std::string, std::string > custom_keybindings
Definition project.h:82
std::vector< std::string > saved_layouts
Definition project.h:63
std::map< std::string, bool > editor_visibility
Definition project.h:84
std::vector< std::string > recent_files
Definition project.h:83
std::vector< std::string > favorite_models
Definition project.h:194
std::vector< std::string > model_chain
Definition project.h:195
Modern project structure with comprehensive settings consolidation.
Definition project.h:120
std::string rom_backup_folder
Definition project.h:129
std::unordered_map< int, std::vector< std::string > > custom_object_files
Definition project.h:145
absl::Status ResetToDefaults()
Definition project.cc:1165
std::string custom_objects_folder
Definition project.h:140
absl::Status RepairProject()
Definition project.cc:1226
std::string MakeStorageKey(absl::string_view suffix) const
Definition project.cc:452
static std::string ResolveBundleRoot(const std::string &path)
Definition project.cc:269
struct yaze::project::YazeProject::MusicPersistence music_persistence
absl::StatusOr< std::string > SerializeToString() const
Definition project.cc:468
std::string zscream_project_file
Definition project.h:211
absl::Status ExportForZScream(const std::string &target_path)
Definition project.cc:1132
ProjectMetadata metadata
Definition project.h:122
absl::Status ImportZScreamProject(const std::string &zscream_project_path)
Definition project.cc:1109
absl::Status SaveAllSettings()
Definition project.cc:1160
absl::Status ImportLabelsFromZScreamContent(const std::string &content)
Import labels from ZScream format content directly.
Definition project.cc:2074
std::string git_repository
Definition project.h:171
core::HackManifest hack_manifest
Definition project.h:160
void InitializeResourceLabelProvider()
Initialize the global ResourceLabelProvider with this project's labels.
Definition project.cc:2098
absl::Status ParseFromString(const std::string &content)
Definition project.cc:763
std::vector< std::string > additional_roms
Definition project.h:130
std::string patches_folder
Definition project.h:136
absl::Status LoadFromYazeFormat(const std::string &project_path)
Definition project.cc:1050
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
Definition project.h:153
std::string GenerateProjectId() const
Definition project.cc:1550
absl::Status Create(const std::string &project_name, const std::string &base_path)
Definition project.cc:216
std::string assets_folder
Definition project.h:135
absl::Status SaveToYazeFormat()
Definition project.cc:1071
absl::Status LoadAllSettings()
Definition project.cc:1154
std::string labels_filename
Definition project.h:137
std::vector< std::string > asm_sources
Definition project.h:168
std::string hack_manifest_file
Definition project.h:142
std::string GetDisplayName() const
Definition project.cc:1260
std::vector< std::string > GetMissingFiles() const
Definition project.cc:1205
WorkspaceSettings workspace_settings
Definition project.h:149
std::string output_folder
Definition project.h:164
std::string asm_entry_point
Definition project.h:167
std::string GetRelativePath(const std::string &absolute_path) const
Definition project.cc:1267
absl::Status InitializeEmbeddedLabels(const std::unordered_map< std::string, std::unordered_map< std::string, std::string > > &labels)
Definition project.cc:1981
absl::Status SaveAs(const std::string &new_path)
Definition project.cc:440
struct yaze::project::YazeProject::AgentSettings agent_settings
DungeonOverlaySettings dungeon_overlay
Definition project.h:150
absl::Status ImportFromZScreamFormat(const std::string &project_path)
Definition project.cc:1344
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:1287
std::string GetLabel(const std::string &resource_type, int id, const std::string &default_value="") const
Definition project.cc:2039
absl::Status Open(const std::string &project_path)
Definition project.cc:295
absl::Status ImportLabelsFromZScream(const std::string &filepath)
Import labels from a ZScream DefaultNames.txt file.
Definition project.cc:2054
std::string last_build_hash
Definition project.h:173
std::map< std::string, std::string > zscream_mappings
Definition project.h:212
absl::Status Validate() const
Definition project.cc:1170
core::FeatureFlags::Flags feature_flags
Definition project.h:148
std::vector< std::string > build_configurations
Definition project.h:165
core::RomAddressOverrides rom_address_overrides
Definition project.h:151
std::string symbols_filename
Definition project.h:138