Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions docs/CORE_LIBRARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<id>/`, 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/<key>.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/<key>@<id>`, apart from the game's
`saves/<key>` (`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
Expand All @@ -122,10 +123,11 @@ package as `romhacks/<id>/`, 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)

Expand Down
84 changes: 2 additions & 82 deletions src/hub/hub_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<retcomm::InstalledRomhack> romhacks;
std::string status; // the last thing that happened
bool status_bad = false;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<ov::PickerChoice> 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<int>(i) + 1;
}
const int pick = ov::picker(m, "##base", "Base", choices, current);
if (pick >= 0 && (pick == 0 || h.romhacks[static_cast<size_t>(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<size_t>(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);
Expand Down
Loading