Say so in the log when the settings table is full and a key or a menu change is not kept.

cfg.c keeps at most CFG_MAX_KEYS (512) keys. add, cfg_set, cfg_reset and migrate stopped silently at the cap, so a
file with more keys (every cheat ever switched keeps a cheat_<hash> line, unknown keys count too) had its later keys
ignored and a later menu change lost, with nothing in the log (the audit's probe: low_latency after 600 cheat lines
read as unset, a cfg_set of vk_present lost, 0 log lines). Only a file far past what players have reaches it.

A line now says so: at load once per load, naming the first key and line not read, and once a run for the changes
cfg_set, cfg_reset and migrate could not keep. Raising the cap would change which keys take effect and is left for
approval.

Not reproduced before the change by a game run; tests/cfg_parse_check.py gains the case: a file of 600 cheat keys
and then low_latency gives one line at load from line 513 on, and two cfg_set past the full table one line. The cfg
rows checks and the compile checks pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 17:39:20 -05:00
parent b4556957c2
commit 5b9ed9ed12
3 changed files with 46 additions and 3 deletions

View File

@@ -497,10 +497,20 @@ const char *cfg_unknown_keys(int *count) {
return unknown_keys;
}
// The table holds CFG_MAX_KEYS keys and keeps none past them: a file's later keys were not read, and a menu change was
// lost, without a word (every cheat ever switched keeps a line, cheats.c cheats_set). A line says so once per load, and
// once a run for the changes (cfg_set, cfg_reset, migrate).
static int full_load_told, full_set_told;
static void full_set(const char *key, const char *what) {
if (!full_set_told++) rt_log("cfg: the settings table is full (%d keys): %s %s", CFG_MAX_KEYS, key, what);
}
static void clear(void) {
for (int i = 0; i < n_entries; i++) free(entries[i].key), free(entries[i].value);
n_entries = 0;
unknown_keys[0] = 0, n_unknown = unknown_cut = 0;
full_load_told = 0;
}
static char *trim(char *s) {
@@ -535,7 +545,12 @@ static void add(char *key, char *value, int line) {
entry_line[i] = line;
return;
}
if (n_entries == CFG_MAX_KEYS) return;
if (n_entries == CFG_MAX_KEYS) {
if (!full_load_told++)
rt_log("cfg: nx2navi.ini has more than %d keys: line %d (%s) and the new keys after it are not read",
CFG_MAX_KEYS, line, key);
return;
}
entry_line[n_entries] = line;
entries[n_entries++] = (CfgEntry){strdup(key), strdup(value), 0, 0};
}
@@ -631,6 +646,8 @@ static void migrate(void) {
layout->dirty = 1;
if (!prompts && n_entries < CFG_MAX_KEYS)
entry_line[n_entries] = 0, entries[n_entries++] = (CfgEntry){strdup("PROMPTS"), strdup("xbox"), 1, 0};
else if (!prompts)
full_set("PROMPTS", "= xbox is not kept");
rt_log("cfg: nx2navi.ini pad_layout = xbox_prompts is read as pad_layout = nintendo and prompts = %s",
prompts ? prompts->value : "xbox");
}
@@ -848,6 +865,8 @@ void cfg_set(const char *name, const char *value) {
} else if (n < CFG_MAX_KEYS) {
entries[n] = (CfgEntry){strdup(key), strdup(value), 1, 0};
__atomic_store_n(&n_entries, n + 1, __ATOMIC_RELEASE);
} else {
full_set(key, "is not kept");
}
__atomic_add_fetch(&version, 1, __ATOMIC_RELEASE);
plat_mutex_unlock(&set_lock);
@@ -865,7 +884,10 @@ int cfg_reset(const char *const *keys, int n) {
int i = 0;
while (i < n_entries && strcmp(entries[i].key, name)) i++;
if (i == n_entries) { // no value in memory ("key =" in the file): the line still has to go
if (i == CFG_MAX_KEYS) continue;
if (i == CFG_MAX_KEYS) {
full_set(name, "keeps its line in the file");
continue;
}
entries[i] = (CfgEntry){strdup(name), NULL, 0, 1};
__atomic_store_n(&n_entries, i + 1, __ATOMIC_RELEASE);
} else if (entries[i].value) {

View File

@@ -2,6 +2,7 @@
// run by tests/cfg_parse_check.py: `cfg_parse_check <case> <ini>` loads the file the script wrote and prints every line
// cfg.c logged ("log: ...") and what the case reads back ("got: ...").
// load cfg_load on the file; then cfg_init's check of the environment (the variables the script set)
// full cfg_load on the file, then two cfg_set of keys the file does not have
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
@@ -70,5 +71,14 @@ int main(int argc, char **argv) {
cfg_init(); // finds no nx2navi.ini next to the stand-in exe; checks the environment's values
return 0;
}
if (!strcmp(argv[1], "full")) {
if (!cfg_load(ini)) return printf("cfg_load failed\n"), 2;
const char *ll = cfg_get("NX2NAVI_LOW_LATENCY");
printf("got: low_latency %s\n", ll ? ll : "(unset)");
cfg_set("NX2NAVI_VK_PRESENT", "fifo");
cfg_set("NX2NAVI_FULLSCREEN", "1");
printf("got: vk_present %s\n", cfg_get("NX2NAVI_VK_PRESENT") ? cfg_get("NX2NAVI_VK_PRESENT") : "(unset)");
return 0;
}
return 2;
}

View File

@@ -3,7 +3,9 @@ for what it links with. The values stay as written; what is checked is what the
- values outside their rows' sets (cfg.c check_value): a 0|1 switch with another value, a number outside its range, a
word that is none of the menu's choices, and a ';' or '#' comment kept in a value, each named with its line; none
for a file whose values fit, nor for a list with numbers in it (the menu's presets); at most 16 lines, then a count.
for a file whose values fit, nor for a list with numbers in it (the menu's presets); at most 16 lines, then a count;
- a full settings table (cfg.c CFG_MAX_KEYS): one line at load naming the first key not read, one at the first menu
change not kept.
No game files.
@@ -118,6 +120,15 @@ def main():
listed = sum(1 for l in out.splitlines() if l.startswith('log: cfg: nx2navi.ini line') and "= '7'" in l)
check(rc == 0 and listed == 16 and 'more values outside their settings' in out,
f'20 values outside their sets: {listed} lines and a count', out)
# more keys than the table holds: one line at load, one for the menu's changes
full = ''.join(f'cheat_{0x1000 + i:08x} = 1\n' for i in range(600)) + 'low_latency = 1\n'
rc, out = run(exe, env, 'full', full, work)
load_lines = [l for l in out.splitlines() if 'has more than 512 keys' in l]
set_lines = [l for l in out.splitlines() if 'settings table is full' in l]
check(rc == 0 and len(load_lines) == 1 and 'line 513 (CHEAT_' in load_lines[0] and 'got: low_latency (unset)' in out,
'a file with 601 keys: one line at load from line 513 on', out)
check(rc == 0 and len(set_lines) == 1 and 'VK_PRESENT is not kept' in set_lines[0],
'two menu changes past a full table: one line', out)
print(f'cfg_parse_check: {"FAILED" if bad else "ok"}')
return 1 if bad else 0