10#include <unordered_set>
13#include "absl/strings/ascii.h"
14#include "absl/strings/match.h"
15#include "absl/strings/str_cat.h"
16#include "absl/strings/str_format.h"
36#include "imgui/imgui.h"
37#include "imgui/misc/cpp/imgui_stdlib.h"
38#include "nlohmann/json.hpp"
73 std::string trimmed = token;
74 if (absl::StartsWithIgnoreCase(trimmed,
"0x")) {
75 trimmed = trimmed.substr(2);
77 if (trimmed.empty()) {
81 unsigned long value = std::strtoul(trimmed.c_str(), &end, 16);
82 if (end ==
nullptr || *end !=
'\0') {
85 if (value > 0xFFFFu) {
88 *out =
static_cast<uint16_t
>(value);
92bool ParseHexList(
const std::string& input, std::vector<uint16_t>* out,
105 std::string normalized = input;
106 for (
char& c : normalized) {
107 if (c ==
',' || c ==
';') {
112 std::stringstream ss(normalized);
114 std::unordered_set<uint16_t> seen;
115 while (ss >> token) {
116 auto dash = token.find(
'-');
117 if (dash != std::string::npos) {
118 std::string left = token.substr(0, dash);
119 std::string right = token.substr(dash + 1);
124 *error = absl::StrFormat(
"Invalid range: %s", token);
130 *error = absl::StrFormat(
"Range end before start: %s", token);
134 for (uint16_t value = start; value <= end; ++value) {
135 if (seen.insert(value).second) {
136 out->push_back(value);
138 if (value == 0xFFFF) {
146 *error = absl::StrFormat(
"Invalid hex value: %s", token);
150 if (seen.insert(value).second) {
151 out->push_back(value);
158std::string FormatHexList(
const std::vector<uint16_t>& values) {
160 result.reserve(values.size() * 6);
161 for (
size_t i = 0; i < values.size(); ++i) {
162 const uint16_t value = values[i];
163 std::string token = value <= 0xFF ? absl::StrFormat(
"0x%02X", value)
164 : absl::StrFormat(
"0x%04X", value);
165 if (!result.empty()) {
168 result.append(token);
174 std::vector<uint16_t> values;
175 for (uint16_t tile = 0xB0; tile <= 0xBE; ++tile) {
176 values.push_back(tile);
182 return {0xB7, 0xB8, 0xB9, 0xBA};
186 return {0xD0, 0xD1, 0xD2, 0xD3};
197bool IsLocalEndpoint(
const std::string& base_url) {
198 if (base_url.empty()) {
201 std::string lower = absl::AsciiStrToLower(base_url);
202 return absl::StrContains(lower,
"localhost") ||
203 absl::StrContains(lower,
"127.0.0.1") ||
204 absl::StrContains(lower,
"::1") ||
205 absl::StrContains(lower,
"0.0.0.0") ||
206 absl::StrContains(lower,
"192.168.") || absl::StartsWith(lower,
"10.");
209bool IsTailscaleEndpoint(
const std::string& base_url) {
210 if (base_url.empty()) {
213 std::string lower = absl::AsciiStrToLower(base_url);
214 return absl::StrContains(lower,
".ts.net") ||
215 absl::StrContains(lower,
"100.64.");
219 std::vector<std::string> tags;
220 if (IsLocalEndpoint(host.
base_url)) {
221 tags.push_back(
"local");
223 if (IsTailscaleEndpoint(host.
base_url)) {
224 tags.push_back(
"tailscale");
226 if (absl::StartsWith(absl::AsciiStrToLower(host.
base_url),
"https://")) {
227 tags.push_back(
"https");
228 }
else if (absl::StartsWith(absl::AsciiStrToLower(host.
base_url),
231 !IsTailscaleEndpoint(host.
base_url)) {
232 tags.push_back(
"http");
235 tags.push_back(
"vision");
238 tags.push_back(
"tools");
241 tags.push_back(
"stream");
246 std::string result =
"[";
247 for (
size_t i = 0; i < tags.size(); ++i) {
249 if (i + 1 < tags.size()) {
260 if (path.empty() || path.front() !=
'~') {
264 if (home_dir.empty() || home_dir ==
".") {
267 if (path.size() == 1) {
268 return home_dir.string();
270 if (path[1] ==
'/' || path[1] ==
'\\') {
271 return (home_dir / path.substr(2)).string();
273 return (home_dir / path.substr(1)).string();
276bool AddUniquePath(std::vector<std::string>* paths,
const std::string& path) {
277 if (!paths || path.empty()) {
281 auto it = std::find(paths->begin(), paths->end(), expanded);
282 if (it != paths->end()) {
285 paths->push_back(expanded);
293 ImGui::TextDisabled(tr(
"Settings not available"));
300 ImGuiTreeNodeFlags_DefaultOpen)) {
308 if (ImGui::CollapsingHeader(
ICON_MD_FOLDER " Project Configuration")) {
330 " Workspace Layout")) {
337 if (ImGui::CollapsingHeader(
ICON_MD_TUNE " Editor Behavior")) {
376 ImGui::TextDisabled(tr(
"Feature Flags configuration"));
384 if (ImGui::TreeNode(
ICON_MD_MAP " Overworld Flags")) {
407 ImGui::TextDisabled(tr(
"No active project."));
421 const char* roles[] = {
"base",
"dev",
"patched",
"release"};
423 if (ImGui::Combo(tr(
"Role"), &role_index, roles, IM_ARRAYSIZE(roles))) {
428 const char* policies[] = {
"allow",
"warn",
"block"};
430 if (ImGui::Combo(tr(
"Write Policy"), &policy_index, policies,
431 IM_ARRAYSIZE(policies))) {
438 if (ImGui::InputText(tr(
"Expected Hash"), &expected_hash)) {
443 static std::string cached_rom_hash;
444 static std::string cached_rom_path;
450 ImGui::Text(tr(
"Current ROM Hash: %s"), cached_rom_hash.empty()
452 : cached_rom_hash.c_str());
453 if (ImGui::Button(tr(
"Use Current ROM Hash"))) {
458 ImGui::TextDisabled(tr(
"Current ROM Hash: (no ROM loaded)"));
467 if (ImGui::InputText(tr(
"Output Folder"), &output_folder)) {
474 if (ImGui::InputText(tr(
"Git Repository"), &git_repo)) {
485 if (ImGui::InputText(tr(
"Build Target (ROM)"), &build_target)) {
492 if (ImGui::InputText(tr(
"Symbols File"), &symbols_file)) {
498 ImGui::Text(tr(
"%s ASM / Hack Manifest"),
ICON_MD_CODE);
500 ImGui::TextWrapped(tr(
501 "Optional: load a hack manifest JSON (generated by an ASM project) to "
502 "annotate room tags, show feature flags, and surface which ROM regions "
503 "are owned by ASM vs safe to edit in yaze."));
506 if (ImGui::InputText(tr(
"Hack Manifest File"), &manifest_file)) {
514 ImGui::TextDisabled(manifest_loaded ?
"(loaded)" :
"(not loaded)");
515 if (ImGui::Button(tr(
"Reload Manifest"))) {
519 if (manifest_loaded) {
522 ImGui::Text(tr(
"Manifest Version: %d"),
527 if (!pipeline.dev_rom.empty()) {
528 ImGui::Text(tr(
"Dev ROM: %s"), pipeline.dev_rom.c_str());
530 if (!pipeline.patched_rom.empty()) {
531 ImGui::Text(tr(
"Patched ROM: %s"), pipeline.patched_rom.c_str());
533 if (!pipeline.build_script.empty()) {
534 ImGui::Text(tr(
"Build Script: %s"), pipeline.build_script.c_str());
538 if (msg_layout.first_expanded_id != 0 || msg_layout.last_expanded_id != 0) {
539 ImGui::Text(tr(
"Expanded Messages: 0x%03X-0x%03X (%d)"),
540 msg_layout.first_expanded_id, msg_layout.last_expanded_id,
541 msg_layout.expanded_count);
544 if (ImGui::TreeNode(
ICON_MD_FLAG " Hack Feature Flags")) {
546 ImGui::BulletText(
"%s = %d (%s)", flag.name.c_str(), flag.value,
547 flag.enabled ?
"enabled" :
"disabled");
548 if (!flag.source.empty()) {
550 ImGui::TextDisabled(
"%s", flag.source.c_str());
556 if (ImGui::TreeNode(
ICON_MD_LABEL " Room Tags (Dispatch)")) {
558 ImGui::BulletText(tr(
"0x%02X: %s"), tag.tag_id, tag.name.c_str());
559 if (!tag.enabled && !tag.feature_flag.empty()) {
561 ImGui::TextDisabled(tr(
"(disabled by %s)"), tag.feature_flag.c_str());
563 if (!tag.purpose.empty() && ImGui::IsItemHovered()) {
564 ImGui::SetTooltip(
"%s", tag.purpose.c_str());
576 if (ImGui::InputText(tr(
"Backup Folder"), &backup_folder)) {
589 {.min = 0, .max = 10000})) {
602 {.min = 1, .max = 3650})) {
611 tr(
"Configure collision/object IDs used by minecart overlays and audits. "
612 "Hex values, ranges allowed (e.g. B0-BE)."));
614 static std::string overlay_project_path;
615 static HexListEditorState track_tiles_state;
616 static HexListEditorState stop_tiles_state;
617 static HexListEditorState switch_tiles_state;
618 static HexListEditorState track_object_state;
619 static HexListEditorState minecart_sprite_state;
623 track_tiles_state.text =
625 stop_tiles_state.text =
627 switch_tiles_state.text =
629 track_object_state.text =
631 minecart_sprite_state.text =
633 track_tiles_state.error.clear();
634 stop_tiles_state.error.clear();
635 switch_tiles_state.error.clear();
636 track_object_state.error.clear();
637 minecart_sprite_state.error.clear();
640 auto draw_hex_list = [&](
const char* label,
const char* hint,
641 HexListEditorState& state,
642 const std::vector<uint16_t>& defaults,
643 std::vector<uint16_t>* target) {
649 ImGui::PushItemWidth(-180.0f);
650 if (ImGui::InputTextWithHint(label, hint, &state.text)) {
653 ImGui::PopItemWidth();
654 if (ImGui::IsItemDeactivatedAfterEdit()) {
659 if (ImGui::SmallButton(absl::StrFormat(
"Apply##%s", label).c_str())) {
663 if (ImGui::SmallButton(absl::StrFormat(
"Defaults##%s", label).c_str())) {
664 state.text = FormatHexList(defaults);
667 if (ImGui::IsItemHovered()) {
668 ImGui::SetTooltip(tr(
"Reset to defaults"));
671 if (ImGui::SmallButton(absl::StrFormat(
"Clear##%s", label).c_str())) {
675 if (ImGui::IsItemHovered()) {
676 ImGui::SetTooltip(tr(
"Clear list (empty uses defaults)"));
679 const bool uses_defaults = target->empty();
680 const std::vector<uint16_t>& effective_values =
681 uses_defaults ? defaults : *target;
684 if (ImGui::IsItemHovered()) {
685 ImGui::BeginTooltip();
686 ImGui::Text(tr(
"Effective: %s"), FormatHexList(effective_values).c_str());
688 ImGui::TextDisabled(tr(
"Using defaults (list is empty)"));
694 std::vector<uint16_t> parsed;
696 if (ParseHexList(state.text, &parsed, &error)) {
700 state.text = FormatHexList(parsed);
706 if (!state.error.empty()) {
707 ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.4f, 1.0f),
"%s",
708 state.error.c_str());
712 draw_hex_list(
"Track Tiles",
"0xB0-0xBE", track_tiles_state,
714 draw_hex_list(
"Stop Tiles",
"0xB7, 0xB8, 0xB9, 0xBA", stop_tiles_state,
717 draw_hex_list(
"Switch Tiles",
"0xD0-0xD3", switch_tiles_state,
718 DefaultSwitchTiles(),
720 draw_hex_list(
"Track Object IDs",
"0x31", track_object_state,
721 DefaultTrackObjectIds(),
723 draw_hex_list(
"Minecart Sprite IDs",
"0xA3", minecart_sprite_state,
724 DefaultMinecartSpriteIds(),
735 static int selected_root_index = -1;
736 static std::string new_root_path;
742 ImGui::TextDisabled(tr(
"No project roots configured."));
745 if (ImGui::BeginChild(
"ProjectRootsList", ImVec2(0, 140),
true)) {
746 for (
size_t i = 0; i < roots.size(); ++i) {
747 const bool is_default = roots[i] == prefs.default_project_root;
751 label +=
" (default)";
753 if (ImGui::Selectable(label.c_str(),
754 selected_root_index ==
static_cast<int>(i))) {
755 selected_root_index =
static_cast<int>(i);
761 const bool has_selection =
762 selected_root_index >= 0 &&
763 selected_root_index < static_cast<int>(roots.size());
765 if (ImGui::Button(tr(
"Set Default"))) {
766 prefs.default_project_root = roots[selected_root_index];
771 const std::string removed = roots[selected_root_index];
772 roots.erase(roots.begin() + selected_root_index);
773 if (prefs.default_project_root == removed) {
774 prefs.default_project_root = roots.empty() ?
"" : roots.front();
776 selected_root_index = roots.empty()
778 : std::min(selected_root_index,
779 static_cast<int>(roots.size() - 1));
788 ImGui::InputTextWithHint(
"##project_root_add",
"Add folder path...",
791 const std::string trimmed =
792 std::string(absl::StripAsciiWhitespace(new_root_path));
793 if (!trimmed.empty()) {
794 if (AddUniquePath(&roots, trimmed) &&
795 prefs.default_project_root.empty()) {
796 prefs.default_project_root = trimmed;
804 if (!folder.empty()) {
805 if (AddUniquePath(&roots, folder) && prefs.default_project_root.empty()) {
806 prefs.default_project_root = folder;
819 if (AddUniquePath(&roots, docs_dir->string()) &&
820 prefs.default_project_root.empty()) {
821 prefs.default_project_root = docs_dir->string();
830 if (icloud_dir.ok()) {
831 if (AddUniquePath(&roots, icloud_dir->string())) {
832 prefs.default_project_root = icloud_dir->string();
838 tr(
"iCloud projects live in Documents/Yaze/iCloud on this Mac."));
844 bool use_icloud_sync = prefs.use_icloud_sync;
845 if (ImGui::Checkbox(tr(
"Use iCloud sync (Documents)"), &use_icloud_sync)) {
846 prefs.use_icloud_sync = use_icloud_sync;
847 if (use_icloud_sync) {
850 if (icloud_dir.ok()) {
851 AddUniquePath(&roots, icloud_dir->string());
852 prefs.default_project_root = icloud_dir->string();
858 bool use_files_app = prefs.use_files_app;
859 if (ImGui::Checkbox(tr(
"Prefer Files app on iOS"), &use_files_app)) {
860 prefs.use_files_app = use_files_app;
872 const auto& current = theme_manager.GetCurrentThemeName();
873 const auto& current_theme = theme_manager.GetCurrentTheme();
875 ImGui::Text(tr(
"Current Theme:"));
880 ImDrawList* draw_list = ImGui::GetWindowDrawList();
881 ImVec2 cursor = ImGui::GetCursorScreenPos();
882 const float swatch_size = 12.0f;
883 const float spacing = 2.0f;
885 auto draw_swatch = [&](
const gui::Color& color,
float offset_x) {
886 ImVec2 p_min(cursor.x + offset_x, cursor.y);
887 ImVec2 p_max(p_min.x + swatch_size, p_min.y + swatch_size);
890 draw_list->AddRectFilled(p_min, p_max, col);
893 ImGui::ColorConvertFloat4ToU32(ImVec4(0.5f, 0.5f, 0.5f, 0.6f)));
896 draw_swatch(current_theme.primary, 0.0f);
897 draw_swatch(current_theme.surface, swatch_size + spacing);
898 draw_swatch(current_theme.accent, 2.0f * (swatch_size + spacing));
902 ImVec2(3.0f * swatch_size + 2.0f * spacing + 4.0f, swatch_size));
906 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
"%s", current.c_str());
911 ImGui::Text(tr(
"Available Themes:"));
913 bool any_theme_hovered =
false;
914 if (ImGui::BeginChild(
"ThemeList", ImVec2(0, 200),
true)) {
915 for (
const auto& theme_name : theme_manager.GetAvailableThemes()) {
916 ImGui::PushID(theme_name.c_str());
917 bool is_current = (theme_name == current);
920 const gui::Theme* theme_data = theme_manager.GetTheme(theme_name);
922 ImDrawList* draw_list = ImGui::GetWindowDrawList();
923 ImVec2 cursor = ImGui::GetCursorScreenPos();
924 const float swatch_size = 10.0f;
925 const float swatch_spacing = 2.0f;
926 const float total_swatch_width =
927 3.0f * swatch_size + 2.0f * swatch_spacing + 6.0f;
929 auto draw_small_swatch = [&](
const gui::Color& color,
float offset_x) {
930 ImVec2 p_min(cursor.x + offset_x, cursor.y + 2.0f);
931 ImVec2 p_max(p_min.x + swatch_size, p_min.y + swatch_size);
934 draw_list->AddRectFilled(p_min, p_max, col);
937 ImGui::ColorConvertFloat4ToU32(ImVec4(0.4f, 0.4f, 0.4f, 0.5f)));
940 draw_small_swatch(theme_data->
primary, 0.0f);
941 draw_small_swatch(theme_data->
surface, swatch_size + swatch_spacing);
942 draw_small_swatch(theme_data->
accent,
943 2.0f * (swatch_size + swatch_spacing));
946 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + total_swatch_width);
950 std::string label = is_current
952 : std::string(
" ") + theme_name;
954 if (ImGui::Selectable(label.c_str(), is_current)) {
958 if (theme_manager.IsPreviewActive()) {
959 theme_manager.EndPreview();
961 theme_manager.ApplyTheme(theme_name);
965 if (ImGui::IsItemHovered()) {
966 any_theme_hovered =
true;
967 theme_manager.StartPreview(theme_name);
976 if (!any_theme_hovered && theme_manager.IsPreviewActive()) {
977 theme_manager.EndPreview();
982 theme_manager.RefreshAvailableThemes();
984 if (ImGui::IsItemHovered()) {
986 tr(
"Re-scan theme directories for new or changed themes"));
990 ImGui::SeparatorText(tr(
"Display Density"));
993 auto preset = theme_manager.GetCurrentTheme().density_preset;
994 int density =
static_cast<int>(preset);
995 bool changed =
false;
996 changed |= ImGui::RadioButton(tr(
"Compact (0.75x)"), &density, 0);
998 changed |= ImGui::RadioButton(tr(
"Normal (1.0x)"), &density, 1);
1000 changed |= ImGui::RadioButton(tr(
"Comfortable (1.25x)"), &density, 2);
1004 auto theme = theme_manager.GetCurrentTheme();
1005 theme.ApplyDensityPreset(new_preset);
1006 theme_manager.ApplyTheme(theme);
1011 ImGui::SeparatorText(tr(
"Editor/Workspace Motion"));
1015 if (ImGui::Checkbox(tr(
"Reduced Motion"), &reduced_motion)) {
1016 prefs.reduced_motion = reduced_motion;
1018 prefs.reduced_motion,
1022 if (ImGui::IsItemHovered()) {
1024 tr(
"Disable panel/editor transition animations for a calmer editing "
1028 int switch_profile = std::clamp(prefs.switch_motion_profile, 0, 2);
1029 const char* switch_profile_labels[] = {
"Snappy",
"Standard",
"Relaxed"};
1030 if (ImGui::Combo(tr(
"Switch Motion Profile"), &switch_profile,
1031 switch_profile_labels,
1032 IM_ARRAYSIZE(switch_profile_labels))) {
1033 prefs.switch_motion_profile = switch_profile;
1035 prefs.reduced_motion,
1039 if (ImGui::IsItemHovered()) {
1041 tr(
"Controls editor/workspace switch timing and easing for panel fades "
1042 "and sidebar slides."));
1057 ImGui::Text(tr(
"Global Font Scale"));
1059 if (ImGui::SliderFloat(
"##global_font_scale", &scale, 0.5f, 2.0f,
"%.2f")) {
1061 ImGui::GetIO().FontGlobalScale = scale;
1071 if (ImGui::Checkbox(tr(
"Show Status Bar"), &show_status_bar)) {
1079 if (ImGui::IsItemHovered()) {
1081 tr(
"Display ROM, session, cursor, and zoom info at bottom of window"));
1087 ImGui::TextDisabled(tr(
"UserSettings unavailable."));
1096 if (prefs.named_layouts.empty()) {
1097 ImGui::TextDisabled(
1098 tr(
"No saved layouts yet. Open the Layout Designer to capture one."));
1102 std::vector<std::string> names;
1103 names.reserve(prefs.named_layouts.size());
1104 for (
const auto& entry : prefs.named_layouts) {
1105 names.push_back(entry.first);
1107 std::sort(names.begin(), names.end());
1109 const std::string&
active = prefs.last_applied_layout_name;
1110 const char* preview =
1111 active.empty() ?
"(select a layout…)" :
active.c_str();
1113 if (ImGui::BeginCombo(tr(
"Active Layout"), preview)) {
1114 for (
const auto& name : names) {
1115 const bool is_selected = (name ==
active);
1116 if (ImGui::Selectable(name.c_str(), is_selected)) {
1125 ImGui::SetItemDefaultFocus();
1132 ImGui::BeginDisabled(
active.empty() ||
1133 prefs.named_layouts.count(
active) == 0);
1134 if (ImGui::SmallButton(tr(
"Re-apply"))) {
1137 ImGui::EndDisabled();
1138 if (ImGui::IsItemHovered()) {
1140 tr(
"Re-apply the active layout (useful after closing or rearranging "
1141 "panels manually)."));
1157 "Open failed: \"layout.designer\" panel is not registered.";
1165 if (ImGui::IsItemHovered()) {
1167 tr(
"Open the Layout Designer panel to author or capture a workspace "
1192 if (it == prefs.named_layouts.end()) {
1194 absl::StrCat(
"Apply failed: \"", name,
"\" not found.");
1199 nlohmann::json parsed;
1201 parsed = nlohmann::json::parse(it->second);
1202 }
catch (
const nlohmann::json::parse_error& e) {
1204 absl::StrCat(
"Apply failed: invalid JSON (", e.what(),
").");
1210 if (!tree_or.ok()) {
1212 absl::StrCat(
"Apply failed: ", tree_or.status().message());
1217 std::string validation_error;
1218 if (!tree_or->Validate(&validation_error)) {
1220 absl::StrCat(
"Apply failed: invalid layout (", validation_error,
").");
1226 if (manager ==
nullptr) {
1232 if (dockspace_id == 0) {
1238 const absl::Status apply_status =
1240 if (!apply_status.ok()) {
1242 absl::StrCat(
"Apply failed: ", apply_status.message());
1247 prefs.last_applied_layout_name = name;
1260 if (ImGui::Checkbox(tr(
"Enable Auto-Save"),
1268 if (ImGui::SliderInt(tr(
"Interval (sec)"), &interval, 60, 600)) {
1273 if (ImGui::Checkbox(tr(
"Backup Before Save"),
1293 const char* editors[] = {
"None",
"Overworld",
"Dungeon",
"Graphics"};
1295 editors, IM_ARRAYSIZE(editors))) {
1302 if (ImGui::Checkbox(tr(
"Use HMagic sprite names (expanded)"),
1330 if (ImGui::SliderInt(tr(
"Cache Size (MB)"),
1335 if (ImGui::SliderInt(tr(
"Undo History"),
1342 ImGui::Text(tr(
"Current FPS: %.1f"), ImGui::GetIO().Framerate);
1343 ImGui::Text(tr(
"Frame Time: %.3f ms"), 1000.0f / ImGui::GetIO().Framerate);
1352 static int selected_host_index = -1;
1354 auto draw_key_row = [&](
const char* label, std::string* key,
1355 const char* env_var,
const char* id) {
1357 ImGui::Text(
"%s", label);
1358 const ImVec2 button_size = ImGui::CalcTextSize(
ICON_MD_SYNC " Env");
1359 float env_button_width =
1360 button_size.x + ImGui::GetStyle().FramePadding.x * 2.0f;
1361 float input_width = ImGui::GetContentRegionAvail().x - env_button_width -
1362 ImGui::GetStyle().ItemSpacing.x;
1363 bool stack = input_width < 160.0f;
1364 ImGui::SetNextItemWidth(stack ? -1.0f : input_width);
1365 if (ImGui::InputTextWithHint(
"##key",
"API key...", key,
1366 ImGuiInputTextFlags_Password)) {
1373 const char* env_key = std::getenv(env_var);
1385 draw_key_row(
"OpenAI", &prefs.openai_api_key,
"OPENAI_API_KEY",
"openai_key");
1386 draw_key_row(
"Anthropic", &prefs.anthropic_api_key,
"ANTHROPIC_API_KEY",
1388 draw_key_row(
"Google (Gemini)", &prefs.gemini_api_key,
"GEMINI_API_KEY",
1393 ImGui::Text(tr(
"%s Provider Defaults (legacy)"),
ICON_MD_CLOUD);
1396 const char* providers[] = {
"Ollama (Local)",
"Gemini (Cloud)",
1398 if (ImGui::Combo(
"##Provider", &prefs.ai_provider, providers,
1399 IM_ARRAYSIZE(providers))) {
1407 const char* active_preview =
"None";
1408 const char* remote_preview =
"None";
1409 for (
const auto& host : hosts) {
1410 if (!prefs.active_ai_host_id.empty() &&
1411 host.id == prefs.active_ai_host_id) {
1412 active_preview = host.label.c_str();
1414 if (!prefs.remote_build_host_id.empty() &&
1415 host.id == prefs.remote_build_host_id) {
1416 remote_preview = host.label.c_str();
1420 if (ImGui::BeginCombo(tr(
"Active Host"), active_preview)) {
1421 for (
size_t i = 0; i < hosts.size(); ++i) {
1422 const bool is_selected = (!prefs.active_ai_host_id.empty() &&
1423 hosts[i].id == prefs.active_ai_host_id);
1424 if (ImGui::Selectable(hosts[i].label.c_str(), is_selected)) {
1425 prefs.active_ai_host_id = hosts[i].id;
1426 if (prefs.remote_build_host_id.empty()) {
1427 prefs.remote_build_host_id = hosts[i].id;
1432 ImGui::SetItemDefaultFocus();
1438 if (ImGui::BeginCombo(tr(
"Remote Build Host"), remote_preview)) {
1439 for (
size_t i = 0; i < hosts.size(); ++i) {
1440 const bool is_selected = (!prefs.remote_build_host_id.empty() &&
1441 hosts[i].id == prefs.remote_build_host_id);
1442 if (ImGui::Selectable(hosts[i].label.c_str(), is_selected)) {
1443 prefs.remote_build_host_id = hosts[i].id;
1447 ImGui::SetItemDefaultFocus();
1457 if (selected_host_index >=
static_cast<int>(hosts.size())) {
1458 selected_host_index = hosts.empty() ? -1 : 0;
1460 if (selected_host_index < 0 && !hosts.empty()) {
1461 for (
size_t i = 0; i < hosts.size(); ++i) {
1462 if (!prefs.active_ai_host_id.empty() &&
1463 hosts[i].id == prefs.active_ai_host_id) {
1464 selected_host_index =
static_cast<int>(i);
1468 if (selected_host_index < 0) {
1469 selected_host_index = 0;
1473 ImGui::BeginChild(
"##ai_host_list", ImVec2(0, 150),
true);
1474 for (
size_t i = 0; i < hosts.size(); ++i) {
1475 const bool is_selected =
static_cast<int>(i) == selected_host_index;
1476 std::string label = hosts[i].label;
1477 if (hosts[i].
id == prefs.active_ai_host_id) {
1478 label +=
" (active)";
1480 if (hosts[i].
id == prefs.remote_build_host_id) {
1481 label +=
" (build)";
1483 if (ImGui::Selectable(label.c_str(), is_selected)) {
1484 selected_host_index =
static_cast<int>(i);
1486 std::string tags = BuildHostTagString(hosts[i]);
1487 if (!tags.empty()) {
1489 ImGui::TextDisabled(
"%s", tags.c_str());
1495 if (host.id.empty()) {
1496 host.id = absl::StrFormat(
"host-%zu", hosts.size() + 1);
1498 hosts.push_back(host);
1499 selected_host_index =
static_cast<int>(hosts.size() - 1);
1500 if (prefs.active_ai_host_id.empty()) {
1501 prefs.active_ai_host_id = host.id;
1503 if (prefs.remote_build_host_id.empty()) {
1504 prefs.remote_build_host_id = host.id;
1511 host.
label =
"New Host";
1512 host.
base_url =
"http://localhost:1234";
1517 if (ImGui::Button(
ICON_MD_DELETE " Remove") && selected_host_index >= 0 &&
1518 selected_host_index <
static_cast<int>(hosts.size())) {
1519 const std::string removed_id = hosts[selected_host_index].id;
1520 hosts.erase(hosts.begin() + selected_host_index);
1521 if (prefs.active_ai_host_id == removed_id) {
1522 prefs.active_ai_host_id = hosts.empty() ?
"" : hosts.front().id;
1524 if (prefs.remote_build_host_id == removed_id) {
1525 prefs.remote_build_host_id = prefs.active_ai_host_id;
1527 selected_host_index =
1530 : std::min(selected_host_index,
static_cast<int>(hosts.size() - 1));
1535 if (ImGui::Button(tr(
"Add LM Studio"))) {
1537 host.
label =
"LM Studio (local)";
1538 host.
base_url =
"http://localhost:1234";
1545 if (ImGui::Button(tr(
"Add AFS Bridge"))) {
1547 host.
label =
"halext AFS Bridge";
1548 host.
base_url =
"https://halext.org";
1555 if (ImGui::Button(tr(
"Add Ollama"))) {
1557 host.
label =
"Ollama (local)";
1558 host.
base_url =
"http://localhost:11434";
1565 static std::string tailscale_host;
1566 ImGui::InputTextWithHint(
"##tailscale_host",
"host.ts.net:1234",
1569 if (ImGui::Button(tr(
"Add Tailscale Host"))) {
1570 std::string trimmed =
1571 std::string(absl::StripAsciiWhitespace(tailscale_host));
1572 if (!trimmed.empty()) {
1574 host.
label =
"Tailscale Host";
1575 if (absl::StrContains(trimmed,
"://")) {
1578 host.
base_url =
"http://" + trimmed;
1585 tailscale_host.clear();
1589 if (selected_host_index >= 0 &&
1590 selected_host_index <
static_cast<int>(hosts.size())) {
1591 auto& host = hosts[
static_cast<size_t>(selected_host_index)];
1593 ImGui::Text(tr(
"Host Details"));
1595 if (ImGui::InputText(tr(
"Label"), &host.label)) {
1598 if (ImGui::InputText(tr(
"Base URL"), &host.base_url)) {
1602 const char* api_types[] = {
"openai",
"ollama",
"gemini",
1603 "anthropic",
"lmstudio",
"grpc"};
1605 for (
int i = 0; i < IM_ARRAYSIZE(api_types); ++i) {
1606 if (host.api_type == api_types[i]) {
1611 if (ImGui::Combo(tr(
"API Type"), &api_index, api_types,
1612 IM_ARRAYSIZE(api_types))) {
1613 host.api_type = api_types[api_index];
1617 if (ImGui::InputText(tr(
"API Key"), &host.api_key,
1618 ImGuiInputTextFlags_Password)) {
1621 if (ImGui::InputText(tr(
"Keychain ID"), &host.credential_id)) {
1625 if (ImGui::SmallButton(tr(
"Use Host ID"))) {
1626 host.credential_id = host.id;
1629 if (!host.credential_id.empty() && host.api_key.empty()) {
1630 ImGui::TextDisabled(tr(
"Keychain lookup enabled (leave API key empty)."));
1633 if (ImGui::Checkbox(tr(
"Supports Vision"), &host.supports_vision)) {
1637 if (ImGui::Checkbox(tr(
"Supports Tools"), &host.supports_tools)) {
1641 if (ImGui::Checkbox(tr(
"Supports Streaming"), &host.supports_streaming)) {
1644 if (ImGui::Checkbox(tr(
"Allow Insecure HTTP"), &host.allow_insecure)) {
1653 auto& model_paths = prefs.ai_model_paths;
1654 static int selected_model_path = -1;
1655 static std::string new_model_path;
1657 if (model_paths.empty()) {
1658 ImGui::TextDisabled(tr(
"No model paths configured."));
1661 if (ImGui::BeginChild(
"ModelPathsList", ImVec2(0, 120),
true)) {
1662 for (
size_t i = 0; i < model_paths.size(); ++i) {
1665 if (ImGui::Selectable(label.c_str(),
1666 selected_model_path ==
static_cast<int>(i))) {
1667 selected_model_path =
static_cast<int>(i);
1673 const bool has_model_selection =
1674 selected_model_path >= 0 &&
1675 selected_model_path < static_cast<int>(model_paths.size());
1676 if (has_model_selection) {
1678 model_paths.erase(model_paths.begin() + selected_model_path);
1679 selected_model_path =
1682 : std::min(selected_model_path,
1683 static_cast<int>(model_paths.size() - 1));
1689 ImGui::InputTextWithHint(
"##model_path_add",
"Add folder path...",
1692 const std::string trimmed =
1693 std::string(absl::StripAsciiWhitespace(new_model_path));
1694 if (!trimmed.empty() && AddUniquePath(&model_paths, trimmed)) {
1696 new_model_path.clear();
1702 if (!folder.empty() && AddUniquePath(&model_paths, folder)) {
1712 if (!home_dir.empty() && home_dir !=
".") {
1713 if (AddUniquePath(&model_paths, (home_dir /
"models").
string())) {
1719 if (ImGui::Button(tr(
"Add ~/.lmstudio/models"))) {
1720 if (!home_dir.empty() && home_dir !=
".") {
1721 if (AddUniquePath(&model_paths,
1722 (home_dir /
".lmstudio" /
"models").
string())) {
1728 if (ImGui::Button(tr(
"Add ~/.ollama/models"))) {
1729 if (!home_dir.empty() && home_dir !=
".") {
1730 if (AddUniquePath(&model_paths,
1731 (home_dir /
".ollama" /
"models").
string())) {
1741 if (ImGui::SliderFloat(tr(
"Temperature"),
1745 ImGui::TextDisabled(tr(
"Higher = more creative"));
1756 if (ImGui::Checkbox(tr(
"Proactive Suggestions"),
1761 if (ImGui::Checkbox(tr(
"Auto-Learn Preferences"),
1766 if (ImGui::Checkbox(tr(
"Enable Vision"),
1775 const char* log_levels[] = {
"Debug",
"Info",
"Warning",
"Error",
"Fatal"};
1777 log_levels, IM_ARRAYSIZE(log_levels))) {
1785 ImGuiTreeNodeFlags_DefaultOpen)) {
1786 ImGui::InputTextWithHint(
"##shortcut_filter",
"Filter shortcuts...",
1788 if (ImGui::IsItemHovered()) {
1789 ImGui::SetTooltip(tr(
"Filter by action name or key combo"));
1793 if (ImGui::TreeNode(
"Global Shortcuts")) {
1797 if (ImGui::TreeNode(
"Editor Shortcuts")) {
1801 if (ImGui::TreeNode(
"Panel Shortcuts")) {
1805 ImGui::TextDisabled(
1806 tr(
"Tip: Use Cmd/Opt labels on macOS or Ctrl/Alt on Windows/Linux. "
1807 "Function keys and symbols (/, -) are supported."));
1816 std::string haystack = absl::AsciiStrToLower(text);
1818 return absl::StrContains(haystack, needle);
1823 ImGui::TextDisabled(tr(
"Not available"));
1829 if (shortcuts.empty()) {
1830 ImGui::TextDisabled(tr(
"No global shortcuts registered."));
1834 static std::unordered_map<std::string, std::string> editing;
1836 bool has_match =
false;
1837 for (
const auto& sc : shortcuts) {
1838 std::string label = sc.name;
1844 auto it = editing.find(sc.name);
1845 if (it == editing.end()) {
1850 current = u->second;
1852 editing[sc.name] = current;
1855 ImGui::PushID(sc.name.c_str());
1856 ImGui::Text(
"%s", sc.name.c_str());
1858 ImGui::SetNextItemWidth(180);
1859 std::string& value = editing[sc.name];
1860 if (ImGui::InputText(
"##global", &value,
1861 ImGuiInputTextFlags_EnterReturnsTrue |
1862 ImGuiInputTextFlags_AutoSelectAll)) {
1864 if (!parsed.empty() || value.empty()) {
1867 if (value.empty()) {
1878 ImGui::TextDisabled(tr(
"No shortcuts match the current filter."));
1884 ImGui::TextDisabled(tr(
"Not available"));
1890 std::map<std::string, std::vector<Shortcut>> grouped;
1891 static std::unordered_map<std::string, std::string> editing;
1893 for (
const auto& sc : shortcuts) {
1894 auto pos = sc.name.find(
".");
1896 pos != std::string::npos ? sc.name.substr(0, pos) :
"general";
1897 grouped[group].push_back(sc);
1899 bool has_match =
false;
1900 for (
const auto& [group, list] : grouped) {
1901 std::vector<Shortcut> filtered;
1902 filtered.reserve(list.size());
1903 for (
const auto& sc : list) {
1906 filtered.push_back(sc);
1909 if (filtered.empty()) {
1913 if (ImGui::TreeNode(group.c_str())) {
1914 for (
const auto& sc : filtered) {
1915 ImGui::PushID(sc.name.c_str());
1916 ImGui::Text(
"%s", sc.name.c_str());
1918 ImGui::SetNextItemWidth(180);
1919 std::string& value = editing[sc.name];
1920 if (value.empty()) {
1928 if (ImGui::InputText(
"##editor", &value,
1929 ImGuiInputTextFlags_EnterReturnsTrue |
1930 ImGuiInputTextFlags_AutoSelectAll)) {
1932 if (!parsed.empty() || value.empty()) {
1934 if (value.empty()) {
1948 ImGui::TextDisabled(tr(
"No shortcuts match the current filter."));
1954 ImGui::TextDisabled(tr(
"Registry not available"));
1961 bool has_match =
false;
1962 for (
const auto& category : categories) {
1964 std::vector<
decltype(cards)::value_type> filtered_cards;
1965 filtered_cards.reserve(cards.size());
1966 for (
const auto& card : cards) {
1969 filtered_cards.push_back(card);
1972 if (filtered_cards.empty()) {
1976 if (ImGui::TreeNode(category.c_str())) {
1978 for (
const auto& card : filtered_cards) {
1979 ImGui::PushID(card.card_id.c_str());
1981 ImGui::Text(
"%s %s", card.icon.c_str(), card.display_name.c_str());
1983 std::string current_shortcut;
1986 current_shortcut = it->second;
1987 }
else if (!card.shortcut_hint.empty()) {
1988 current_shortcut = card.shortcut_hint;
1990 current_shortcut =
"None";
1994 std::string display_shortcut = current_shortcut;
1996 if (!parsed.empty()) {
2001 ImGui::SetNextItemWidth(120);
2002 ImGui::SetKeyboardFocusHere();
2005 ImGuiInputTextFlags_EnterReturnsTrue)) {
2022 if (ImGui::Button(display_shortcut.c_str(), ImVec2(120, 0))) {
2028 if (ImGui::IsItemHovered()) {
2029 ImGui::SetTooltip(tr(
"Click to edit shortcut"));
2040 ImGui::TextDisabled(tr(
"No shortcuts match the current filter."));
2049 if (patches_dir_status.ok()) {
2064 ImGui::TextDisabled(tr(
"No patches loaded"));
2065 ImGui::TextDisabled(tr(
"Place .asm patches in assets/patches/"));
2067 if (ImGui::Button(tr(
"Browse for Patches Folder..."))) {
2076 ImGui::Text(tr(
"Loaded: %d patches (%d enabled)"), total_count,
2083 ImGuiTabBarFlags_FittingPolicyScroll)) {
2085 if (ImGui::BeginTabItem(folder.c_str())) {
2088 ImGui::EndTabItem();
2101 ImGui::TextDisabled(tr(
"Select a patch to view details"));
2110#ifdef YAZE_WITH_Z3DK
2128 for (
const auto& range :
z3dk.prohibited_memory_ranges) {
2130 {.start = range.start, .end = range.end, .reason = range.reason});
2138 if (!
z3dk.rom_path.empty()) {
2149 LOG_ERROR(
"Settings",
"Failed to apply patches: %s", status.message());
2151 LOG_INFO(
"Settings",
"Applied %d patches successfully", enabled_count);
2154 LOG_WARN(
"Settings",
"No ROM loaded");
2157 if (ImGui::IsItemHovered()) {
2158 ImGui::SetTooltip(tr(
"Apply all enabled patches to the loaded ROM"));
2165 LOG_ERROR(
"Settings",
"Failed to save patches: %s", status.message());
2178 if (patches.empty()) {
2179 ImGui::TextDisabled(tr(
"No patches in this folder"));
2184 float available_height = std::min(200.0f, patches.size() * 25.0f + 10.0f);
2185 if (ImGui::BeginChild(
"##PatchList", ImVec2(0, available_height),
true)) {
2186 for (
auto* patch : patches) {
2187 ImGui::PushID(patch->filename().c_str());
2189 bool enabled = patch->enabled();
2190 if (ImGui::Checkbox(
"##Enabled", &enabled)) {
2191 patch->set_enabled(enabled);
2198 if (ImGui::Selectable(patch->name().c_str(), is_selected)) {
2231 if (!params.empty()) {
2236 for (
auto& param : params) {
2248 switch (param->
type) {
2252 int value = param->
value;
2253 const char* format = param->
use_decimal ?
"%d" :
"$%X";
2256 ImGui::SetNextItemWidth(100);
2257 if (ImGui::InputInt(
"##Value", &value, 1, 16)) {
2271 if (ImGui::Checkbox(param->
display_name.c_str(), &checked)) {
2279 for (
size_t i = 0; i < param->
choices.size(); ++i) {
2280 bool selected = (param->
value ==
static_cast<int>(i));
2281 if (ImGui::RadioButton(param->
choices[i].c_str(), selected)) {
2282 param->
value =
static_cast<int>(i);
2290 for (
size_t i = 0; i < param->
choices.size(); ++i) {
2291 if (param->
choices[i].empty() || param->
choices[i] ==
"_EMPTY") {
2294 bool bit_set = (param->
value & (1 << i)) != 0;
2295 if (ImGui::Checkbox(param->
choices[i].c_str(), &bit_set)) {
2297 param->
value |= (1 << i);
2299 param->
value &= ~(1 << i);
2309 ImGui::SetNextItemWidth(150);
2310 if (ImGui::InputInt(tr(
"Item ID"), ¶m->
value)) {
2311 param->
value = std::clamp(param->
value, 0, 255);
const std::string & version() const
std::vector< PatchParameter > & mutable_parameters()
const std::string & author() const
const std::string & description() const
const std::string & name() const
const std::vector< FeatureFlag > & feature_flags() const
const MessageLayout & message_layout() const
const std::vector< RoomTagEntry > & room_tags() const
Get all room tags.
const std::string & hack_name() const
bool loaded() const
Check if the manifest has been loaded.
int manifest_version() const
const BuildPipeline & build_pipeline() const
absl::Status ApplyEnabledPatches(Rom *rom)
Apply all enabled patches to a ROM.
absl::Status SaveAllPatches()
Save all patches to their files.
const std::vector< std::string > & folders() const
Get list of patch folder names.
int GetEnabledPatchCount() const
Get count of enabled patches.
std::vector< AsmPatch * > GetPatchesInFolder(const std::string &folder)
Get all patches in a specific folder.
const std::vector< std::unique_ptr< AsmPatch > > & patches() const
Get all loaded patches.
absl::Status LoadPatches(const std::string &patches_dir)
Load all patches from a directory structure.
virtual void SetDependencies(const EditorDependencies &deps)
Manages ImGui DockBuilder layouts for each editor type.
absl::Status ApplyDockTree(const layout_designer::DockTree &tree, ImGuiID dockspace_id)
Apply a DockTree to the given dockspace.
ImGuiID GetMainDockspaceId() const
Get the cached main dockspace ID.
void DrawPerformanceSettings()
std::string editing_card_id_
void SetStatusBar(StatusBar *bar)
void DrawProjectSettings()
void DrawPatchList(const std::string &folder)
void DrawFilesystemSettings()
void SetWindowManager(WorkspaceWindowManager *registry)
void DrawAppearanceSettings()
char shortcut_edit_buffer_[64]
ShortcutManager * shortcut_manager_
void SetDependencies(const EditorDependencies &deps) override
void DrawEditorShortcuts()
void DrawGlobalShortcuts()
void ApplyNamedLayoutToDockspace(const std::string &name)
bool MatchesShortcutFilter(const std::string &text) const
void DrawParameterWidget(core::PatchParameter *param)
void DrawEditorBehavior()
core::AsmPatch * selected_patch_
void DrawGeneralSettings()
void DrawWorkspaceSettings()
core::PatchManager patch_manager_
project::YazeProject * project_
void SetShortcutManager(ShortcutManager *manager)
void SetUserSettings(UserSettings *settings)
std::string selected_folder_
UserSettings * user_settings_
void DrawKeyboardShortcuts()
bool is_editing_shortcut_
std::string workspace_status_message_
std::string shortcut_filter_
void SetProject(project::YazeProject *project)
WorkspaceWindowManager * window_manager_
bool workspace_status_is_error_
void DrawPanelShortcuts()
void DrawAIAgentSettings()
std::vector< Shortcut > GetShortcutsByScope(Shortcut::Scope scope) const
bool UpdateShortcutKeys(const std::string &name, const std::vector< ImGuiKey > &keys)
void SetEnabled(bool enabled)
Enable or disable the status bar.
std::vector< WindowDescriptor > GetWindowsInCategory(size_t session_id, const std::string &category) const
void SetWindowPinned(size_t session_id, const std::string &base_window_id, bool pinned)
bool IsWindowOpen(size_t session_id, const std::string &base_window_id) const
bool OpenWindow(size_t session_id, const std::string &base_window_id)
std::vector< std::string > GetAllCategories(size_t session_id) const
static MotionProfile ClampMotionProfile(int raw_profile)
void SetMotionPreferences(bool reduced_motion, MotionProfile profile)
static ThemeManager & Get()
static std::string ShowOpenFolderDialog()
ShowOpenFolderDialog opens a file dialog and returns the selected folder path. Uses global feature fl...
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_FOLDER_SPECIAL
#define ICON_MD_TEXT_FIELDS
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_EXTENSION
#define ICON_MD_DASHBOARD_CUSTOMIZE
#define ICON_MD_PSYCHOLOGY
#define ICON_MD_HORIZONTAL_RULE
#define ICON_MD_DASHBOARD
#define ICON_MD_SMART_TOY
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
constexpr char kProviderOpenAi[]
constexpr char kProviderOllama[]
constexpr char kProviderLmStudio[]
LayoutManager * layout_manager()
Get the shared LayoutManager instance.
std::vector< uint16_t > DefaultTrackTiles()
std::string ExpandLeadingTilde(const std::string &path)
bool ParseHexToken(const std::string &token, uint16_t *out)
bool AddUniquePath(std::vector< std::string > *paths, const std::string &path)
std::vector< uint16_t > DefaultSwitchTiles()
std::vector< uint16_t > DefaultStopTiles()
std::string BuildHostTagString(const UserSettings::Preferences::AiHost &host)
std::vector< uint16_t > DefaultMinecartSpriteIds()
std::vector< uint16_t > DefaultTrackObjectIds()
absl::StatusOr< DockTree > DockTreeFromJson(const nlohmann::json &j)
std::vector< ImGuiKey > ParseShortcut(const std::string &shortcut)
std::string PrintShortcut(const std::vector< ImGuiKey > &keys)
bool DrawProperty(const char *label, bool *value, const PropertyOptions &opts)
ImVec4 ConvertColorToImVec4(const Color &color)
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
DensityPreset
Typography and spacing density presets.
bool FontPicker(const char *label, int *index)
std::string ComputeRomHash(const uint8_t *data, size_t size)
void SetPreferHmagicSpriteNames(bool prefer)
void SetActiveFontIndex(int index)
Represents a configurable parameter within an ASM patch.
std::vector< std::string > choices
bool warn_branch_outside_bank
std::vector< Z3dkMemoryRange > prohibited_memory_ranges
bool capture_nocash_symbols
std::vector< std::string > include_paths
std::vector< std::pair< std::string, std::string > > defines
std::string std_defines_path
bool warn_unauthorized_hook
std::string std_includes_path
std::string hooks_rom_path
Unified dependency container for all editor types.
project::YazeProject * project
ShortcutManager * shortcut_manager
UserSettings * user_settings
WorkspaceWindowManager * window_manager
std::vector< std::string > project_root_paths
std::unordered_map< std::string, std::string > panel_shortcuts
std::unordered_map< std::string, std::string > named_layouts
std::unordered_map< std::string, std::string > editor_shortcuts
std::vector< AiHost > ai_hosts
std::unordered_map< std::string, std::string > global_shortcuts
bool prefer_hmagic_sprite_names
Comprehensive theme structure for YAZE.
std::vector< uint16_t > track_object_ids
std::vector< uint16_t > minecart_sprite_ids
std::vector< uint16_t > track_stop_tiles
std::vector< uint16_t > track_tiles
std::vector< uint16_t > track_switch_tiles
int backup_keep_daily_days
int backup_retention_count
std::string rom_backup_folder
std::string git_repository
core::HackManifest hack_manifest
void ReloadHackManifest()
std::string hack_manifest_file
WorkspaceSettings workspace_settings
std::string output_folder
DungeonOverlaySettings dungeon_overlay
std::string symbols_filename
Z3dkSettings z3dk_settings