Say "after restart" in the menu only while a restart would change something, and count the waiting settings on Restart.

A setting read at the start showed "after restart" under its value always. The values the game started with are now
kept (cfg.c start_values), a row shows the note only while its value differs from them, and the Restart button counts
the settings that wait, the upscaler, frame generation and HDR among them. A value the environment sets never waits.
--probe-cfg checks the count.

From pull request #377 by mooki000. Three conflicts in the menu resolved against the frame generation rows that came
in since: the upscaling row keeps its description by what can load, and the frame generation restart test knows both
ways NVIDIA's runs.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-10 09:14:23 -05:00
parent a1fcbb0b07
commit 1f2b320736
11 changed files with 126 additions and 22 deletions

View File

@@ -153,7 +153,8 @@ beside the game.
a DLSS multiplier of 2x, or 3x and 4x on RTX 50 cards; FSR 3.1 on any card where it loads; it needs DLSS or FSR 3.1
upscaling on, and the frame limit then counts the frames on screen; with no limit chosen it is the display's refresh
rate), HDR (the game's picture tone mapped to your display, with its own grading rows, or Native, the game's own HDR; turn HDR on in Windows first) and the
resolution. Those marked "after restart" take effect when the game starts again.
resolution. A setting that takes effect only when the game starts again says "after restart" once you change it,
until you set it back, and the Restart button at the bottom then counts the settings waiting for it.
- **Controls** in the menu has Button prompts: the game's own, or Xbox, PlayStation, Steam Deck or keyboard and mouse ones when
the build drew them (a kit with `prompt-art`). It takes effect after a restart. The keyboard and mouse prompts show
the keys `nx2navi.ini` binds when the build runs: after rebinding keys, run

View File

@@ -610,6 +610,42 @@ int cfg_int(const char *name) {
int cfg_bool(const char *name) { return strcmp(cfg_value(name), "0") != 0; }
// ---- the restart-only settings' values at start (cfg_init), which the running game keeps using whatever the menu sets
// later: cfg_pending compares against them, so the menu says "after restart" only while a restart would change
// something. One per row of metas, NULL for a live one.
static const char *at_start[sizeof metas / sizeof *metas];
static const char *meta_value(size_t i) {
char name[80];
snprintf(name, sizeof name, PREFIX "%s", metas[i].key);
return cfg_value(name);
}
static void start_values(void) {
for (size_t i = 0; i < sizeof metas / sizeof *metas; i++)
if (!metas[i].live) at_start[i] = strdup(meta_value(i)); // a copy: cfg_load (the probes) frees what it read
}
// A value the environment sets never waits: the menu cannot change it and a restart keeps the environment. The command
// line's --monitor and --audio-device arrive that way after the start values are taken (main.c).
static int pending_at(size_t i) {
char name[80];
if (!at_start[i]) return 0;
snprintf(name, sizeof name, PREFIX "%s", metas[i].key);
return !cfg_from_env(name) && strcmp(at_start[i], cfg_value(name)) != 0;
}
int cfg_pending(const char *key) {
const CfgMeta *m = cfg_meta(key);
return m ? pending_at((size_t)(m - metas)) : 0;
}
int cfg_pending_count(void) {
int n = 0;
for (size_t i = 0; i < sizeof metas / sizeof *metas; i++) n += pending_at(i);
return n;
}
// ---- cfg_save: the file as lines, each with its line ending
typedef struct { const char *s; size_t n; } Line; // n includes the '\n' (and '\r') when present
@@ -879,4 +915,5 @@ void cfg_init(void) {
if (profile_of())
rt_log("cfg: device profile Steam Deck: defaults for the keys nx2navi.ini does not set (NX2NAVI_DEVICE_PROFILE=desktop "
"turns it off)");
start_values();
}

View File

@@ -6,7 +6,7 @@
// is kept). A name without the NX2NAVI_ prefix goes to getenv unchanged. Safe from any thread once cfg_init has returned.
const char *cfg_get(const char *name);
// Reads nx2navi.ini once, from mem_init after the address space is claimed and before any setting is read. Logs the file, its key count, unknown keys and environment overrides.
// Reads nx2navi.ini once, from mem_init after the address space is claimed and before any setting is read. Logs the file, its key count, unknown keys and environment overrides. Keeps the restart-only settings' values (cfg_pending).
void cfg_init(void);
// Replaces the loaded keys with those of the file at path (tests). 1 when the file was read, 0 when missing.
@@ -55,6 +55,11 @@ const CfgMeta *cfg_meta_at(int i);
// What key reads when the player has not set it: the device profile's value (the Steam Deck's), else the row's default;
// NULL for a key without a row. The menu's "default" mark follows it.
const char *cfg_default(const char *key);
// A restart-only setting (live 0) whose value differs from the one cfg_init read at start, which the running game still
// uses: 1 while a restart would change it, 0 otherwise and for a live key or one without a row (the menu's "after
// restart" note). cfg_pending_count: how many restart-only settings differ.
int cfg_pending(const char *key);
int cfg_pending_count(void);
// The setting's value for consumers: the environment or nx2navi.ini value, else the CfgMeta default. name has the NX2NAVI_
// prefix, like cfg_get; a name without a row is a bug (rt_fatal). A row with an empty default has no fixed value: its

View File

@@ -1,4 +1,5 @@
// --probe-cfg: nx2navi.ini parsing, the environment override and a missing file (cfg.c). Needs no game files.
// --probe-cfg: nx2navi.ini parsing, the environment override, a missing file and the settings waiting for a restart
// (cfg.c). Needs no game files.
#include <math.h>
#include <stdint.h>
#include <stdio.h>
@@ -17,6 +18,26 @@ static int fails;
#define EQ(a, b) ((a) && !strcmp((a), (b)))
int probe_cfg(void) {
// cfg_pending, before this probe's files replace the settings cfg_init read: a restart-only setting counts while its
// value differs from the one at start, a live one never
if (!cfg_from_env("NX2NAVI_MICROPHONE") && !cfg_from_env("NX2NAVI_VOLUME")) {
const char *mic = cfg_value("NX2NAVI_MICROPHONE");
int base = cfg_pending_count();
CHECK(!cfg_pending("NX2NAVI_MICROPHONE"), "pending: unchanged since the start");
cfg_set("NX2NAVI_MICROPHONE", strcmp(mic, "0") ? "0" : "1");
CHECK(cfg_pending("NX2NAVI_MICROPHONE") && cfg_pending_count() == base + 1, "pending: a restart-only setting changed");
cfg_set("NX2NAVI_MICROPHONE", mic);
CHECK(!cfg_pending("NX2NAVI_MICROPHONE") && cfg_pending_count() == base, "pending: set back to its start value");
cfg_set("NX2NAVI_VOLUME", "7");
CHECK(!cfg_pending("NX2NAVI_VOLUME") && cfg_pending_count() == base, "pending: a live setting never counts");
// a value the environment sets after the start (the command line's --monitor arrives that way) never waits:
// no row can change it and a restart keeps it
if (!cfg_from_env("NX2NAVI_MONITOR")) {
cfg_setenv("NX2NAVI_MONITOR", "2");
CHECK(!cfg_pending("NX2NAVI_MONITOR") && cfg_pending_count() == base, "pending: set by the environment after the start");
cfg_setenv("NX2NAVI_MONITOR", NULL);
}
}
char path[1024];
if (!plat_exe_path(path, sizeof path)) return 1;
char *cut = plat_last_sep(path);

View File

@@ -597,17 +597,18 @@ int main(int argc, char **argv) {
if (++i == argc) return usage(argv[0]);
*path = argv[i];
} else if (!strcmp(argv[i], "--audio-device")) {
// Same approach as every setting: NX2NAVI_AUDIO_DEVICE is read from the environment first (cfg.c raw_get), and
// nothing has read it yet because the audio renderer opens in rt_boot. An empty value would remove the variable
// (plat_setenv) and hand the choice back to nx2navi.ini, so "default" is the spelling for the system default.
// Same approach as every setting: NX2NAVI_AUDIO_DEVICE is read from the environment first (cfg.c raw_get).
// cfg_init has read the settings by now, so the variable goes in through cfg_setenv, which makes the lookups
// read again. An empty value would remove the variable (plat_setenv) and hand the choice back to
// nx2navi.ini, so "default" is the spelling for the system default.
if (++i == argc || !*argv[i]) return usage(argv[0]);
plat_setenv("NX2NAVI_AUDIO_DEVICE", argv[i]);
cfg_setenv("NX2NAVI_AUDIO_DEVICE", argv[i]);
} else if (!strcmp(argv[i], "--monitor")) {
// The settings read NX2NAVI_MONITOR from the environment first (cfg.c raw_get), and nothing has read it yet:
// the window opens in rt_boot. An empty value would remove the variable (plat_setenv) and hand the choice back
// to nx2navi.ini, so "auto" is the spelling for "no monitor choice" on the command line.
// The settings read NX2NAVI_MONITOR from the environment first (cfg.c raw_get); cfg_setenv as above. An empty
// value would remove the variable (plat_setenv) and hand the choice back to nx2navi.ini, so "auto" is the
// spelling for "no monitor choice" on the command line.
if (++i == argc || !*argv[i]) return usage(argv[0]);
plat_setenv("NX2NAVI_MONITOR", argv[i]);
cfg_setenv("NX2NAVI_MONITOR", argv[i]);
} else if (!strcmp(argv[i], "--seconds")) {
char *end;
if (++i == argc) return usage(argv[0]);

View File

@@ -822,6 +822,10 @@ static void upscaling_name(char *out, size_t size) {
else snprintf(out, size, mode ? "DLSS %s" : "NVIDIA %s", menu_text(dlss[mode]));
}
// The Upscaling row's NVIDIA DLSS waits for a restart (vk_dlaa_needs_restart), where DLSS is offered: the row's note and
// the Restart button's count (restart_pending).
static bool upscaling_restart(void) { return dlss_offered() && vk_dlaa_needs_restart(); }
static void row_upscaling(void) {
static const char *const modes[] = {"dlaa", "quality", "balanced", "performance", "ultra_performance"};
static const UiTextId mode_names[] = {UI_MENU_DLSS_DLAA, UI_MENU_DLSS_QUALITY, UI_MENU_DLSS_BALANCED,
@@ -867,7 +871,7 @@ static void row_upscaling(void) {
// FSR 3.1 still set where it cannot load: nothing upscales, so the row says so under its value
const char *note = nullptr;
if (!fsr && now.kind == FSR3) note = menu_text(UI_MENU_FSR3_OFF);
else if (dlss && vk_dlaa_needs_restart()) note = menu_text(UI_MENU_AFTER_RESTART);
else if (upscaling_restart()) note = menu_text(UI_MENU_AFTER_RESTART);
int nv = opt_choice_index("NX2NAVI_UPSCALER", UI_MENU_UPSCALING, names, n, cur, 1,
!fsr ? UI_MENU_UPSCALING_DESC_NO_FSR : dlss ? UI_MENU_UPSCALING_DESC : UI_MENU_UPSCALING_DESC_FSR,
note);
@@ -973,6 +977,15 @@ static void row_framegen_mult_ngx(void) {
if (nv >= 0 && nv < n) opt_apply("NX2NAVI_FG_MULT", values[nv]);
}
// Frame generation to or from NVIDIA's waits for a restart: AMD's follows the setting, NVIDIA's is set up with the device
// (vk_dlssg.c). Through NGX it follows the setting too, once NGX is up, which needs DLSS upscaling on when the game
// starts. The row's note and the Restart button's count (restart_pending).
static bool framegen_restart(void) {
const bool set = !strcmp(cfg_value("NX2NAVI_FRAMEGEN"), "dlss");
if (!dlss_offered()) return false;
return framegen_ngx() ? set && !vk_ngxfg_wanted() : set != (vk_dlssg_wanted() != 0);
}
// Frame generation (vk_fg.cpp, at once; NVIDIA's after a restart); it needs a temporal upscaler's motion vectors
static void row_framegen(void) {
// the generated frames are made from DLSS's or FSR 3.1's motion vectors: without one of them it would do nothing,
@@ -1004,7 +1017,7 @@ static void row_framegen(void) {
// AMD's follows the setting; NVIDIA's is set up with the device, so only a change to or from it waits for a restart.
// Through NGX it follows the setting too, once NGX is up, which needs DLSS upscaling on when the game starts.
const bool ngx = framegen_ngx();
const bool restart = ngx ? dlss && cur == 2 && !vk_ngxfg_wanted() : dlss && (cur == 2) != (vk_dlssg_wanted() != 0);
const bool restart = framegen_restart();
if (restart) note = UI_MENU_AFTER_RESTART;
else if (cur == 1 && !fsr) note = UI_MENU_FSR3_OFF; // set where it cannot load: "in use: off" and why
// NVIDIA's set since the start but not running: "in use: off" and why (vk_dlssg_status; the log has the details).
@@ -1260,6 +1273,17 @@ static const char *prompts_note(bool *restart) {
}
}
// How many settings wait for a restart, on the Restart button: the restart-only ones whose value differs from the one the
// game started with (cfg.c cfg_pending_count), NVIDIA's upscaling, frame generation and HDR while their rows say "after
// restart" (their settings are live ones, so the rows decide), and the prompts when a changed pad layout asks for
// others (the prompts setting itself, when changed, is among the first).
static int restart_pending(void) {
bool prompts;
prompts_note(&prompts);
return cfg_pending_count() + upscaling_restart() + framegen_restart() + (vk_hdr_needs_restart() != 0)
+ (prompts && !cfg_pending("NX2NAVI_PROMPTS"));
}
// The confirmation of the menu's Restart button, over the menu; Cancel has the cursor first. Restart writes nx2navi.ini,
// then closes the game as Quit does and starts it again (gfx_window_request_restart). It waits while the game writes a
// save, and the game stays when nx2navi.ini cannot be written, as the changed settings would not come back. From the
@@ -1480,8 +1504,15 @@ static void layout_menu(uint32_t w, uint32_t h, float scale) {
ImGui::SetCursorPosY(y + st.FramePadding.y);
ImGui::TextColored(ImVec4(0.95f, 0.45f, 0.35f, 1), "%s", menu_text(UI_MENU_SAVE_FAILED));
}
const char *cont = menu_text(UI_MENU_CONTINUE), *restart = menu_text(UI_MENU_RESTART_YES), *quit = menu_text(UI_MENU_QUIT);
float bw = fmaxf(fmaxf(ImGui::CalcTextSize(cont).x, ImGui::CalcTextSize(restart).x), ImGui::CalcTextSize(quit).x) + st.FramePadding.x * 4;
// Restart says how many settings wait for it (restart_pending); "###restart" keeps the button's id, and so the cursor
// on it, when the count changes
char shown[80], restart[96];
const int pending = restart_pending();
if (pending) snprintf(shown, sizeof shown, menu_text(UI_MENU_RESTART_COUNT), pending);
else snprintf(shown, sizeof shown, "%s", menu_text(UI_MENU_RESTART_YES));
snprintf(restart, sizeof restart, "%s###restart", shown);
const char *cont = menu_text(UI_MENU_CONTINUE), *quit = menu_text(UI_MENU_QUIT);
float bw = fmaxf(fmaxf(ImGui::CalcTextSize(cont).x, ImGui::CalcTextSize(shown).x), ImGui::CalcTextSize(quit).x) + st.FramePadding.x * 4;
// the save menu's brown buttons, with the game's cursor on the selected one (ui/menu_skin.cpp)
float gap = st.ItemSpacing.x + skin_cursor_reach(scale);
// right-aligned; in a window too narrow for all three, from the left edge

View File

@@ -347,13 +347,15 @@ static int row_text(const char *key, const char *label, const char *desc, const
dl->AddText(font, small, ImVec2(nx, ny), v.note ? IM_COL32(250, 206, 130, 255) : SKIN_TEXT_DIM, t, NULL, 0, &nclip);
if (tw > 0) note_right = nx - fs * 0.6f;
}
// the label and its note, clipped before the value
// the label and its note, clipped before the value. A restart-only setting says "after restart" while its value
// differs from the one the game started with (cfg_pending), so the note marks what a restart would change; the
// description says it whatever the value (desc_set)
ImVec4 clip(p.x, p.y, value_left - fs * 0.6f, p.y + h);
dl->AddText(font, fs, ImVec2(p.x + pad, ty), off ? kGrey : SKIN_TEXT, label, NULL, 0, &clip);
const char *note = disabled ? disabled
: off ? menu_text(UI_MENU_SET_BY_ENV)
: m && !m->live ? menu_text(UI_MENU_AFTER_RESTART)
: NULL;
const char *note = disabled ? disabled
: off ? menu_text(UI_MENU_SET_BY_ENV)
: m && !m->live && cfg_pending(key) ? menu_text(UI_MENU_AFTER_RESTART)
: NULL;
ImVec4 note_clip(p.x, p.y, note_right, p.y + h);
if (note)
dl->AddText(font, small, ImVec2(p.x + pad, ny), off ? IM_COL32(200, 150, 90, 255) : SKIN_TEXT_DIM, note, NULL, 0,

View File

@@ -2,8 +2,8 @@
// row's description on the right. Each row shows a setting of the cfg table (cfg.h) and changes it through cfg_set: A
// steps it (toggle, next value), left and right step it, and the mouse steps it on the two framed arrows beside its
// value and nowhere else on the row (a click on the row selects it; held on an arrow it repeats). A row marks the
// default value, says "after restart" when the setting is read at start, and is greyed out with a note when an
// environment variable fixes it.
// default value, says "after restart" while a setting read at start differs from the value the game started with
// (cfg_pending), and is greyed out with a note when an environment variable fixes it.
//
// opt_begin("##screen", scale);
// opt_toggle("NX2NAVI_FULLSCREEN", UI_MENU_FULLSCREEN, UI_MENU_FULLSCREEN_DESC);

View File

@@ -282,6 +282,7 @@ static const char *const text_en[UI_TEXT_COUNT] = {
[UI_MENU_RESTART_CONFIRM] = "Restart the game now? Progress since your last save is lost.",
[UI_MENU_QUIT_CONFIRM] = "Quit the game? Progress since your last save is lost.",
[UI_MENU_RESTART_YES] = "Restart",
[UI_MENU_RESTART_COUNT] = "Restart (%d)",
[UI_MENU_RESTART_SAVING] = "The game is saving: Restart is possible in a moment.",
[UI_MENU_QUIT_SAVING] = "The game is saving: Quit is possible in a moment.",
[UI_MENU_GROUP_STICKS] = "Analog sticks",
@@ -842,6 +843,7 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
[UI_MENU_RESTART_CONFIRM] = "Перезапустить игру сейчас? Прогресс после последнего сохранения будет потерян.",
[UI_MENU_QUIT_CONFIRM] = "Выйти из игры? Прогресс после последнего сохранения будет потерян.",
[UI_MENU_RESTART_YES] = "Перезапустить",
[UI_MENU_RESTART_COUNT] = "Перезапустить (%d)",
[UI_MENU_RESTART_SAVING] = "Игра сохраняется: перезапуск будет доступен через мгновение.",
[UI_MENU_QUIT_SAVING] = "Игра сохраняется: выход будет доступен через мгновение.",
[UI_MENU_GROUP_STICKS] = "Стики",
@@ -1526,6 +1528,7 @@ static const char *const text_pt_br[UI_TEXT_COUNT] = {
[UI_MENU_RESTART_CONFIRM] = "Reiniciar o jogo agora? O progresso desde o último salvamento é perdido.",
[UI_MENU_QUIT_CONFIRM] = "Sair do jogo? O progresso desde o último salvamento é perdido.",
[UI_MENU_RESTART_YES] = "Reiniciar",
[UI_MENU_RESTART_COUNT] = "Reiniciar (%d)",
[UI_MENU_RESTART_SAVING] = "O jogo está salvando: dá para reiniciar em um instante.",
[UI_MENU_QUIT_SAVING] = "O jogo está salvando: dá para sair em um instante.",
[UI_MENU_STICK_SENS] = "Velocidade da câmera (analógico)",
@@ -2183,6 +2186,7 @@ static const char *const text_zh_hans[UI_TEXT_COUNT] = {
[UI_MENU_RESTART_CONFIRM] = "现在重启游戏?上次存档之后的进度将会丢失。",
[UI_MENU_QUIT_CONFIRM] = "退出游戏?上次存档之后的进度将会丢失。",
[UI_MENU_RESTART_YES] = "重启",
[UI_MENU_RESTART_COUNT] = "重启(%d)",
[UI_MENU_RESTART_SAVING] = "游戏正在存档:稍后即可重启。",
[UI_MENU_QUIT_SAVING] = "游戏正在存档:稍后即可退出。",
[UI_MENU_STICK_SENS] = "摇杆镜头速度",

View File

@@ -114,6 +114,7 @@ typedef enum {
UI_MENU_PROMPTS_OFF_MODS, UI_MENU_PROMPTS_OFF_PATCHES,
// "Restart now" under the prompts while they change after restart, and its confirmation (Cancel: UI_MENU_RESET_NO)
UI_MENU_RESTART, UI_MENU_RESTART_DESC, UI_MENU_RESTART_CONFIRM, UI_MENU_QUIT_CONFIRM, UI_MENU_RESTART_YES, UI_MENU_RESTART_SAVING, UI_MENU_QUIT_SAVING,
UI_MENU_RESTART_COUNT, // the bar's Restart with the count of settings waiting for it (printf: %d)
UI_MENU_GROUP_STICKS, UI_MENU_LSTICK_DEADZONE, UI_MENU_RSTICK_DEADZONE, UI_MENU_STICK_DEADZONE_DESC,
UI_MENU_STICK_DEADZONE_NOW, UI_MENU_STICK_SENS, UI_MENU_STICK_SENS_DESC,
UI_MENU_MOUSE_MODE, UI_MENU_MOUSE_MODE_DESC, UI_MENU_MOUSE_STICK, UI_MENU_MOUSE_DIRECT,

View File

@@ -73,6 +73,7 @@ int cfg_from_env(const char *) { return 0; }
void cfg_set(const char *, const char *) {}
int cfg_save(void) { return 1; }
int cfg_reset(const char *const *, int) { return 0; }
int cfg_pending(const char *) { return 0; } // nothing changed since the start: no "after restart" notes
unsigned cfg_version(void) { return 1; }
void menu_sound_play(MenuSound) {}
const char *ov_language(void) { return "en-US"; }