D256: Linux $S (saves/config) now falls back to the exe dir, mirroring Windows

romdataInit finds the ROM in CWD data/, exe-dir data/, or ./, but the
POSIX $S branch only ever produced a CWD-relative path, so any launch
from another directory ran fine while every EEPROM and ini write failed
silently (no ./data/ to fopen into) — saves never persisted and F10
settings reset on every launch. Mirror the Windows branch via
readlink(/proc/self/exe) and mkdir the target dir best-effort. Verified
on WSL with exe+data in /tmp and CWD=/: config, eeprom, and ROM all
land in <exedir>/data/.
This commit is contained in:
James Dansereau committed 2026-09-13 20:17:34 -04:00
1 parent c7898d1620
commit 4ca075061e
3 files changed
+36 -1

No files matched your search

+1
View File
@@ -137,3 +137,4 @@ D252,?,"Particle effects cycle through a rainbow palette (M-124, user playtest o
D253,FIXED,Facility (and any level with a FORCECROUCH/LADDER linked special tile) SIGSEGV on Linux/Steam Deck —,FIXED — Deck-verified:
D254,FIXED,Linux crash handler destroyed its own logs:,FIXED — needs one Deck crash to prove the new format in the field.
D255,OPEN,"Facility firefight SIGSEGV on Steam Deck (v0.2.0-pre CI bundle, D253 fix confirmed working —",OPEN — awaiting register capture from the Deck.
D256,FIXED,Saves (and config) silently failed to persist on Linux whenever the game was not launched from the directory containing data/ —,FIXED (path half) — save-flow detail still owed from the user.
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
137 D253
138 D254
139 D255
140 D256
+1
View File
@@ -533,6 +533,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D253 | **Facility (and any level with a FORCECROUCH/LADDER linked special tile) SIGSEGV on Linux/Steam Deck — `stanCheckLinkedSpecialTile` stores to a garbage `outFlags` pointer (D203 root cause).** The B-callback typedef `standTileLocusCallback_B_t` (`bondtypes.h:573`) declares the middle three params as `f32 f32 f32`, so on x86-64 SysV the caller (`sub_GAME_7F0B1DDC`, `stan.c:2228`) passes the 6th arg (`record`) in **`%rdx`** (3rd integer slot; the floats take xmm0–2). But the actual implementation `stanCheckLinkedSpecialTile` was decompiled with an all-`s32` prototype, so it reads `outFlags` from **`%r9`** (6th integer slot) — a stale scratch value left by earlier callees. On the N64 (MIPS o32) both prototypes agree: everything flows through GPRs a0–a5 in order, 6th arg = a5 either way, so the original is fine and the decompilation looked consistent. The mismatch stays dormant until a tile actually hits the FORCECROUCH or LADDER branch and *stores* through `outFlags` → SEGV; that's why it was level-specific (Facility's opening crouch tile) and input-dependent, and why idle repros were negative. The sibling B-callback `stanLocusEdgeIsAboveY` already had the correct float prototype, which is why only this path was booby-trapped. **Fix (ABI-class, semantics-preserving):** declare the three middle params of `stanCheckLinkedSpecialTile` as `f32` in both the forward decl and definition (`stan.c:152`, `stan.c:2407`) to match the typedef; the body never uses them, and on N64 o32 f32 args occupy the same GPRs, so N64 codegen is unaffected. **Verified:** gdb run on WSL (gcc 15) — pre-fix, SIGSEGV at `stan.c:2441` with `r9=0x3` and valid `record=0x409fea00` in the caller frame; post-fix, the same walk route completes with no crash. Side benefit: the FORCECROUCH/LADDER branches were effectively dead-on-arrival on PC (any hit = crash); they now function. **Also observed post-fix (separate, WSLg-only):** a segfault *inside libgallium* during `gfx_opengl_upload_texture` (32×48 RGBA + mipmaps) while walking further — GL-driver-side under WSLg's Mesa; not reproducible on Windows; park as a new finding if it recurs on real hardware. | FIXED — Deck-verified: user got past the crouch tile with the re-cut bundle (a *new*, later crash is D255). |
| D254 | **Linux crash handler destroyed its own logs: `ge007.crash.log` only ever contained the SIGABRT block, never the original SEGV.** The POSIX `crashHandler` (`port/src/crash.c`) finished by calling `sysFatalError()` → `abort()` → SIGABRT, which re-entered the *same* handler (SIGABRT was one of the installed signals); the second pass reopened the log with `"wb"` and overwrote the first pass's SEGV block — faulting PC, signal, everything. The Deck firefight crash (D255) came back as a bare `SIGNAL: 6` abort record for exactly this reason, forcing offline disassembly of the CI binary to recover what the log should have said. **Fix (port-only):** re-entrancy guard (`inCrashHandler`, second pass `_exit(128+sig)` immediately) + terminate directly with `_exit()` instead of `sysFatalError()` (the Windows path already terminated directly for the same reason) + the Linux block now dumps the faulting context: `FAULT ADDR` (`si_addr`) and the full general register set (Rax/Rcx/Rdx/Rsi/Rdi/R8–R11, Rsp/Rbp) from the `ucontext`, so a field crash now names the bad pointer on its face. | FIXED — needs one Deck crash to prove the new format in the field. |
| D255 | **Facility firefight SIGSEGV on Steam Deck (v0.2.0-pre CI bundle, D253 fix confirmed working — user got past the opening crouch tile).** Crash during gunplay; abort-unwind backtrace points at `sub_GAME_7F09BAC4` (`vtxstore.c:158`) ← `sub_GAME_7F09BBBC` (`:220`) ← `lvlManageMpGame` (`lv.c:1302`) ← `bossMainloop` (`boss.c:567`). Disassembling the exact CI binary at the fault PC (`0x200db135`) gives `mov 0x14(%rax),%edi; mov 0x10(%rdi),%rax`: the function walks the active PropRecord list keeping `type == 1` — which is **PROP_TYPE_OBJ, not CHR** (the decompiler cast to the union's first member and misnamed the field `ChrRecord.chrflags`; it is actually `ObjectRecord.model` at `0x14`) — then dereferences `model->obj`. Two port-relevant wrinkles: (a) the load is **32-bit zero-extended** because the decomp types the field through the `CHRFLAG` enum, so a model pointer ≥ 4 GB would truncate to garbage; (b) the loop skips records whose `model->obj` is NULL but *dereferences* `model` unconditionally, so an active object with a NULL/freed `model` faults — plausible in a firefight where destructibles (crates etc.) die. Not reproduced on WSL after equivalent play. **Next:** the D254 handler now captures registers at fault time (`rax` = the ObjectRecord, `rdi` = its model value) — one more Deck crash identifies which case: tiny/zero `rdi` → destroy-path ordering; plausible-but-wrong `rdi` → stale/reused record or truncation. | OPEN — awaiting register capture from the Deck. |
| D256 | **Saves (and config) silently failed to persist on Linux whenever the game was not launched from the directory containing `data/` — reported as "saves do not work at all" on Steam Deck.** `romdataInit` finds the ROM in `$S/` (CWD `data/`) *or* `$E/` (exe dir) *or* `./`, but `sysResolvePath`'s POSIX `$S` branch (used by `ge007.eep` and `ge007.ini`) only ever produced a CWD-relative path — its "fallback" was `./data/`, identical to the primary. Launch from any other CWD (Steam non-steam-game shortcut, terminal in `~`): ROM loads via the exe-dir candidate, game runs, but every EEPROM/config write hits `fopen(..., "wb")` on a nonexistent `./data/` and fails with a stderr-only "cannot write" — saves never persist, and **every launch also starts with default settings** (F10 changes don't stick), which presents as "draw distance/LOD is low" on levels like Dam where the user had raised `Video.DrawDistance` on another machine. The Windows branch already did CWD-else-exedir; the POSIX branch now mirrors it (`readlink("/proc/self/exe")`) and creates the directory best-effort so first-run writes don't fail. **Verified on WSL:** with exe+`data/` in `/tmp/fb2` and CWD=`/`, config/eeprom/ROM all resolve into `/tmp/fb2/data/` and a 25 s run completes clean; the pre-fix binary in the same layout fails to find the ROM at all. **Caveat:** if the user's Deck launch *was* from the bundle dir (CWD had `data/`), this fix does not explain their save failure — need the save-flow detail (does the memory-card screen appear? slot confirm? "saved" message?) before closing the report. Side note found while testing: a `data/` without the generated sidecars (`pccg-*/`, `pcmodels-*/` from `tools_pc/d69_emit.py`/`d43_emit.py`) starts fine but faults in `load_bg_file` (`bg.c:865`) on level load — the reserve functions warn "stage loads will fault" and return 0; expected degraded mode, not a new bug. | FIXED (path half) — save-flow detail still owed from the user. |
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS
threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software
+34 -1
View File
@@ -26,6 +26,7 @@
#define _POSIX_C_SOURCE 199309L
#include <unistd.h>
#include <time.h>
#include <sys/stat.h>
#endif
#include "system.h"
@@ -245,10 +246,42 @@ const char *sysResolvePath(const char *path)
else
snprintf(out, sizeof(out), "%s\\data\\%s", exedir, path + 3);
#else
/* D256: mirror the Windows branch. The ROM itself is found via
* $S/ OR $E/ (exe dir) by romdataInit, so a launch from any CWD
* (Steam shortcut, terminal in ~) runs fine while saves/config —
* which only knew the CWD-relative path — silently failed to write.
* Prefer CWD data/ (dev/repo workflow), else exe-dir data/, and
* create the directory best-effort so first-run writes don't fail. */
if (access("data", F_OK) == 0)
snprintf(out, sizeof(out), "data/%s", path + 3);
else {
static char sexedir[1024] = "";
if (!sexedir[0]) {
ssize_t n = readlink("/proc/self/exe", sexedir, sizeof(sexedir) - 1);
if (n > 0) {
char *slash;
sexedir[n] = 0;
slash = strrchr(sexedir, '/');
if (slash)
*slash = 0;
else
snprintf(out, sizeof(out), "./data/%s", path + 3);
sexedir[0] = 0;
}
}
if (sexedir[0])
snprintf(out, sizeof(out), "%s/data/%s", sexedir, path + 3);
else
snprintf(out, sizeof(out), "data/%s", path + 3);
}
{
char dirbuf[1024];
char *slash;
strncpy(dirbuf, out, sizeof(dirbuf) - 1);
dirbuf[sizeof(dirbuf) - 1] = 0;
slash = strrchr(dirbuf, '/');
if (slash && slash != dirbuf)
*slash = 0, mkdir(dirbuf, 0755); /* best effort; EEXIST is fine */
}
#endif
return out;
}