yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
rom_load_options_dialog.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <filesystem>
6#include <system_error>
7#include <unordered_map>
8
9#include "absl/strings/numbers.h"
10#include "absl/strings/str_cat.h"
11#include "absl/strings/str_format.h"
12#include "absl/strings/str_split.h"
13#include "absl/strings/string_view.h"
14#include "app/gui/core/icons.h"
18#include "core/features.h"
19#include "imgui/imgui.h"
20#include "rom/rom.h"
21
22namespace yaze {
23namespace editor {
24
25// Preset definitions
26const char* RomLoadOptionsDialog::kPresetNames[kNumPresets] = {
27 "Vanilla ROM Hack", "ZSCustomOverworld v2",
28 "ZSCustomOverworld v3 (Recommended)", "Randomizer Compatible"};
29
30const char* RomLoadOptionsDialog::kPresetDescriptions[kNumPresets] = {
31 "Standard ROM editing without custom ASM. Limited to vanilla features.",
32 "Basic overworld expansion: custom BG colors, main palettes, parent "
33 "system.",
34 "Full overworld expansion: wide/tall areas, animated GFX, overlays, all "
35 "features.",
36 "Compatible with ALttP Randomizer. Minimal custom features."};
37
38namespace {
39
41
42constexpr int kRememberedChoiceVersion = 1;
43
45 const char* key;
47};
48
49// The flag-backed fields, in encoded order. Encode and decode both walk this
50// table. Decode requires every key, so adding a field here makes older
51// remembered choices show the dialog again instead of guessing a value.
53 {"custom", &LoadOptions::enable_custom_overworld},
54 {"tail", &LoadOptions::enable_tail_expansion},
55 {"ow_maps", &LoadOptions::save_overworld_maps},
56 {"ow_entrances", &LoadOptions::save_overworld_entrances},
57 {"ow_exits", &LoadOptions::save_overworld_exits},
58 {"ow_items", &LoadOptions::save_overworld_items},
59 {"ow_sprites", &LoadOptions::save_overworld_sprites},
60 {"dungeon_maps", &LoadOptions::save_dungeon_maps},
61 {"palettes", &LoadOptions::save_all_palettes},
62 {"gfx_groups", &LoadOptions::save_gfx_groups},
63};
64
65} // namespace
66
75
77 if (rom) {
78 Open(rom, rom->filename(), false);
79 }
80}
81
82void RomLoadOptionsDialog::Open(Rom* rom, bool suggest_project_creation) {
83 if (rom) {
84 Open(rom, rom->filename(), suggest_project_creation);
85 }
86}
87
88void RomLoadOptionsDialog::Draw(bool* p_open) {
89 Show(p_open);
90}
91
92void RomLoadOptionsDialog::Open(Rom* rom, const std::string& rom_filename) {
93 Open(rom, rom_filename, false);
94}
95
96void RomLoadOptionsDialog::Open(Rom* rom, const std::string& rom_filename,
97 bool suggest_project_creation) {
98 rom_ = rom;
100 is_open_ = true;
101 confirmed_ = false;
102 show_advanced_ = false;
103 reviewing_ = false;
104 // The open ROM's overworld loaded with these flags.
105 const auto& loaded = core::FeatureFlags::get().overworld;
107 loaded_tail_expansion_ = loaded.kEnableSpecialWorldExpansion;
108
109 // Reset options for each open so stale state from previous ROM loads does
110 // not carry over.
117 options_.create_project = suggest_project_creation;
118
119 // Detect ROM version
120 if (rom_ && rom_->is_loaded()) {
123 } else {
126 }
127
128 // Set default project name from ROM filename
129 size_t last_slash = rom_filename_.find_last_of("/\\");
130 size_t last_dot = rom_filename_.find_last_of('.');
131 std::string base_name;
132 if (last_slash != std::string::npos) {
133 base_name = rom_filename_.substr(last_slash + 1);
134 } else {
135 base_name = rom_filename_;
136 }
137 if (last_dot != std::string::npos && last_dot > last_slash) {
138 base_name = base_name.substr(0, base_name.find_last_of('.'));
139 }
140
141 snprintf(project_name_buffer_, sizeof(project_name_buffer_), "%s_project",
142 base_name.c_str());
143
144 // Auto-select preset based on detected version. A vanilla ROM gets the v3
145 // preset's flags; the ROM itself changes only through the Overworld
146 // toolbar's Upgrade button.
147 switch (detected_version_) {
150 break;
153 selected_preset_index_ = 1; // Keep v2 features
154 break;
156 selected_preset_index_ = 2; // Full v3 features
157 break;
158 }
159
161 options_.create_project = suggest_project_creation;
162}
163
165 Rom* rom, const core::FeatureFlags::Flags& active,
166 const std::string& preset_name, bool remembered) {
167 if (!rom) {
168 return;
169 }
170 Open(rom, rom->filename(), false);
171 reviewing_ = true;
172 for (int i = 0; i < kNumPresets; ++i) {
173 if (preset_name == kPresetNames[i]) {
175 break;
176 }
177 }
178
179 // Show the flags Save uses now, not the preset Open() picked for the ROM
180 // version. The two can differ after a remembered choice or a menu toggle.
181 const LoadOptions current = OptionsFromFeatureFlags(active);
182 for (const auto& flag : kRememberedFlags) {
183 options_.*flag.member = current.*flag.member;
184 }
187 options_.create_project = false;
188 options_.remember_for_rom = remembered;
189 show_advanced_ = true;
190}
191
192bool RomLoadOptionsDialog::Show(bool* p_open) {
193 if (!is_open_)
194 return false;
195
196 ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver);
197 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
198 ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
199
200 ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse;
201
202 bool result = false;
203 if (ImGui::Begin(
204 absl::StrFormat("%s ROM Load Options", ICON_MD_SETTINGS).c_str(),
205 &is_open_, flags)) {
207 if (reviewing_) {
208 ImGui::Spacing();
209 gui::StyleColorGuard note_color(ImGuiCol_Text, gui::GetInfoColor());
210 ImGui::TextWrapped(
211 "%s", tr("These are the options this session uses now. Apply "
212 "changes what the next save writes."));
213 }
214 ImGui::Separator();
215
217 ImGui::Separator();
218
220
221 if (show_advanced_) {
222 ImGui::Separator();
224 }
225
226 // The ROM already has a session; a project comes from Save Project As.
227 if (!reviewing_) {
228 ImGui::Separator();
230 }
231
232 ImGui::Separator();
234
235 result = confirmed_;
236 }
237 ImGui::End();
238
239 // Cancel and the title-bar close button both end here, once: a closed
240 // dialog returns before drawing on later frames.
243 }
244
245 if (p_open) {
246 *p_open = is_open_;
247 }
248
249 return result;
250}
251
253 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
254
255 ImGui::Text(tr("%s ROM Information"), ICON_MD_INFO);
256 ImGui::Spacing();
257
258 // ROM filename
259 ImGui::TextColored(gui::ConvertColorToImVec4(theme.text_secondary),
260 tr("File: %s"), rom_filename_.c_str());
261
262 // Detected version with color coding
263 const char* version_name =
265
266 ImVec4 version_color;
267 switch (detected_version_) {
269 version_color = gui::GetWarningColor(); // Vanilla ROM needs an upgrade.
270 break;
273 version_color = gui::GetInfoColor(); // Partial feature support.
274 break;
276 version_color = gui::GetSuccessColor(); // Full feature support.
277 break;
278 default:
279 version_color = gui::GetTextSecondaryVec4();
280 }
281
282 ImGui::TextColored(version_color, tr("%s Detected: %s"), ICON_MD_VERIFIED,
283 version_name);
284
285 // The upgrade lives in the Overworld toolbar; this dialog only sets flags.
287 gui::StyleColorGuard hint_color(ImGuiCol_Text, gui::GetTextSecondaryVec4());
288 ImGui::TextWrapped(tr("%s To apply ZSCustomOverworld, use Upgrade in the "
289 "Overworld toolbar. This dialog never changes the "
290 "ROM."),
292 }
293}
294
296 ImGui::Text(tr("%s Overworld Loading"), ICON_MD_AUTO_FIX_HIGH);
297 ImGui::Spacing();
298
299 ImGui::Checkbox(tr("Enable custom overworld features in editor"),
301 if (ImGui::IsItemHovered()) {
302 ImGui::SetTooltip(
303 "%s", tr("On a vanilla ROM, gives each area the ZSCustomOverworld "
304 "fields (main palette, mosaic, animated GFX, overlay),\n"
305 "filled with the values the vanilla game uses.\n"
306 "A ZSCustomOverworld ROM always loads its own values, so "
307 "this has no effect there."));
308 }
309
310 ImGui::Checkbox(tr("Enable special world tail (0xA0-0xBF)"),
312 if (ImGui::IsItemHovered()) {
313 ImGui::SetTooltip(
314 "%s", tr("Builds special world maps 0xA0-0xBF when the ROM has the "
315 "TailMapExpansion patch.\n"
316 "Without the patch, those maps stay blank."));
317 }
318
319 // The open ROM's overworld already loaded, so a change waits for a reopen.
321 gui::StyleColorGuard note_color(ImGuiCol_Text, gui::GetWarningColor());
322 ImGui::TextWrapped(tr("%s The open overworld loaded with different "
323 "choices. Reopen the ROM to apply these."),
325 } else {
326 gui::StyleColorGuard note_color(ImGuiCol_Text, gui::GetTextSecondaryVec4());
327 ImGui::TextWrapped(tr("%s Both are read when the overworld loads. Reopen "
328 "the ROM to apply a change."),
330 }
331}
332
334 ImGui::Text(tr("%s Feature Presets"), ICON_MD_TUNE);
335 ImGui::Spacing();
336
337 // Preset selection combo
338 if (ImGui::BeginCombo("##PresetCombo",
340 for (int i = 0; i < kNumPresets; i++) {
341 bool is_selected = (selected_preset_index_ == i);
342 if (ImGui::Selectable(kPresetNames[i], is_selected)) {
345 }
346
347 if (ImGui::IsItemHovered()) {
348 ImGui::SetTooltip("%s", kPresetDescriptions[i]);
349 }
350
351 if (is_selected) {
352 ImGui::SetItemDefaultFocus();
353 }
354 }
355 ImGui::EndCombo();
356 }
357
358 // Show preset description
359 ImGui::TextColored(gui::GetTextSecondaryVec4(), "%s",
361
362 // Advanced toggle
363 ImGui::Spacing();
364 if (ImGui::Button(show_advanced_ ? tr("Hide Advanced Options")
365 : tr("Show Advanced Options"))) {
367 }
368}
369
371 ImGui::Text(tr("%s Feature Flags"), ICON_MD_FLAG);
372 ImGui::Spacing();
373
374 // Overworld flags
375 if (ImGui::TreeNodeEx("Overworld", ImGuiTreeNodeFlags_DefaultOpen)) {
376 ImGui::Checkbox(tr("Save overworld maps"), &options_.save_overworld_maps);
377 ImGui::Checkbox(tr("Save entrances"), &options_.save_overworld_entrances);
378 ImGui::Checkbox(tr("Save exits"), &options_.save_overworld_exits);
379 ImGui::Checkbox(tr("Save items"), &options_.save_overworld_items);
380 ImGui::Checkbox(tr("Save sprites"), &options_.save_overworld_sprites);
381 ImGui::TreePop();
382 }
383
384 // Dungeon flags
385 if (ImGui::TreeNodeEx("Dungeon")) {
386 ImGui::Checkbox(tr("Save dungeon maps"), &options_.save_dungeon_maps);
387 ImGui::TreePop();
388 }
389
390 // Graphics flags
391 if (ImGui::TreeNodeEx("Graphics")) {
392 ImGui::Checkbox(tr("Save all palettes"), &options_.save_all_palettes);
393 ImGui::Checkbox(tr("Save GFX groups"), &options_.save_gfx_groups);
394 ImGui::TreePop();
395 }
396}
397
399 ImGui::Text(tr("%s Project Options"), ICON_MD_FOLDER);
400 ImGui::Spacing();
401
402 ImGui::Checkbox(tr("Create associated project file"),
404
406 ImGui::Indent();
407
408 ImGui::Text(tr("Project Name:"));
409 ImGui::SetNextItemWidth(-1);
410 ImGui::InputText("##ProjectName", project_name_buffer_,
411 sizeof(project_name_buffer_));
413
414 ImGui::TextColored(
416 tr("Project file stores settings, labels, and preferences."));
417
418 ImGui::Unindent();
419 }
420}
421
423 ImGui::Checkbox(tr("Remember for this ROM file"), &options_.remember_for_rom);
424 if (ImGui::IsItemHovered()) {
425 ImGui::SetTooltip(
426 "%s",
427 tr("Skip this dialog the next time this file opens.\n"
428 "Change it later with ROM Load Options… in the command palette.\n"
429 "Forgotten when the ROM's ZSCustomOverworld version changes."));
430 }
431 ImGui::Spacing();
432
433 const float button_width = 120.0f;
434 const float spacing = 10.0f;
435 float total_width = button_width * 2 + spacing;
436
437 // Center buttons
438 float avail = ImGui::GetContentRegionAvail().x;
439 ImGui::SetCursorPosX((avail - total_width) * 0.5f + ImGui::GetCursorPosX());
440
441 // Cancel button
442 if (ImGui::Button(tr("Cancel"), ImVec2(button_width, 0))) {
443 is_open_ = false;
444 confirmed_ = false;
445 }
446
447 ImGui::SameLine(0, spacing);
448
449 // Confirm button with accent color
450 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
451 ImVec4 accent = gui::ConvertColorToImVec4(theme.accent);
452
453 {
454 gui::StyleColorGuard btn_guard(
455 {{ImGuiCol_Button, accent},
456 {ImGuiCol_ButtonHovered,
457 ImVec4(accent.x * 1.1f, accent.y * 1.1f, accent.z * 1.1f, accent.w)},
458 {ImGuiCol_ButtonActive, ImVec4(accent.x * 0.9f, accent.y * 0.9f,
459 accent.z * 0.9f, accent.w)}});
460
461 // StrFormat needs a literal format, so pick the whole string.
462 const std::string confirm_label =
463 reviewing_ ? absl::StrFormat("%s Apply", ICON_MD_CHECK)
464 : absl::StrFormat("%s Continue", ICON_MD_CHECK);
465 if (ImGui::Button(confirm_label.c_str(), ImVec2(button_width, 0))) {
466 if (apply_guard_ && !apply_guard_()) {
467 Close();
468 return;
469 }
470 // Apply options
473
474 confirmed_ = true;
475 is_open_ = false;
476
477 // Call confirm callback
478 if (confirm_callback_) {
480 }
481 }
482 }
483}
484
485void RomLoadOptionsDialog::ApplyPreset(const std::string& preset_name) {
486 if (preset_name == "Vanilla ROM Hack") {
496 } else if (preset_name == "ZSCustomOverworld v2") {
506 } else if (preset_name == "ZSCustomOverworld v3 (Recommended)") {
516 } else if (preset_name == "Randomizer Compatible") {
526 }
527}
528
532
546
562
564 const LoadOptions& options, int zso_version_byte) {
565 std::string encoded =
566 absl::StrCat("v=", kRememberedChoiceVersion, ";zso=", zso_version_byte);
567 for (const auto& flag : kRememberedFlags) {
568 absl::StrAppend(&encoded, ";", flag.key, "=",
569 options.*flag.member ? "1" : "0");
570 }
571
572 // ';' separates fields, and a tab or newline would split the legacy INI
573 // line, so neither may reach the stored preset name.
574 std::string preset = options.selected_preset;
575 preset.erase(std::remove_if(preset.begin(), preset.end(),
576 [](char c) {
577 return c == ';' || c == '\t' || c == '\r' ||
578 c == '\n';
579 }),
580 preset.end());
581 if (!preset.empty()) {
582 absl::StrAppend(&encoded, ";preset=", preset);
583 }
584 return encoded;
585}
586
587std::optional<RomLoadOptionsDialog::RememberedChoice>
589 const auto as_absl_view = [](std::string_view view) {
590 return absl::string_view(view.data(), view.size());
591 };
592 std::unordered_map<std::string_view, std::string_view> fields;
593 for (absl::string_view absl_part :
594 absl::StrSplit(as_absl_view(encoded), ';', absl::SkipEmpty())) {
595 const std::string_view part(absl_part.data(), absl_part.size());
596 const size_t eq = part.find('=');
597 if (eq == std::string_view::npos) {
598 continue;
599 }
600 fields[part.substr(0, eq)] = part.substr(eq + 1);
601 }
602
603 int version = 0;
604 auto it = fields.find("v");
605 if (it == fields.end() ||
606 !absl::SimpleAtoi(as_absl_view(it->second), &version) ||
607 version != kRememberedChoiceVersion) {
608 return std::nullopt;
609 }
610
611 RememberedChoice choice;
612 it = fields.find("zso");
613 if (it == fields.end() ||
614 !absl::SimpleAtoi(as_absl_view(it->second), &choice.zso_version_byte) ||
615 choice.zso_version_byte < 0 || choice.zso_version_byte > 0xFF) {
616 return std::nullopt;
617 }
618
619 for (const auto& flag : kRememberedFlags) {
620 it = fields.find(flag.key);
621 if (it == fields.end() || (it->second != "0" && it->second != "1")) {
622 return std::nullopt;
623 }
624 choice.options.*flag.member = it->second == "1";
625 }
626
627 it = fields.find("preset");
628 if (it != fields.end()) {
629 choice.options.selected_preset = std::string(it->second);
630 }
631 return choice;
632}
633
634std::string RomLoadOptionsDialog::RomChoiceKey(const std::string& rom_path) {
635 if (rom_path.empty()) {
636 return {};
637 }
638 std::filesystem::path path(rom_path);
639 if (path.is_relative()) {
640 std::error_code ec;
641 auto absolute = std::filesystem::absolute(path, ec);
642 if (!ec) {
643 path = std::move(absolute);
644 }
645 }
646 return path.lexically_normal().string();
647}
648
653
654} // namespace editor
655} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:52
auto filename() const
Definition rom.h:219
bool is_loaded() const
Definition rom.h:199
static Flags & get()
Definition features.h:122
std::function< void(const LoadOptions &) confirm_callback_)
void ApplyPreset(const std::string &preset_name)
void Open(Rom *rom, const std::string &rom_filename)
Open the dialog after ROM detection.
static const char * kPresetNames[kNumPresets]
void OpenForReview(Rom *rom, const core::FeatureFlags::Flags &active, const std::string &preset_name, bool remembered)
Reopen the dialog for a ROM that is already loaded.
static LoadOptions OptionsFromFeatureFlags(const core::FeatureFlags::Flags &flags)
static std::optional< RememberedChoice > DecodeRememberedChoice(std::string_view encoded)
static std::string RomChoiceKey(const std::string &rom_path)
bool Show(bool *p_open)
Show the dialog.
static void ApplyToFeatureFlags(const LoadOptions &options, core::FeatureFlags::Flags &flags)
static std::string EncodeRememberedChoice(const LoadOptions &options, int zso_version_byte)
static const char * kPresetDescriptions[kNumPresets]
void Draw(bool *p_open)
Draw the dialog (wrapper around Show)
RAII guard for ImGui style colors.
Definition style_guard.h:29
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
static OverworldVersion GetVersion(const Rom &rom)
Detect ROM version from ASM marker byte.
static uint8_t GetAsmVersion(const Rom &rom)
Get raw ASM version byte from ROM.
static const char * GetVersionName(OverworldVersion version)
Get human-readable version name for display/logging.
#define ICON_MD_SETTINGS
Definition icons.h:1699
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_UPGRADE
Definition icons.h:2047
#define ICON_MD_CHECK
Definition icons.h:397
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_VERIFIED
Definition icons.h:2055
#define ICON_MD_AUTO_FIX_HIGH
Definition icons.h:218
#define ICON_MD_FLAG
Definition icons.h:784
#define ICON_MD_FOLDER
Definition icons.h:809
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:140
ImVec4 GetSuccessColor()
Definition ui_helpers.cc:49
ImVec4 GetTextSecondaryVec4()
ImVec4 GetWarningColor()
Definition ui_helpers.cc:54
ImVec4 GetInfoColor()
Definition ui_helpers.cc:64
@ kZSCustomV2
Parent system, BG colors, main palettes.
@ kZSCustomV1
Basic features, expanded pointers.
@ kVanilla
0xFF in ROM, no ZScream ASM applied
@ kZSCustomV3
Area enum, wide/tall areas, all features.
struct yaze::core::FeatureFlags::Flags::Overworld overworld