diff --git a/docs/CORE_LIBRARY.md b/docs/CORE_LIBRARY.md index 3d419c6..c9321a4 100644 --- a/docs/CORE_LIBRARY.md +++ b/docs/CORE_LIBRARY.md @@ -91,20 +91,21 @@ The main loop then: caller that waits on `retcomm launch`, such as a Steam shortcut, therefore still waits for the game. -## Romhack bases (2026-10-08) +## Legacy romhack bases (updated 2026-10-14) -An n64lle game can run as the game itself or as exactly one installed romhack, +Players install ROMhacks as standalone games. Neither the desktop title home +nor the controller overlay exposes a Base picker. With n64lle 0.449.0, the +legacy `system.romhack`, `system.startup`, and `system.pif_rom` declarations are +developer-only; ordinary settings require no ROMhack or PIF setup. + +The backend still supports an explicitly configured legacy base so existing +saves and mod compatibility checks remain valid. An n64lle game can run as the +game itself or as exactly one installed romhack, its **base** (n64lle `docs/ROMHACK-PLAN.md`). A romhack is installed beside the game package as `romhacks//`, holding `base.toml`, the patch and a delta package (n64lle `tools/n64romhack.py install`). The core picks the base from its option `system.romhack`; empty means the game itself. -- **Picker.** In Direct and title-app mode the home page shows a **Base** picker - when anything is installed (`src/core/romhacks.cpp` `scan_romhacks`). It writes - the title's `system.romhack` to `options/.ini`, the same file the System - tab writes, so a session gets it like any other option. - - A base the core would refuse is listed but disabled, with the reason as its - tooltip: an id that is not its directory's name, or a missing delta. - **Saves.** A base keeps its own saves, `saves/@`, apart from the game's `saves/` (`base_save_dir`). The exception is a `base.toml` that says `saves = "shared-with-stock"`, which is the hack author's word that the layout is @@ -122,10 +123,11 @@ package as `romhacks//`, holding `base.toml`, the patch and a delta package "Last session" shows the core's `base:` and romhack lines with its `mods:` ones. Checked by `hub_romhack` (ctest): the scan and its refusals, the save dirs, and the -standings, through the Mods page's own manifest reader. The picker and the Files -row were checked by screenshot against the n64lle fixture package with three bases -installed. Clicking an entry in the open picker was not driven: synthetic clicks did -not reach the window, so that interaction is unexercised. +standings, through the Mods page's own manifest reader. The removal passed a +Windows Release build and the `hub_core_settings`, `hub_romhack`, and `hub_title` +tests. The rebuilt n64lle core's `--describe` output marks all three controls +`restart,netplay,developer`; existing settings grouping excludes those declarations +from ordinary sections. No live game was interrupted for this cleanup. ## How it was checked (2026-09-25) diff --git a/src/hub/hub_main.cpp b/src/hub/hub_main.cpp index a4875e0..e3dc586 100644 --- a/src/hub/hub_main.cpp +++ b/src/hub/hub_main.cpp @@ -16498,8 +16498,8 @@ struct DirectHome { bool focus_pending = true; retcomm::ModScanResult mods; - // Romhack bases installed beside the package (romhacks.hpp); the Base - // picker lists them and writes the title's system.romhack. + // Legacy/developer romhack selections still need their save and mod + // compatibility metadata; ordinary titles are installed independently. std::vector romhacks; std::string status; // the last thing that happened bool status_bad = false; @@ -17303,53 +17303,6 @@ DirectAction draw_direct_actions(DirectHome& h, HubModel& hub, const Theme& th) } ImGui::Dummy(ImVec2(0, 6)); - // THE BASE (n64lle docs/ROMHACK-PLAN.md R5): the game itself, or one - // installed romhack. It is the title's system.romhack core option, so a - // session, its saves and its netplay match key all follow it. - if (!h.romhacks.empty()) { - const std::string cur = direct_base_id(h, hub); - const retcomm::InstalledRomhack* sel = retcomm::find_romhack(h.romhacks, cur); - const std::string preview = - cur.empty() ? std::string("The game") : (sel ? sel->title : cur + " (not installed)"); - ImGui::TextColored(th.text_muted, "Base"); - ImGui::SetNextItemWidth(w); - if (ImGui::BeginCombo("##base", preview.c_str())) { - auto choose = [&](const std::string& id) { - auto title = retcomm::hub::load_core_options(hub.paths.data_dir, h.platform, - h.title_key); - if (id.empty()) title.erase(retcomm::kRomhackOption); - else title[retcomm::kRomhackOption] = id; - std::string err; - if (!retcomm::hub::save_core_options(hub.paths.data_dir, h.platform, h.title_key, - title, &err)) { - h.status = "Cannot save the base: " + err; - h.status_bad = true; - } - }; - if (ImGui::Selectable("The game", cur.empty())) choose({}); - for (const retcomm::InstalledRomhack& r : h.romhacks) { - const std::string label = r.title + (r.version.empty() ? "" : " " + r.version) + - (r.error.empty() ? "" : " (unusable)"); - ImGui::BeginDisabled(!r.error.empty()); - if (ImGui::Selectable((label + "##" + r.id).c_str(), r.id == cur)) choose(r.id); - ImGui::EndDisabled(); - if (!r.error.empty() && - ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) - ImGui::SetTooltip("%s", r.error.c_str()); - } - ImGui::EndCombo(); - } - if (sel) { - ImGui::PushStyleColor(ImGuiCol_Text, th.text_muted); - ImGui::TextWrapped("%s", sel->saves_shared - ? "Shares the game's saves (its author says the save " - "layout is unchanged)." - : "Keeps its own saves, apart from the game's."); - ImGui::PopStyleColor(); - } - ImGui::Dummy(ImVec2(0, 4)); - } - if (ImGui::Button("Mods", ImVec2(w, 0))) open_direct_page(h, hub, DirectPage::Mods); ImGui::BeginDisabled(!have_rom); if (ImGui::Button("Netplay", ImVec2(w, 0))) open_direct_page(h, hub, DirectPage::Netplay); @@ -17903,39 +17856,6 @@ DirectAction draw_direct_home_overlay(DirectHome& h, HubModel& hub, const Theme& if (h.d.title_mode && have_rom && ov::setting_row(m, "rom", "ROM", "Choose another...", nullptr)) begin_title_rom_pick(h); - // THE BASE: the game itself, or one installed romhack (it is the title's system.romhack - // option, so a session, its saves and its netplay match key all follow it). - if (!h.romhacks.empty()) { - const std::string cur = direct_base_id(h, hub); - const retcomm::InstalledRomhack* sel = retcomm::find_romhack(h.romhacks, cur); - const std::string preview = cur.empty() ? std::string("The game") : (sel ? sel->title : cur + " (not installed)"); - if (ov::setting_row(m, "base", "Base", preview, - sel ? (sel->saves_shared ? "Shares the game's saves (its author says the save layout is " - "unchanged)." - : "Keeps its own saves, apart from the game's.") - : nullptr)) - ov::open_picker("##base"); - std::vector choices{{"The game", ""}}; - int current = cur.empty() ? 0 : -1; - for (size_t i = 0; i < h.romhacks.size(); ++i) { - const retcomm::InstalledRomhack& r = h.romhacks[i]; - choices.push_back({r.title + (r.version.empty() ? "" : " " + r.version), - r.error.empty() ? std::string() : "Unusable: " + r.error}); - if (r.id == cur) current = static_cast(i) + 1; - } - const int pick = ov::picker(m, "##base", "Base", choices, current); - if (pick >= 0 && (pick == 0 || h.romhacks[static_cast(pick - 1)].error.empty())) { - auto title = retcomm::hub::load_core_options(hub.paths.data_dir, h.platform, h.title_key); - if (pick == 0) title.erase(retcomm::kRomhackOption); - else title[retcomm::kRomhackOption] = h.romhacks[static_cast(pick - 1)].id; - std::string err; - if (!retcomm::hub::save_core_options(hub.paths.data_dir, h.platform, h.title_key, title, &err)) { - h.status = "Cannot save the base: " + err; - h.status_bad = true; - } - } - } - if (ov::setting_row(m, "settings", "Settings", "Core options, video, accessories")) open_direct_core_settings(h, hub, ""); if (ov::setting_row(m, "controls", "Controls", "Players, buttons")) open_direct_core_settings(h, hub, "Controls"); if (ov::setting_row(m, "mods", "Mods", "")) open_direct_page(h, hub, DirectPage::Mods);