https://gitlab.synchro.net/main/sbbs/-/commit/aa8f8077290791b6225fd81c
Added Files:
src/doors/syncmoo1/syncmoo1_cfgprune.c syncmoo1_cfgprune.h src/doors/syncmoo1/tests/test_cfgprune.c
Modified Files:
src/doors/syncmoo1/CMakeLists.txt hw_sbbs.c syncmoo1.example.ini syncmoo1_config.c syncmoo1_config.h src/doors/syncmoo1/tests/CMakeLists.txt
Log Message:
syncmoo1: make the user's 1oom config a pure diff against sysop policy
1oom's cfg_save() writes EVERY registered item on exit, so after one session
a player's config pinned every option -- including ones they never touched.
A sysop who later changed a default could never reach an existing player.
Two halves. syncmoo1.ini gains a [1oom] passthrough whose keys are literal
1oom cfg names (module.item); the door writes them to a temp file and calls 1oom's own cfg_load(), so the engine's per-item range checks validate them
and every option it has -- now or later -- is settable with no code here.
Then cfg_save() snapshots the base.
1oom's cfg_load() assigns only the keys physically present in a file, so the user's config still overrides whatever it names. At exit we prune it against the base, dropping every key the player never diverged on. A player who never opens the options menu ends with an empty config, and the next sysop change reaches them.
The pruner (syncmoo1_cfgprune.c) is pure and I/O-free so it unit-tests with
no engine and no termgfx: comments, blank lines, malformed lines, and keys absent from the base are all preserved; it is idempotent; values compare with whitespace stripped. Best-effort at runtime -- any failure leaves the file exactly as 1oom wrote it.
Wiring the prune call took two rounds to get right, both confirmed by an
actual headless run rather than static reasoning. First: 1oom's game loop returns to main() with a plain C `return` on a normal quit -- nothing calls exit() at that point, so cfg_save() (atexit(main_shutdown)'d from inside main_1oom()) hasn't run yet when a plain post-call prune would fire. Fix: atexit(sm_config_prune_user_cfg) registered BEFORE main_1oom(), so it lands earlier on the atexit stack and therefore runs LATER (atexit is LIFO) --
after cfg_save(), whether the process ends via a normal return, the DOOR32 time-limit's exit(0), or anything else that calls exit(). Second: by the
time our now-later handler runs, main_shutdown()'s own os_shutdown() has already freed 1oom's cached user-config-dir, so a second cfg_cfgname() call silently re-derives a $HOME/XDG path instead of the door's per-user sandbox. Fixed by caching the resolved config path once, during the same early pass
that snapshots the base, before 1oom's shutdown can clear it.
Two defaults belong in a door and now ship in syncmoo1.example.ini: ui.copy_protection_disabled, because MoO1's manual-lookup check ends the game after year 40 and a BBS player has no manual; and game.skipintro, which is
4.6s of fades. Note cfg-backed options must be set here, not as 1oom command line flags -- options_parse() applies those after the base snapshot, so they would read as user divergence and pin themselves into every config.
---
þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net