10#include "absl/strings/str_format.h"
11#include "absl/strings/str_join.h"
12#include "absl/strings/str_split.h"
14#include "imgui/imgui.h"
20#include "yaze_config.h"
38std::pair<std::string, std::string> ParseKeyValue(
const std::string& line) {
39 size_t eq_pos = line.find(
'=');
40 if (eq_pos == std::string::npos)
43 std::string key = line.substr(0, eq_pos);
44 std::string value = line.substr(eq_pos + 1);
47 key.erase(0, key.find_first_not_of(
" \t"));
48 key.erase(key.find_last_not_of(
" \t") + 1);
49 value.erase(0, value.find_first_not_of(
" \t"));
50 value.erase(value.find_last_not_of(
" \t") + 1);
56 return value ==
"true" || value ==
"1" || value ==
"yes";
61 return std::stof(value);
68 std::vector<std::string> result;
72 std::vector<std::string> parts = absl::StrSplit(value,
',');
73 for (
const auto& part : parts) {
74 std::string trimmed = std::string(part);
75 trimmed.erase(0, trimmed.find_first_not_of(
" \t"));
76 trimmed.erase(trimmed.find_last_not_of(
" \t") + 1);
77 if (!trimmed.empty()) {
78 result.push_back(trimmed);
85 std::string key(input);
87 if (!std::isalnum(
static_cast<unsigned char>(c))) {
100 const std::string& base_path) {
102 filepath = base_path +
"/" + project_name +
".yaze";
105 auto now = std::chrono::system_clock::now();
106 auto time_t = std::chrono::system_clock::to_time_t(now);
107 std::stringstream ss;
108 ss << std::put_time(std::localtime(&time_t),
"%Y-%m-%d %H:%M:%S");
119#ifndef __EMSCRIPTEN__
121 std::filesystem::path project_dir(base_path +
"/" + project_name);
122 std::filesystem::create_directories(project_dir);
123 std::filesystem::create_directories(project_dir /
"code");
124 std::filesystem::create_directories(project_dir /
"assets");
125 std::filesystem::create_directories(project_dir /
"patches");
126 std::filesystem::create_directories(project_dir /
"backups");
127 std::filesystem::create_directories(project_dir /
"output");
157 auto storage_or = platform::WasmStorage::LoadProject(storage_key);
158 if (storage_or.ok()) {
164 if (project_path.ends_with(
".yaze")) {
168 std::ifstream file(project_path);
169 if (file.is_open()) {
170 std::stringstream buffer;
171 buffer << file.rdbuf();
172 std::string content = buffer.str();
174#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
175 if (!content.empty() && content.front() ==
'{') {
176 LOG_DEBUG(
"Project",
"Detected JSON format project file");
177 return LoadFromJsonFormat(project_path);
184 return absl::InvalidArgumentError(
185 absl::StrFormat(
"Cannot open project file: %s", project_path));
186 }
else if (project_path.ends_with(
".zsproj")) {
191 return absl::InvalidArgumentError(
"Unsupported project file format");
199 std::string old_filepath =
filepath;
202 auto status =
Save();
214 }
else if (!
name.empty()) {
217 base = std::filesystem::path(
filepath).stem().string();
219 base = SanitizeStorageKey(base);
220 if (suffix.empty()) {
223 return absl::StrFormat(
"%s_%s", base, suffix);
227 std::ostringstream file;
230 file <<
"# yaze Project File\n";
231 file <<
"# Format Version: 2.0\n";
236 file <<
"[project]\n";
237 file <<
"name=" <<
name <<
"\n";
247 file <<
"tags=" << absl::StrJoin(
metadata.
tags,
",") <<
"\n\n";
260 file <<
"additional_roms=" << absl::StrJoin(
additional_roms,
",") <<
"\n\n";
263 file <<
"[feature_flags]\n";
264 file <<
"load_custom_overworld="
267 file <<
"apply_zs_custom_overworld_asm="
271 file <<
"save_dungeon_maps="
273 file <<
"save_graphics_sheet="
275 file <<
"enable_custom_objects="
279 file <<
"[workspace]\n";
284 file <<
"autosave_enabled="
286 file <<
"autosave_interval_secs="
288 file <<
"backup_on_save="
292 file <<
"show_collision="
294 file <<
"prefer_hmagic_names="
298 file <<
"saved_layouts="
300 file <<
"recent_files="
304 file <<
"[agent_settings]\n";
309 file <<
"custom_system_prompt="
311 file <<
"use_custom_prompt="
313 file <<
"show_reasoning="
321 file <<
"stream_responses="
323 file <<
"favorite_models="
325 file <<
"model_chain="
328 file <<
"enable_tool_resources="
330 file <<
"enable_tool_dungeon="
332 file <<
"enable_tool_overworld="
334 file <<
"enable_tool_messages="
336 file <<
"enable_tool_dialogue="
338 file <<
"enable_tool_gui="
340 file <<
"enable_tool_music="
342 file <<
"enable_tool_sprite="
344 file <<
"enable_tool_emulator="
351 file <<
"[keybindings]\n";
353 file << key <<
"=" << value <<
"\n";
360 file <<
"[editor_visibility]\n";
362 file << key <<
"=" << (value ?
"true" :
"false") <<
"\n";
369 if (!labels.empty()) {
370 file <<
"[labels_" << type <<
"]\n";
371 for (
const auto& [key, value] : labels) {
372 file << key <<
"=" << value <<
"\n";
383 file <<
"track_changes=" << (
track_changes ?
"true" :
"false") <<
"\n";
384 file <<
"build_configurations="
388 file <<
"asm_sources=" << absl::StrJoin(
asm_sources,
",") <<
"\n";
394 file <<
"persist_custom_music="
401 file <<
"[zscream_compatibility]\n";
404 file << key <<
"=" << value <<
"\n";
409 file <<
"# End of YAZE Project File\n";
414 std::istringstream stream(content);
416 std::string current_section;
418 while (std::getline(stream, line)) {
419 if (line.empty() || line[0] ==
'#')
422 if (line.front() ==
'[' && line.back() ==
']') {
423 current_section = line.substr(1, line.length() - 2);
427 auto [key, value] = ParseKeyValue(line);
431 if (current_section ==
"project") {
434 else if (key ==
"description")
436 else if (key ==
"author")
438 else if (key ==
"license")
440 else if (key ==
"version")
442 else if (key ==
"created_date")
444 else if (key ==
"last_modified")
446 else if (key ==
"yaze_version")
448 else if (key ==
"created_by")
450 else if (key ==
"tags")
452 else if (key ==
"project_id")
454 }
else if (current_section ==
"files") {
455 if (key ==
"rom_filename")
457 else if (key ==
"rom_backup_folder")
459 else if (key ==
"code_folder")
461 else if (key ==
"assets_folder")
463 else if (key ==
"patches_folder")
465 else if (key ==
"labels_filename")
467 else if (key ==
"symbols_filename")
469 else if (key ==
"output_folder")
471 else if (key ==
"custom_objects_folder")
473 else if (key ==
"additional_roms")
475 }
else if (current_section ==
"feature_flags") {
476 if (key ==
"load_custom_overworld")
478 else if (key ==
"apply_zs_custom_overworld_asm")
480 else if (key ==
"save_dungeon_maps")
482 else if (key ==
"save_graphics_sheet")
484 else if (key ==
"enable_custom_objects")
486 }
else if (current_section ==
"workspace") {
487 if (key ==
"font_global_scale")
489 else if (key ==
"dark_mode")
491 else if (key ==
"ui_theme")
493 else if (key ==
"autosave_enabled")
495 else if (key ==
"autosave_interval_secs")
497 else if (key ==
"backup_on_save")
499 else if (key ==
"show_grid")
501 else if (key ==
"show_collision")
503 else if (key ==
"prefer_hmagic_names")
505 else if (key ==
"last_layout_preset")
507 else if (key ==
"saved_layouts")
509 else if (key ==
"recent_files")
511 }
else if (current_section ==
"agent_settings") {
512 if (key ==
"ai_provider")
514 else if (key ==
"ai_model")
516 else if (key ==
"ollama_host")
518 else if (key ==
"gemini_api_key")
520 else if (key ==
"custom_system_prompt")
522 else if (key ==
"use_custom_prompt")
524 else if (key ==
"show_reasoning")
526 else if (key ==
"verbose")
528 else if (key ==
"max_tool_iterations")
530 else if (key ==
"max_retry_attempts")
532 else if (key ==
"temperature")
534 else if (key ==
"top_p")
536 else if (key ==
"max_output_tokens")
538 else if (key ==
"stream_responses")
540 else if (key ==
"favorite_models")
542 else if (key ==
"model_chain")
544 else if (key ==
"chain_mode")
546 else if (key ==
"enable_tool_resources")
548 else if (key ==
"enable_tool_dungeon")
550 else if (key ==
"enable_tool_overworld")
552 else if (key ==
"enable_tool_messages")
554 else if (key ==
"enable_tool_dialogue")
556 else if (key ==
"enable_tool_gui")
558 else if (key ==
"enable_tool_music")
560 else if (key ==
"enable_tool_sprite")
562 else if (key ==
"enable_tool_emulator")
564 else if (key ==
"builder_blueprint_path")
566 }
else if (current_section ==
"build") {
567 if (key ==
"build_script")
569 else if (key ==
"output_folder")
571 else if (key ==
"git_repository")
573 else if (key ==
"track_changes")
575 else if (key ==
"build_configurations")
577 else if (key ==
"build_target")
579 else if (key ==
"asm_entry_point")
581 else if (key ==
"asm_sources")
583 else if (key ==
"last_build_hash")
585 else if (key ==
"build_number")
587 }
else if (current_section.rfind(
"labels_", 0) == 0) {
588 std::string label_type = current_section.substr(7);
590 }
else if (current_section ==
"keybindings") {
592 }
else if (current_section ==
"editor_visibility") {
594 }
else if (current_section ==
"zscream_compatibility") {
595 if (key ==
"original_project_file")
599 }
else if (current_section ==
"music") {
600 if (key ==
"persist_custom_music")
602 else if (key ==
"storage_key")
604 else if (key ==
"last_saved_at")
619 return absl::OkStatus();
625 auto storage_or = platform::WasmStorage::LoadProject(storage_key);
626 if (storage_or.ok()) {
631 std::ifstream file(project_path);
632 if (!file.is_open()) {
633 return absl::InvalidArgumentError(
634 absl::StrFormat(
"Cannot open project file: %s", project_path));
637 std::stringstream buffer;
638 buffer << file.rdbuf();
645 auto now = std::chrono::system_clock::now();
646 auto time_t = std::chrono::system_clock::to_time_t(now);
647 std::stringstream ss;
648 ss << std::put_time(std::localtime(&time_t),
"%Y-%m-%d %H:%M:%S");
657 auto storage_status =
658 platform::WasmStorage::SaveProject(
MakeStorageKey(
"project"), serialized);
659 if (!storage_status.ok()) {
660 return storage_status;
665 if (!file.is_open()) {
666 return absl::InvalidArgumentError(
667 absl::StrFormat(
"Cannot create project file: %s",
filepath));
674 return absl::OkStatus();
678 const std::string& zscream_project_path) {
684 std::filesystem::path zs_path(zscream_project_path);
685 name = zs_path.stem().string() +
"_imported";
697 return absl::OkStatus();
702 std::ofstream file(target_path);
703 if (!file.is_open()) {
704 return absl::InvalidArgumentError(
705 absl::StrFormat(
"Cannot create ZScream project file: %s", target_path));
709 file <<
"# ZScream Compatible Project File\n";
711 file <<
"name=" <<
name <<
"\n";
719 return absl::OkStatus();
739 std::vector<std::string> errors;
742 errors.push_back(
"Project name is required");
744 errors.push_back(
"Project file path is required");
746 errors.push_back(
"ROM file is required");
748#ifndef __EMSCRIPTEN__
752 errors.push_back(
"ROM file does not exist: " +
rom_filename);
757 errors.push_back(
"Code folder does not exist: " +
code_folder);
766 if (!errors.empty()) {
767 return absl::InvalidArgumentError(absl::StrJoin(errors,
"; "));
770 return absl::OkStatus();
774 std::vector<std::string> missing;
776#ifndef __EMSCRIPTEN__
797 return absl::OkStatus();
804 for (
const auto& folder : folders) {
805 if (!folder.empty()) {
807 if (!std::filesystem::exists(abs_path)) {
808 std::filesystem::create_directories(abs_path);
816 if (!std::filesystem::exists(abs_labels)) {
817 std::ofstream labels_file(abs_labels);
818 labels_file <<
"# yaze Resource Labels\n";
819 labels_file <<
"# Format: [type] key=value\n\n";
824 return absl::OkStatus();
832 return name.empty() ?
"Untitled Project" :
name;
836 const std::string& absolute_path)
const {
837 if (absolute_path.empty() ||
filepath.empty())
838 return absolute_path;
840 std::filesystem::path project_dir =
841 std::filesystem::path(
filepath).parent_path();
842 std::filesystem::path abs_path(absolute_path);
845 std::filesystem::path relative =
846 std::filesystem::relative(abs_path, project_dir);
847 return relative.string();
849 return absolute_path;
854 const std::string& relative_path)
const {
855 if (relative_path.empty() ||
filepath.empty())
856 return relative_path;
858 std::filesystem::path project_dir =
859 std::filesystem::path(
filepath).parent_path();
860 std::filesystem::path abs_path = project_dir / relative_path;
862 return abs_path.string();
870 const std::string& project_path) {
874 std::filesystem::path zs_path(project_path);
875 name = zs_path.stem().string() +
"_imported";
880 return absl::OkStatus();
925 auto now = std::chrono::system_clock::now().time_since_epoch();
927 std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
928 return absl::StrFormat(
"yaze_project_%lld", timestamp);
932std::vector<ProjectManager::ProjectTemplate>
934 std::vector<ProjectTemplate> templates;
943 t.
name =
"Vanilla ROM Hack";
944 t.
description =
"Standard ROM editing without custom ASM. Limited to vanilla features.";
952 templates.push_back(t);
958 t.
name =
"ZSCustomOverworld v2";
959 t.
description =
"Basic overworld expansion: custom BG colors, main palettes, parent system.";
970 templates.push_back(t);
976 t.
name =
"ZSCustomOverworld v3 (Recommended)";
977 t.
description =
"Full overworld expansion: wide/tall areas, animated GFX, overlays, all features.";
991 templates.push_back(t);
997 t.
name =
"Randomizer Compatible";
998 t.
description =
"Compatible with ALttP Randomizer. Minimal custom features to avoid conflicts.";
1005 templates.push_back(t);
1015 t.
name =
"Dungeon Designer";
1016 t.
description =
"Focused on dungeon creation and modification.";
1022 templates.push_back(t);
1028 t.
name =
"Graphics Pack";
1029 t.
description =
"Project focused on graphics, sprites, and visual modifications.";
1037 templates.push_back(t);
1043 t.
name =
"Complete Overhaul";
1044 t.
description =
"Full-scale ROM hack with all features enabled.";
1058 templates.push_back(t);
1065 const std::string& template_name,
const std::string& project_name,
1066 const std::string& base_path) {
1068 auto status = project.
Create(project_name, base_path);
1074 if (template_name ==
"Full Overworld Mod") {
1078 project.
metadata.
tags = {
"overworld",
"maps",
"graphics"};
1079 }
else if (template_name ==
"Dungeon Designer") {
1083 project.
metadata.
tags = {
"dungeons",
"rooms",
"design"};
1084 }
else if (template_name ==
"Graphics Pack") {
1088 project.
metadata.
tags = {
"graphics",
"sprites",
"palettes"};
1089 }
else if (template_name ==
"Complete Overhaul") {
1095 project.
metadata.
tags = {
"complete",
"overhaul",
"full-mod"};
1098 status = project.
Save();
1107 const std::string& directory) {
1108#ifdef __EMSCRIPTEN__
1112 std::vector<std::string> projects;
1115 for (
const auto& entry : std::filesystem::directory_iterator(directory)) {
1116 if (entry.is_regular_file()) {
1117 std::string filename = entry.path().filename().string();
1118 if (filename.ends_with(
".yaze") || filename.ends_with(
".zsproj")) {
1119 projects.push_back(entry.path().string());
1123 }
catch (
const std::filesystem::filesystem_error& e) {
1132#ifdef __EMSCRIPTEN__
1134 return absl::UnimplementedError(
1135 "Project backups are not supported in the web build");
1138 return absl::InvalidArgumentError(
"Project has no file path");
1141 std::filesystem::path project_path(project.
filepath);
1142 std::filesystem::path backup_dir = project_path.parent_path() /
"backups";
1143 std::filesystem::create_directories(backup_dir);
1145 auto now = std::chrono::system_clock::now();
1146 auto time_t = std::chrono::system_clock::to_time_t(now);
1147 std::stringstream ss;
1148 ss << std::put_time(std::localtime(&time_t),
"%Y%m%d_%H%M%S");
1150 std::string backup_filename = project.
name +
"_backup_" + ss.str() +
".yaze";
1151 std::filesystem::path backup_path = backup_dir / backup_filename;
1154 std::filesystem::copy_file(project.
filepath, backup_path);
1155 }
catch (
const std::filesystem::filesystem_error& e) {
1156 return absl::InternalError(
1157 absl::StrFormat(
"Failed to backup project: %s", e.what()));
1160 return absl::OkStatus();
1171 std::vector<std::string> recommendations;
1174 recommendations.push_back(
"Add a ROM file to begin editing");
1178 recommendations.push_back(
"Set up a code folder for assembly patches");
1182 recommendations.push_back(
"Create a labels file for better organization");
1186 recommendations.push_back(
"Add a project description for documentation");
1190 recommendations.push_back(
1191 "Consider setting up version control for your project");
1195 if (!missing_files.empty()) {
1196 recommendations.push_back(
1197 "Some project files are missing - use Project > Repair to fix");
1200 return recommendations;
1206 std::ifstream file(filename);
1207 if (!file.is_open()) {
1214 std::string current_type =
"";
1216 while (std::getline(file, line)) {
1217 if (line.empty() || line[0] ==
'#')
1221 if (line[0] ==
'[' && line.back() ==
']') {
1222 current_type = line.substr(1, line.length() - 2);
1227 size_t eq_pos = line.find(
'=');
1228 if (eq_pos != std::string::npos && !current_type.empty()) {
1229 std::string key = line.substr(0, eq_pos);
1230 std::string value = line.substr(eq_pos + 1);
1231 labels_[current_type][key] = value;
1245 if (!file.is_open())
1248 file <<
"# yaze Resource Labels\n";
1249 file <<
"# Format: [type] followed by key=value pairs\n\n";
1251 for (
const auto& [type, type_labels] :
labels_) {
1252 if (!type_labels.empty()) {
1253 file <<
"[" << type <<
"]\n";
1254 for (
const auto& [key, value] : type_labels) {
1255 file << key <<
"=" << value <<
"\n";
1266 if (!p_open || !*p_open)
1270 if (ImGui::Begin(
"Resource Labels", p_open)) {
1271 ImGui::Text(
"Resource Labels Manager");
1273 ImGui::Text(
"Total types: %zu",
labels_.size());
1275 for (
const auto& [type, type_labels] :
labels_) {
1276 if (ImGui::TreeNode(type.c_str())) {
1277 ImGui::Text(
"Labels: %zu", type_labels.size());
1278 for (
const auto& [key, value] : type_labels) {
1279 ImGui::Text(
"%s = %s", key.c_str(), value.c_str());
1289 const std::string& key,
1290 const std::string& newValue) {
1291 labels_[type][key] = newValue;
1295 bool selected,
const std::string& type,
const std::string& key,
1296 const std::string& defaultValue) {
1298 if (ImGui::Selectable(
1299 absl::StrFormat(
"%s: %s", key.c_str(),
GetLabel(type, key).c_str())
1307 const std::string& key) {
1308 auto type_it =
labels_.find(type);
1312 auto label_it = type_it->second.find(key);
1313 if (label_it == type_it->second.end())
1316 return label_it->second;
1320 const std::string& type,
const std::string& key,
1321 const std::string& defaultValue) {
1322 auto existing =
GetLabel(type, key);
1323 if (!existing.empty())
1326 labels_[type][key] = defaultValue;
1327 return defaultValue;
1335 const std::unordered_map<std::string,
1336 std::unordered_map<std::string, std::string>>&
1366 LOG_DEBUG(
"Project",
"Initialized embedded labels:");
1368 LOG_DEBUG(
"Project",
" - %d entrance names",
1370 LOG_DEBUG(
"Project",
" - %d sprite names",
1372 LOG_DEBUG(
"Project",
" - %d overlord names",
1375 LOG_DEBUG(
"Project",
" - %d music names",
1377 LOG_DEBUG(
"Project",
" - %d graphics names",
1379 LOG_DEBUG(
"Project",
" - %d room effect names",
1381 LOG_DEBUG(
"Project",
" - %d room tag names",
1383 LOG_DEBUG(
"Project",
" - %d tile type names",
1386 return absl::OkStatus();
1387 }
catch (
const std::exception& e) {
1388 return absl::InternalError(
1389 absl::StrCat(
"Failed to initialize embedded labels: ", e.what()));
1394 const std::string& default_value)
const {
1398 auto label_it = type_it->second.find(std::to_string(
id));
1399 if (label_it != type_it->second.end()) {
1400 return label_it->second;
1404 return default_value.empty() ? resource_type +
"_" + std::to_string(
id)
1409#ifdef __EMSCRIPTEN__
1411 return absl::UnimplementedError(
1412 "File-based label import is not supported in the web build");
1415 if (!file.is_open()) {
1416 return absl::InvalidArgumentError(
1417 absl::StrFormat(
"Cannot open labels file: %s",
filepath));
1420 std::stringstream buffer;
1421 buffer << file.rdbuf();
1429 const std::string& content) {
1436 auto status = provider.ImportFromZScreamFormat(content);
1441 LOG_DEBUG(
"Project",
"Imported ZScream labels:");
1445 LOG_DEBUG(
"Project",
" - %d room tag labels",
1448 return absl::OkStatus();
1457 "Initialized ResourceLabelProvider with project labels");
1458 LOG_DEBUG(
"Project",
" - prefer_hmagic_names: %s",
1466#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
1468absl::Status YazeProject::LoadFromJsonFormat(
const std::string& project_path) {
1469#ifdef __EMSCRIPTEN__
1470 return absl::UnimplementedError(
1471 "JSON project format loading is not supported in the web build");
1473 std::ifstream file(project_path);
1474 if (!file.is_open()) {
1475 return absl::InvalidArgumentError(
1476 absl::StrFormat(
"Cannot open JSON project file: %s", project_path));
1484 if (j.contains(
"yaze_project")) {
1485 auto& proj = j[
"yaze_project"];
1487 if (proj.contains(
"name"))
1488 name = proj[
"name"].get<std::string>();
1489 if (proj.contains(
"description"))
1491 if (proj.contains(
"author"))
1493 if (proj.contains(
"version"))
1495 if (proj.contains(
"created"))
1497 if (proj.contains(
"modified"))
1499 if (proj.contains(
"created_by"))
1503 if (proj.contains(
"rom_filename"))
1504 rom_filename = proj[
"rom_filename"].get<std::string>();
1505 if (proj.contains(
"code_folder"))
1506 code_folder = proj[
"code_folder"].get<std::string>();
1507 if (proj.contains(
"assets_folder"))
1509 if (proj.contains(
"patches_folder"))
1511 if (proj.contains(
"labels_filename"))
1513 if (proj.contains(
"symbols_filename"))
1517 if (proj.contains(
"use_embedded_labels")) {
1522 if (proj.contains(
"feature_flags")) {
1523 auto& flags = proj[
"feature_flags"];
1526 if (flags.contains(
"kSaveDungeonMaps"))
1528 flags[
"kSaveDungeonMaps"].get<
bool>();
1529 if (flags.contains(
"kSaveGraphicsSheet"))
1531 flags[
"kSaveGraphicsSheet"].get<
bool>();
1535 if (proj.contains(
"workspace_settings")) {
1536 auto& ws = proj[
"workspace_settings"];
1537 if (ws.contains(
"auto_save_enabled"))
1539 ws[
"auto_save_enabled"].get<
bool>();
1540 if (ws.contains(
"auto_save_interval"))
1542 ws[
"auto_save_interval"].get<
float>();
1545 if (proj.contains(
"agent_settings") &&
1546 proj[
"agent_settings"].is_object()) {
1547 auto& agent = proj[
"agent_settings"];
1574 if (agent.contains(
"favorite_models") &&
1575 agent[
"favorite_models"].is_array()) {
1577 for (
const auto& model : agent[
"favorite_models"]) {
1578 if (model.is_string())
1580 model.get<std::string>());
1583 if (agent.contains(
"model_chain") && agent[
"model_chain"].is_array()) {
1585 for (
const auto& model : agent[
"model_chain"]) {
1586 if (model.is_string())
1615 if (proj.contains(
"build_script"))
1616 build_script = proj[
"build_script"].get<std::string>();
1617 if (proj.contains(
"output_folder"))
1619 if (proj.contains(
"git_repository"))
1621 if (proj.contains(
"track_changes"))
1625 return absl::OkStatus();
1626 }
catch (
const json::exception& e) {
1627 return absl::InvalidArgumentError(
1628 absl::StrFormat(
"JSON parse error: %s", e.what()));
1632absl::Status YazeProject::SaveToJsonFormat() {
1633#ifdef __EMSCRIPTEN__
1634 return absl::UnimplementedError(
1635 "JSON project format saving is not supported in the web build");
1638 auto& proj = j[
"yaze_project"];
1642 proj[
"name"] =
name;
1664 proj[
"feature_flags"][
"kSaveGraphicsSheet"] =
1668 proj[
"workspace_settings"][
"auto_save_enabled"] =
1670 proj[
"workspace_settings"][
"auto_save_interval"] =
1673 auto& agent = proj[
"agent_settings"];
1709 if (!file.is_open()) {
1710 return absl::InvalidArgumentError(
1711 absl::StrFormat(
"Cannot write JSON project file: %s",
filepath));
1715 return absl::OkStatus();
1723 if (!config_dir.ok()) {
1730#ifdef __EMSCRIPTEN__
1731 auto status = platform::WasmStorage::SaveProject(
1734 LOG_WARN(
"RecentFilesManager",
"Could not persist recent files: %s",
1735 status.ToString().c_str());
1741 if (!config_dir_status.ok()) {
1742 LOG_ERROR(
"Project",
"Failed to get or create config directory: %s",
1743 config_dir_status.status().ToString().c_str());
1748 std::ofstream file(filepath);
1749 if (!file.is_open()) {
1750 LOG_WARN(
"RecentFilesManager",
"Could not save recent files to %s",
1756 file << file_path << std::endl;
1761#ifdef __EMSCRIPTEN__
1763 if (!storage_or.ok()) {
1767 std::istringstream stream(storage_or.value());
1769 while (std::getline(stream, line)) {
1770 if (!line.empty()) {
1776 std::ifstream file(filepath);
1777 if (!file.is_open()) {
1784 while (std::getline(file, line)) {
1785 if (!line.empty()) {
static std::vector< std::string > FindProjectsInDirectory(const std::string &directory)
static absl::Status ValidateProjectStructure(const YazeProject &project)
static absl::StatusOr< YazeProject > CreateFromTemplate(const std::string &template_name, const std::string &project_name, const std::string &base_path)
static std::vector< std::string > GetRecommendedFixesForProject(const YazeProject &project)
static std::vector< ProjectTemplate > GetProjectTemplates()
static absl::Status BackupProject(const YazeProject &project)
std::string GetFilePath() const
std::vector< std::string > recent_files_
#define ICON_MD_VIDEOGAME_ASSET
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
float ParseFloat(const std::string &value)
bool ParseBool(const std::string &value)
std::string SanitizeStorageKey(absl::string_view input)
std::vector< std::string > ParseStringList(const std::string &value)
const std::string kRecentFilesFilename
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
bool kSaveOverworldProperties
bool kApplyZSCustomOverworldASM
bool kLoadCustomOverworld
bool kSaveOverworldEntrances
bool kEnableCustomObjects
struct yaze::core::FeatureFlags::Flags::Overworld overworld
YazeProject template_project
std::string CreateOrGetLabel(const std::string &type, const std::string &key, const std::string &defaultValue)
void DisplayLabels(bool *p_open)
std::string GetLabel(const std::string &type, const std::string &key)
void EditLabel(const std::string &type, const std::string &key, const std::string &newValue)
bool LoadLabels(const std::string &filename)
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
std::string last_layout_preset
std::map< std::string, std::string > custom_keybindings
std::vector< std::string > saved_layouts
std::map< std::string, bool > editor_visibility
float autosave_interval_secs
std::vector< std::string > recent_files
std::string custom_system_prompt
std::string gemini_api_key
bool enable_tool_emulator
std::vector< std::string > favorite_models
bool enable_tool_resources
bool enable_tool_messages
std::vector< std::string > model_chain
std::string builder_blueprint_path
bool enable_tool_dialogue
bool enable_tool_overworld
std::string last_saved_at
bool persist_custom_music
Modern project structure with comprehensive settings consolidation.
std::string rom_backup_folder
absl::Status ResetToDefaults()
std::string custom_objects_folder
absl::Status RepairProject()
std::string MakeStorageKey(absl::string_view suffix) const
struct yaze::project::YazeProject::MusicPersistence music_persistence
absl::StatusOr< std::string > SerializeToString() const
std::string zscream_project_file
absl::Status ExportForZScream(const std::string &target_path)
absl::Status ImportZScreamProject(const std::string &zscream_project_path)
absl::Status SaveAllSettings()
absl::Status ImportLabelsFromZScreamContent(const std::string &content)
Import labels from ZScream format content directly.
std::string git_repository
void InitializeResourceLabelProvider()
Initialize the global ResourceLabelProvider with this project's labels.
absl::Status ParseFromString(const std::string &content)
std::vector< std::string > additional_roms
std::string patches_folder
absl::Status LoadFromYazeFormat(const std::string &project_path)
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
std::string GenerateProjectId() const
absl::Status Create(const std::string &project_name, const std::string &base_path)
std::string assets_folder
absl::Status SaveToYazeFormat()
absl::Status LoadAllSettings()
std::string labels_filename
std::vector< std::string > asm_sources
std::string GetDisplayName() const
std::vector< std::string > GetMissingFiles() const
WorkspaceSettings workspace_settings
std::string output_folder
std::string asm_entry_point
std::string GetRelativePath(const std::string &absolute_path) const
absl::Status InitializeEmbeddedLabels(const std::unordered_map< std::string, std::unordered_map< std::string, std::string > > &labels)
absl::Status SaveAs(const std::string &new_path)
struct yaze::project::YazeProject::AgentSettings agent_settings
absl::Status ImportFromZScreamFormat(const std::string &project_path)
void InitializeDefaults()
std::string GetAbsolutePath(const std::string &relative_path) const
std::string GetLabel(const std::string &resource_type, int id, const std::string &default_value="") const
absl::Status Open(const std::string &project_path)
absl::Status ImportLabelsFromZScream(const std::string &filepath)
Import labels from a ZScream DefaultNames.txt file.
std::string last_build_hash
std::map< std::string, std::string > zscream_mappings
absl::Status Validate() const
core::FeatureFlags::Flags feature_flags
std::vector< std::string > build_configurations
std::string symbols_filename