forked from Ports/goldeneye-pc-port
Docs: v0.2.0 finalization — README/Pages + media pass (#80)
* README: 32 s gameplay preview montage (mp4) + preview-video runbook * Prune: drop superseded goldeneye-demo.gif (replaced by gh-preview montage) * Docs: v0.2.0 finalization pass — final wording, aligned known issues, Deck-verified line, social preview card - README + pages site: 'v0.2.0 (pre-release)' -> v0.2.0 throughout; the stale 'not yet verified on real Deck hardware' line is replaced with the playtest fact (D253 fixed, D255 still open). - Known-issues lists in README + site now match the release notes one-for-one (adds the F10 bottom-row duplicate D251 and the intermittent Deck SIGSEGV D255; D243/D230/D252/D245/D246/D75 carry their finding labels). - Working list mentions AllUnlocked default-ON (D257) with the F10 opt-out. - First-public-release note added to the README hero and site status line, hedged with 'to our knowledge'. - social-preview.png (1280x640 GitHub link card) composed from a gameplay frame of the preview montage; scratchpad/make-social-preview.py is the local recipe (gitignored, like make-preview.py). - Commit docs/img/shots/manifest.csv (gallery -> source recording map); gitignore the regenerable CONTACT-SHEET.jpg; drop scratch-gdb-cmds.txt. * Social preview: regenerate card from updated briefing HTML (v0.2.0 final wording) The PNG committed earlier was a screenshot of the rendered site (stale wording, video element unrendered). The canonical source is .github/social-preview.html per .github/social-preview.md; update its Status/Statistics lines to the v0.2.0 final state and re-render: - Status: RELEASED v0.2.0 — WINDOWS · LINUX · STEAM DECK - All 21 solo missions — full campaign completed in playtesting - ~22k lines C/C++ · ~10k Python (port layer + tools) - ~250 bugs root-caused · two coding agents, one human, part-time Verified by pixel-diff against the previous render: only the four text bands changed; title/fonts/layout identical. Drop the unreferenced root-level social-preview.png. * docs: refresh social preview + status for v0.2.0 playtest results - Social card: 'RELEASED v0.2.0', 21/21 missions completed in playtesting, ~250 bugs fixed, two agents + one human; re-rendered the PNG from the canonical .github/social-preview.html source (pixel-diff vs the old render shows only the four edited text bands changed) - README Status: campaign now claimed as completable end to end (user playtest: full campaign, Agent difficulty, PC); Working list matches - findings: D255 updated with the Deck register capture (Rdi=nil -> NULL ObjectRecord.model, destroy-path ordering case confirmed; truncation ruled out) and the rule-#2 sign-off question for a guard; D254 field-verified * docs: regenerate findings index (D254/D255 status updates)
This commit is contained in:
1 parent
c62803f890
commit
608808a212
11 files changed
+169
-42
No files matched your search
@@ -118,18 +118,18 @@
|
||||
<div class="h1">GOLDENEYE <span class="r">007</span><br><small>PC PORT</small></div>
|
||||
|
||||
<div class="meta">
|
||||
Mission: Native PC port of GoldenEye, compiled from the N64 decomp project
|
||||
Mission: Native PC port of GoldenEye 007, compiled from the N64 decomp project
|
||||
</div>
|
||||
|
||||
<div class="rep">REPORT:</div>
|
||||
|
||||
<div class="rows">
|
||||
<div class="statusline">Status: <span class="status">ALPHA v0.1.0 — PHASE 2 OF 4</span></div>
|
||||
<div class="statusline">Status: <span class="status">RELEASED v0.2.0 — WINDOWS · LINUX · STEAM DECK</span></div>
|
||||
<div class="stats">
|
||||
<span class="k">Statistics:</span><span>19 / 21 solo missions completable in a full playthrough</span>
|
||||
<span class="k">Statistics:</span><span>All 21 solo missions — full campaign completed in playtesting</span>
|
||||
<span></span><span>~230 game-code units, compiled unmodified</span>
|
||||
<span></span><span>~17k lines C/C++ · ~6k Python (port layer + tools)</span>
|
||||
<span></span><span>~190 bugs root-caused · built in ~3 weeks, part-time</span>
|
||||
<span></span><span>~22k lines C/C++ · ~10k Python (port layer + tools)</span>
|
||||
<span></span><span>~250 bugs root-caused · two coding agents, one human, part-time</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 583 KiB After Width: | Height: | Size: 597 KiB |
@@ -156,6 +156,9 @@ ge007.crash.log
|
||||
|
||||
# agent scratch space + audio probe artifacts (regenerable dumps/traces)
|
||||
scratchpad/
|
||||
# montage work artifact: the tiled contact sheet behind docs/img/shots/ and
|
||||
# the preview video (regenerable from the same session recordings)
|
||||
docs/img/shots/CONTACT-SHEET.jpg
|
||||
/audiodump.raw
|
||||
/audiotrace.log
|
||||
/mixertrace.log
|
||||
|
||||
@@ -12,9 +12,11 @@ in a dedicated `port/` layer, following the architecture of the
|
||||
[Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark), the same
|
||||
Rare "Indy" engine family, one hardware generation apart.
|
||||
|
||||
**v0.2.0 (pre-release)** is out for Windows and Linux — including Steam Deck,
|
||||
where the Linux bundle sideloads as-is — and runs the full campaign at a steady
|
||||
60 fps with known rough edges ([Status](#status)).
|
||||
**v0.2.0** is out for Windows and Linux — including Steam Deck, where the
|
||||
Linux bundle sideloads as-is — and runs the full campaign at a steady 60 fps
|
||||
with known rough edges ([Status](#status)). To our knowledge it's also the
|
||||
first open-source port of the original N64 game to ship as a public release:
|
||||
free to download, build on and modify (you bring the ROM).
|
||||
|
||||
It's also a case study in AI-agent collaboration on a large, low-level
|
||||
codebase: two coding agents, driven by one person part-time, porting ~230
|
||||
@@ -28,11 +30,10 @@ See [Background](#background).
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/img/attract-bunker1.png" width="32%" alt="Bunker 1 intro camera">
|
||||
<img src="docs/media/goldeneye-demo.gif" width="32%" alt="~15 s of the port running: mission dossier, Facility, Silo, Jungle, Archives">
|
||||
<video src="docs/media/goldeneye-gh-preview.mp4" width="32%" controls muted loop></video>
|
||||
<img src="docs/img/attract-dam.png" width="32%" alt="Dam intro camera">
|
||||
<br><em>In-engine, running in the port — Bunker 1 and Dam attract views, and a
|
||||
~15 s clip (the clip itself has no audio track): mission dossier →
|
||||
Facility → Silo → Jungle → Archives.</em>
|
||||
~32 s gameplay montage from live play sessions (the clip has no audio track).</em>
|
||||
</p>
|
||||
|
||||
## Download
|
||||
@@ -58,34 +59,46 @@ Then:
|
||||
3. Run the one-time asset step: `python3 prepare-assets/prepare-assets.py` (Python 3.8+, stdlib only).
|
||||
4. Launch the executable from that folder.
|
||||
|
||||
Read the [Status](#status) caveats first — this is a pre-release.
|
||||
Read the [Status](#status) caveats first — v0.2.0 has known rough edges,
|
||||
listed plainly there.
|
||||
|
||||
## Status
|
||||
|
||||
**v0.2.0 (pre-release) — playable, with known rough edges.** It runs the full
|
||||
single-player campaign at a steady 60 fps with no known crashes; the point of
|
||||
the pre-release is an end-to-end playtest that all 21 missions are completable
|
||||
start to finish on this build — feedback is welcome.
|
||||
**v0.2.0 — playable, with known rough edges.** The full single-player
|
||||
campaign is completable end to end (all 21 missions, Agent difficulty —
|
||||
playtested), at a steady 60 fps; all 21 solo missions load, render and run
|
||||
crash-free, verified on Windows, Linux and real Steam Deck hardware. Feedback
|
||||
is very welcome.
|
||||
|
||||
**Working:** boot sequence and front end (menu → mission select → briefing →
|
||||
start); all 21 solo missions load, render and are crash-free; steady 60 fps
|
||||
start); all 21 solo missions load, render and are crash-free (full campaign
|
||||
playtested end to end at Agent difficulty); steady 60 fps
|
||||
(software RSP off the presentation critical path); full audio — in-level music
|
||||
and SFX; keyboard + mouse (click-to-lock, proportional aim mode) and a modern
|
||||
dual-stick controller layout; file-backed saves; F10 in-game options overlay
|
||||
(resolution, frame cap, MSAA, filtering, FOV, sensitivity); Windows and Linux.
|
||||
dual-stick controller layout; file-backed saves; all levels, 007 mode and the full cheat menu unlocked by
|
||||
default (F10 → *All unlocked* restores faithful N64 progression); F10 in-game
|
||||
options overlay (resolution, frame cap, MSAA, filtering, FOV, sensitivity);
|
||||
Windows and Linux.
|
||||
|
||||
**Known issues:**
|
||||
|
||||
- **Cutscenes glitch frequently** — skipped beats, wrong camera, misplaced or
|
||||
hovering actors, wrong timing. The most visible gap in this release.
|
||||
- A few in-level music tracks sound wrong (wrong instruments, occasional
|
||||
garbling).
|
||||
hovering actors, wrong timing; the Dam level-end cutscene is racy (D243).
|
||||
The most visible gap in this release.
|
||||
- A few in-level music tracks sound wrong — a wrong-sounding bass instrument
|
||||
on some elevator/level tracks (D230).
|
||||
- Particle colours drift through a rainbow palette instead of holding their
|
||||
grey/orange intent (bullet sparks, lingering smoke/explosion residue).
|
||||
- Water levels show a moving seam between two water patterns; pixel strips at
|
||||
the left/right screen edges at non-integer scales; some front-end 3D models
|
||||
(spinning Nintendo logo, MISSION COMPLETE / mode-select) mispositioned or
|
||||
absent. Assorted further cosmetic defects are tracked in
|
||||
grey/orange intent (bullet sparks, lingering smoke/explosion residue) (D252).
|
||||
- Water levels show a moving seam between two water patterns (D245); thin
|
||||
pixel strips at the left/right screen edges at non-integer window scales
|
||||
(D246).
|
||||
- Some front-end 3D models are off — the spinning Nintendo logo renders as two
|
||||
white blobs and the Rareware logo's texture filtering looks wrong (D75).
|
||||
- The F10 overlay's bottom row duplicates whatever item is currently selected
|
||||
(D251).
|
||||
- **Steam Deck:** one intermittent SIGSEGV remains in heavy firefights (D255);
|
||||
current builds capture full faulting registers in `ge007.crash.log`.
|
||||
- Assorted further cosmetic defects are tracked in
|
||||
[`docs/dev/GRAPHICS-BACKLOG.md`](docs/dev/GRAPHICS-BACKLOG.md).
|
||||
- No macOS or ARM support; no controller rebinding UI.
|
||||
|
||||
@@ -102,9 +115,10 @@ installing. On SteamOS the first launch seeds `ge007.ini` with Deck-friendly
|
||||
defaults — native 1280×800 fullscreen, VSync, MSAA 4, and 150% draw/LOD
|
||||
distance (the authored N64 fade distances read short on the close-up panel);
|
||||
everything is changeable in the options overlay and persists afterwards. The renderer is CPU-bound (software RSP); expect original N64-era
|
||||
performance at 60 fps rather than more. A v0.1.0-era crash on the Deck in
|
||||
Facility was never reproduced and its prime suspect has since been fixed, but
|
||||
this release has not yet been verified on real Deck hardware.
|
||||
performance at 60 fps rather than more. This release was playtested on real
|
||||
Deck hardware — the v0.1.0-era Facility crash was never reproduced there, and
|
||||
its prime suspect (D253) is fixed; one intermittent SIGSEGV in heavy
|
||||
firefights remains open (D255).
|
||||
|
||||
**In-game settings on the Deck.** The options overlay is fully gamepad-driven:
|
||||
it opens with **Select**, the D-pad or left stick (up/down) moves between
|
||||
@@ -160,7 +174,7 @@ code with this one.
|
||||
| **How** | Decompilation-based source port — human-reconstructed C, compiled for the host; game logic runs as written | Static binary recompilation — the shipped machine code is auto-translated to C; no source-level understanding |
|
||||
| **Lineage** | [GoldenEye 007 decompilation](https://github.com/n64decomp/007) + [Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark) engine family | Xbox 360 "…Recompiled" static-recompilation family |
|
||||
| **Renderer** | Software RSP → OpenGL | Hardware (Vulkan) |
|
||||
| **Status** | v0.2.0 pre-release; see [Status](#status) | Playable full game, higher frame rates, online multiplayer |
|
||||
| **Status** | v0.2.0; see [Status](#status) | Playable full game, higher frame rates, online multiplayer |
|
||||
| **Why it exists** | A [case study in AI-agent collaboration](#background) on a hard low-level codebase | A polished, playable PC release of the remaster |
|
||||
|
||||
If you just want to play GoldenEye on PC today, use one of the recompilation
|
||||
|
||||
@@ -135,6 +135,7 @@ D250,?,FPS is inconsistent/low on many levels since the port started actually ru
|
||||
D251,FIXED,F10 options overlay QoL pass:,"FIXED (port-only, port/src/optionsoverlay.c)."
|
||||
D252,?,"Particle effects cycle through a rainbow palette (M-124, user playtest of the v0.2.0-pre candidate).",
|
||||
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.
|
||||
D254,FIXED,Linux crash handler destroyed its own logs:,FIXED — field-verified.
|
||||
D255,OPEN,"Facility firefight SIGSEGV on Steam Deck (v0.2.0-pre CI bundle, D253 fix confirmed working —","OPEN — case identified (NULL model on an active OBJ record; destroy-path ordering), reproducible on Deck in combat (Bunker + Frigate); fix pending rule-#2 sign-off."
|
||||
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.
|
||||
D257,FIXED,"New Game.AllUnlocked option (F10, default ON for this cut):",FIXED — verified by user playtest; PR open.
|
||||
|
@@ -531,8 +531,8 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| D251 | **F10 options overlay QoL pass: (a) bottom rows drawn off-screen in the in-game 2D space — the layout assumed a ~320×240 field and hard-fit all rows, but `viSetXY` gives 440×330 on front-end screens (`src/game/front.c:8618`) vs 320×240 in-game (`front.c:8628`), so with 17 rows × 15u pitch + 2 header rows the last rows land at y≈282, off-screen whenever the panel is opened in-level; (b) MSAA row only cycled forward — `rowAdjust()`'s `ROW_MSAA` case clamped its index to [0,3] instead of wrapping, so left-click stuck it at 8x with no way down via clicks; (c) manual % rows (`Video.DrawDistance`/`Video.LodDistance`) showed unconditionally even while their `*AutoFov` toggles were on, where the percent is inert/misleading. | **FIXED (port-only, `port/src/optionsoverlay.c`).** (a) The panel now computes how many rows fit the *actual* current 2D height (`maxVisibleRows()` from `viGetY()`) and scrolls: a visible-row window (`s_scroll`) follows the selection, driven by wheel (`optionsOverlayScroll`) and arrow/D-pad at the list edges; layout, mouse hit-testing (`overlayRowAtY` now returns a visible position) and keyboard nav all index the visible list (`s_visIdx`/`s_visN`, `s_sel` is now a visible-list index), so it fits both 240- and 330-tall spaces with no per-space tuning. (b) `ROW_MSAA` wraps `(idx + dir + 4) % 4` — OFF→2x→4x→8x→OFF in both directions, matching the already-wrapping `ROW_ENUM`/`ROW_RES`; sliders keep clamping (standard for drag-able values). (c) New `Row.hiddenIfOn`/`hidePtr` fields: a row is hidden while the named option is nonzero — `Draw distance %` hides under `Video.DrawDistanceAutoFov`, `LOD distance %` under `Video.LodDistanceAutoFov`; rebuilt every frame in `overlayUpdateVisible()` (selection clamped into range when its row vanishes). Hint line updated to "click / drag / arrows / wheel". **Verified:** compiles clean in the full 241-target build; final link of this session was blocked only by a running game instance holding the exe (re-link pending). Not yet eyeballed live: scroll feel at 320×240, hover/scroll interaction, and the auto/% row appear-vanish transitions. Cross-ref D213 (FPS readout in the same file), D218/D249 (the auto-coupling these rows expose). **Follow-up after live test:** (d) list navigation (wheel + arrows) wrapped at both ends, so scrolling past the bottom made the highlight "repeat" from the top — now clamped at both edges like a normal PC settings list; (e) new `Input.SensLink` toggle (default ON, registered in `port/src/input.c`) links the aim/turn sensitivity knobs: moving either rescales the other to hold the stock 38 (`Input.AimModeSens`) : 50 (`Input.MouseTurnSpeed`) ratio, via a re-entrancy-guarded pair-write in `rowSet()`; (f) the "Mouse aim speed" row now edits `Input.AimModeSens` (the knob the default GEPD aim path actually uses — it previously edited `Input.MouseAimSpeed`, which only feeds the legacy velocity-stick fallback and was inert under default settings). **Second follow-up:** hover-to-highlight removed entirely -- moving the mouse over the panel no longer moves the selection or scrolls; near a list edge the hover->select->scroll-shift->new-row-under-cursor feedback loop made the bottom of the list twitch/echo (the persistent "duplicate row" report). Selection now moves only by explicit click (which also selects the clicked row), wheel, or arrows. **Reassessment of the 4K-fullscreen ghost-row bug:** the presentation-UV-wrap theory is DEAD — `gfxFramebuffer` has zero readers (the textured-quad present never happens); presentation is a full-frame `glBlitFramebuffer` from the same-size MSAA FBO to the default framebuffer (fb index 0 is reserved for the window, `gfx_opengl_init`), which has no UVs and cannot wrap. The overlay DL draws each visible row exactly once, in-bounds, with its own scissor. Local repro (maximized 16:9 window + MSAA=4 + `GE_OPTIONSOVERLAY=1` + `GE_PCDUMP`) shows NO ghost — the FBO/MSAA path alone is not the trigger; the remaining variable is true fullscreen on the user's 3072×1728 display (driver/display-level suspect). Next: user bisection — Fullscreen ON + MSAA OFF (restart) → does the ghost persist? **User follow-up (logged, not fixed):** the 4K-fullscreen ghost is *intermittent* across launches, and the user suspects the `hiddenIfOn` appear/vanish transitions of the Draw/LOD distance % rows (visible-list resize while the panel is open) — unverified; note `overlayUpdateVisible()`+`overlayUpdateScroll()` are re-run together in both emit and input each frame, so a stale-state window would have to come from elsewhere (e.g. the one frame a row count changes). MSAA-off bisection still pending. User: in the sessions seen so far the ghost is present on *first open* of F10 (not only after an auto-toggle flip) — weakens but does not kill the appear/vanish theory (toggles had flipped in earlier sessions before a restart). **v0.2.0-pre playtest (user, latest build):** the symptom has changedLine truncated
|
||||
| D252 | **Particle effects cycle through a rainbow palette (M-124, user playtest of the v0.2.0-pre candidate).** Shooting repeatedly makes the bullet-impact "sparks" slowly progress through the full hue wheel (~10 shots to sweep it); other lingering particle effects — smoke / explosion residue — show the same rainbow colouration instead of their intended grey/orange palette. **Mechanism context (verified this session):** particle tris carry no per-primitive material; their colour comes entirely from the ROM-resident state records `globalDL_0x078 … globalDL_0xa50` (`assets/oddtextures.c`, 15 of them) replayed per particle batch (`explosion.c:894`). Each record binds a **distinct** texture pair — `IMAGE_SMOKE_N` (IA, tile 0 @ TMEM 0) × `IMAGE_FIRE_N` (RGBA16, tile 1 @ TMEM 0x188) under `G_CC_INTERFERENCE` (TEXEL0·TEXEL1), and `explosionRenderPart` uses a fixed all-white default vertex (`explosion.c:215`) — so the intended per-stage colour is whatever those 15 fire/smoke pairs encode, and particles **age through the records** (`var_s2` 14→0 at `frame/flareanimspeed`), so a per-texture decode error surfaces exactly as a hue sweep over time / across shots. **Ruled out this session:** (a) the M-82/D172 tile-offset fix is correctly configured for this path — `gsDPSetTextureLOD(G_TL_TILE)` does NOT set the `G_TL_LOD` bit, so `rdp.tex_lod` stays false (`gfx_pc.cpp:2860`) and `gfx_lod_tile_offset` returns `i`, i.e. TEXEL1 genuinely samples tile 1; (b) the RGBA16/5551 texel decode in `import_texture_rgba16` is correct (big-endian, R=bits15-11 G=10-6 B=5-1 A=bit0). **Prime suspect: the width/height derivation in `import_texture_rgba16`** — `width = rdp.texture_tile[tile].line_size_bytes / 2`, `height = size_bytes / line_size_bytes`. The fire tiles are 16 px wide with a MASK_16 TMEM line (64 bytes), so `width` computes 32 (true: 16) and, with `size_bytes` = the LOADBLOCK's 16×14×2 = 448, `height` computes 7 (true: 14) — the GL texture would be built from the right pixels at the wrong dimensions, scrambling each of the 15 fire sprites into a different wrong-hued smear; that fits "slow rainbow progression" precisely (each aging stage = a different scrambled sprite). **Not yet confirmed** — needs one imported fire tile dumped and compared against the raw `IMAGE_FIRE_N` bytes (`GE_TEXDUMP`/`GE_PCDUMP`, or a targeted probe at the `upload_texture` call). **Per user instruction: documented only, NOT fixed for v0.2.0-pre** — ships as a known issue; pick up afterwards (cheap if the suspect holds: derive width/height from the LOADBLOCK's dtx/dlh rather than the tile line size, or special-case non-32-texel-wide RGBA16 tiles). D172/M-82 lineage. |
|
||||
| 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. |
|
||||
| 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. **Field-verified 2026-09-13:** the D255 Deck crash came back as `SIGNAL: 11` + `FAULT ADDR: 0x10` + full register set — exactly the format designed here; it directly identified the NULL-model case (see D255). | FIXED — field-verified. |
|
||||
| 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. **Register capture (D254 format, 2026-09-13):** two more Deck crashes — Bunker mid-firefight, then Frigate with ~3 NPCs alive; log: `SIGNAL: 11 / FAULT ADDR: 0x10 / Rax=0x701e5f54 Rdi=(nil) / PC 0x200db135`. `Rdi` = the record's model value = **exactly NULL** → case (b) confirmed: an active PROP_TYPE_OBJ record with a NULL `model`, dereferenced by the unconditional `mov 0x10(%rdi),%rax`; truncation (a) and stale/garbage pointers are ruled out. Faulting PC matches the recorded disassembly (`sub_GAME_7F09BAC4` = `vtxstore_fix_refs`). Grep-verified: nothing in game code ever writes NULL into `ObjectRecord.model` — `clear_model_obj()` nulls `model->obj` only, and `objInit`/spawn paths only activate a record once model ≠ NULL — so the NULL comes from either a fresh/uninitialized record slot being activated or a destroy-path ordering race (model freed/nulled while the record is still active this tick). **Fix constraint:** any guard lands in game code (`vtxstore.c`) → needs rule-#2 sign-off; `port/` cannot intercept this dereference. Next: (1) decision on whether a NULL-model skip in `vtxstore_fix_refs` qualifies as an ABI-class exception (semantics-preserving defensive guard, PD analogue check); (2) if yes, add it plus a `#ifdef PORT` trace at the deactivation/model-free sites to name the path that leaves the record model-less. | OPEN — case identified (NULL model on an active OBJ record; destroy-path ordering), reproducible on Deck in combat (Bunker + Frigate); fix pending rule-#2 sign-off. |
|
||||
| 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. |
|
||||
| D257 | **New `Game.AllUnlocked` option (F10, default ON for this cut): skip N64 progression entirely — all 21 solo levels selectable at every difficulty, 007 mode available, and the cheat menu fully populated. Port-layer only; no game-code edits.** Three mechanisms, all in `port/`: (1) `main.c` seeds the two live `LEFTOVERDEBUG` RAM flags after `configLoad()` — `debug_enable_all_levels_flag` (all levels) and `debug_007_unlock_flag` (007 mode; both in `src/game/debugmenu_handler.c`, same class as the existing `GE_UNLOCK_ALL` getenv hook in the getter); (2) the eep shim (`libultra.c geEepromPatchAllCheats`) patches the save block at read time — special-casing `fileValidateSaves`'s exact pattern (block 4, 5×96 bytes) — setting all 20 progression-gated cheat-unlock bits per slot (`unlocked_cheats_1/2/3`; the cheat menu row on mode-select only renders when ≥1 is set, `front.c interface_menu06_modesel`) **and** filling every *empty* stage/difficulty completion-time field with the max time 0x3FF (existing player times preserved); both are then CRC'd per slot with the game's own `fileGenerateCRC` or `fileValidateSaves` would wipe the slots. (3) `optionsoverlay.c` row so users can toggle OFF for faithful progression. **Why the completion-time half is required (found by user repro):** when ANY single-player cheat is active, `g_AppendCheatSinglePlayer` makes mission-select (`get_highest_unlocked_difficulty_for_level`, front.c) accept only levels with status `STAGESTATUS_COMPLETED (3)`, not merely `UNLOCKED (1)` — AllUnlocked's debug-flag path returns 1, so enabling a cheat locked every level ("could only do Silo"). Reading the save as a fully-completed campaign satisfies both branches. Time bit-math mirrors `fileGet/SetSaveStageDifficultyTime` verbatim (10-bit fields, difficulties 0–2; 007 times are virtual). **Pitfall hit during implementation:** the first mirror of `save_data` used `times[80]` instead of `(SP_LEVEL_MAX-1)*4 = 76`, making `sizeof` 100≠96 so the block-read size check silently never matched — now enforced by a compile-time `ge_save_slot_size_check`. **Semantics:** unlocked cheats/completions persist to `ge007.eep` on any subsequent save write (self-consistent; mirrors N64 where completing a level permanently unlocks its cheat). No *active* cheats are enabled (invincibility/all-guns stay OFF); weapons remain per-mission pickups. **Verified:** fresh eep — all 5 slots validate `stored==calc` under `GE_SAVELOG=1`; user playtest: all levels selectable at each difficulty, 007 mode present, cheat menu populated, cheats toggle and work (time-modifying cheats untested by user), mission select intact with all cheats ON. | FIXED — verified by user playtest; PR open. |
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# GitHub preview video — runbook
|
||||
|
||||
The README embeds `docs/media/goldeneye-gh-preview.mp4` (the gif of the same
|
||||
name is too heavy for GitHub; keep it only if someone wants a local copy).
|
||||
It's a ~32 s, 8-clip montage of gameplay cut from ShadowPlay session
|
||||
recordings.
|
||||
|
||||
## Tooling
|
||||
|
||||
`scratchpad/` is gitignored, so `make-preview.py` is a **local** tool (it
|
||||
hardcodes local video paths anyway) — this doc is the tracked artifact. If it
|
||||
goes missing, the commands below describe exactly what to rebuild. Run with
|
||||
plain `python`.
|
||||
Working files (per-clip mp4s, concat list, palette, `state.json`) go to
|
||||
`scratchpad/.preview-work/` — disposable, safe to wipe (but then re-run
|
||||
`build` after any edit).
|
||||
|
||||
```sh
|
||||
python scratchpad/make-preview.py list # montage index -> source session + time
|
||||
python scratchpad/make-preview.py shift <i> <±sec> # nudge a clip within its source video
|
||||
python scratchpad/make-preview.py set <i> <sec> # absolute source time
|
||||
python scratchpad/make-preview.py replace <i> <.10|.12|.18|.19> # new random spot in a session
|
||||
python scratchpad/make-preview.py regen --seed N # full random redo (same clips/lengths)
|
||||
python scratchpad/make-preview.py build # re-concat + re-render from state.json
|
||||
```
|
||||
|
||||
Every edit command rebuilds mp4 + gif automatically. A full rebuild takes
|
||||
~30–50 s; a single-clip shift is the same cost (concat dominates). Budget
|
||||
one command per step if running under a timeout — cut+concat and the two gif
|
||||
passes together fit in ~55 s.
|
||||
|
||||
## Source material
|
||||
|
||||
`C:\Users\james\Videos\NVIDIA\Build-pc\new\` — ShadowPlay recordings
|
||||
(3072x1728 @ 60 fps, h264), no usable audio track (cut with `-an`). The four
|
||||
sessions and their tags are hardcoded in `SESSIONS` at the top of the script;
|
||||
add a new session there (tag, filename suffix, duration) to use it.
|
||||
|
||||
## Rules learned from review rounds
|
||||
|
||||
- **Never start a clip in the first ~60 s** of a session: desktop / boot /
|
||||
menus. (`MIN_START`)
|
||||
- **Bad zones** — source times that show non-gameplay; random picks avoid
|
||||
them and `replace` should too. Currently known (in `BAD_ZONES`):
|
||||
- `.10` @ ~395–425 s: file-select menu
|
||||
- `.10` @ ~1290–1420 s: watch close-up (appears twice in that session)
|
||||
Add new ones here whenever a review round finds another.
|
||||
- **Clip length** 4 s is the sweet spot; 5 s starts to drag, <3 s flickers.
|
||||
- **Same-session spacing**: two clips from one session ≥90 s apart, or they
|
||||
read as a repeat of the same scene.
|
||||
- When told "roll forward/back ~N seconds", use N ± a few s and prefer a
|
||||
high-motion moment in that window if you have keyframe stats (see below);
|
||||
otherwise a flat offset is fine.
|
||||
- If a pick lands on a menu/loading screen, step it forward again rather
|
||||
than picking a new random time — the user's "a bit later/earlier" almost
|
||||
always means the *same* scene, different part.
|
||||
|
||||
## Output spec (don't change casually)
|
||||
|
||||
- mp4: 1920x1080 @ 30 fps, libx264 crf 20, `+faststart`, no audio. ~25 MB for 32 s.
|
||||
- gif: 512 px wide, 12 fps, two-pass palette (`palettegen` then
|
||||
`paletteuse=dither=bayer:bayer_scale=3`). ~28 MB — local use only.
|
||||
|
||||
## Optional: motion data to find "cool" moments
|
||||
|
||||
`scratchpad/analyze-sessions.py` dumps per-keyframe YAVG stats for the
|
||||
session files; the diff between consecutive keyframes is a decent proxy for
|
||||
"something is happening". If you need to find the coolest moment in a time
|
||||
window, maximize it — but note plain random picks with spacing constraints
|
||||
have been preferred by the reviewer over signal-optimized cuts, so use this
|
||||
only when asked for "something cool".
|
||||
|
||||
## ffmpeg notes (Windows)
|
||||
|
||||
No system ffmpeg; the script falls back to the `imageio-ffmpeg` static
|
||||
binary (`pip install imageio-ffmpeg`). Pass paths as forward-slash or bare
|
||||
filenames with `cwd=` set — drive-letter colons and backslashes break
|
||||
filter-graph argument parsing.
|
||||
@@ -0,0 +1,27 @@
|
||||
new_name,original,video_session,snapshot_time
|
||||
shot-01.jpg,Build-pc 2026.09.13 - 16.05.44.10.mp4_snapshot_02.11.149.jpg,2026.09.13 - 16.05.44.10,02:11:149
|
||||
shot-02.jpg,Build-pc 2026.09.13 - 16.05.44.10.mp4_snapshot_06.00.446.jpg,2026.09.13 - 16.05.44.10,06:00:446
|
||||
shot-03.jpg,Build-pc 2026.09.13 - 16.05.44.10.mp4_snapshot_07.56.664.jpg,2026.09.13 - 16.05.44.10,07:56:664
|
||||
shot-04.jpg,Build-pc 2026.09.13 - 16.05.44.10.mp4_snapshot_08.33.264.jpg,2026.09.13 - 16.05.44.10,08:33:264
|
||||
shot-05.jpg,Build-pc 2026.09.13 - 16.05.44.10.mp4_snapshot_10.16.041.jpg,2026.09.13 - 16.05.44.10,10:16:041
|
||||
shot-06.jpg,Build-pc 2026.09.13 - 16.05.44.10.mp4_snapshot_11.03.430.jpg,2026.09.13 - 16.05.44.10,11:03:430
|
||||
shot-07.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_02.25.190.jpg,2026.09.13 - 16.54.07.12,02:25:190
|
||||
shot-08.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_03.05.000.jpg,2026.09.13 - 16.54.07.12,03:05:000
|
||||
shot-09.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_05.39.999.jpg,2026.09.13 - 16.54.07.12,05:39:999
|
||||
shot-10.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_05.55.651.jpg,2026.09.13 - 16.54.07.12,05:55:651
|
||||
shot-11.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_06.25.650.jpg,2026.09.13 - 16.54.07.12,06:25:650
|
||||
shot-12.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_09.33.997.jpg,2026.09.13 - 16.54.07.12,09:33:997
|
||||
shot-13.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_12.09.456.jpg,2026.09.13 - 16.54.07.12,12:09:456
|
||||
shot-14.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_12.59.497.jpg,2026.09.13 - 16.54.07.12,12:59:497
|
||||
shot-15.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_13.09.496.jpg,2026.09.13 - 16.54.07.12,13:09:496
|
||||
shot-16.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_15.22.656.jpg,2026.09.13 - 16.54.07.12,15:22:656
|
||||
shot-17.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_15.39.495.jpg,2026.09.13 - 16.54.07.12,15:39:495
|
||||
shot-18.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_16.54.496.jpg,2026.09.13 - 16.54.07.12,16:54:496
|
||||
shot-19.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_17.29.495.jpg,2026.09.13 - 16.54.07.12,17:29:495
|
||||
shot-20.jpg,Build-pc 2026.09.13 - 16.54.07.12.mp4_snapshot_17.39.495.jpg,2026.09.13 - 16.54.07.12,17:39:495
|
||||
shot-21.jpg,Build-pc 2026.09.13 - 17.49.35.18.mp4_snapshot_00.21.484.jpg,2026.09.13 - 17.49.35.18,00:21:484
|
||||
shot-22.jpg,Build-pc 2026.09.13 - 17.49.35.18.mp4_snapshot_00.31.328.jpg,2026.09.13 - 17.49.35.18,00:31:328
|
||||
shot-23.jpg,Build-pc 2026.09.13 - 17.49.35.18.mp4_snapshot_02.03.469.jpg,2026.09.13 - 17.49.35.18,02:03:469
|
||||
shot-24.jpg,Build-pc 2026.09.13 - 17.54.08.19.mp4_snapshot_00.11.972.jpg,2026.09.13 - 17.54.08.19,00:11:972
|
||||
shot-25.jpg,Build-pc 2026.09.13 - 17.54.08.19.mp4_snapshot_02.36.532.jpg,2026.09.13 - 17.54.08.19,02:36:532
|
||||
shot-26.jpg,settings menu.jpg,settings-menu,
|
||||
|
+11
-7
@@ -2,7 +2,7 @@
|
||||
title: GoldenEye 007 PC Port
|
||||
description: >-
|
||||
A native PC port of the original Nintendo 64 GoldenEye 007, built from its
|
||||
decompiled source with a software RSP. v0.2.0 pre-release is out for Windows
|
||||
decompiled source with a software RSP. v0.2.0 is out for Windows
|
||||
and Linux (including Steam Deck) — download it, drop in your own ROM, and
|
||||
play; or dig into the code and the engineering record behind it.
|
||||
---
|
||||
@@ -15,15 +15,17 @@ N64's graphics coprocessor (RSP) running in software — the same architecture
|
||||
as the [Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark), the
|
||||
same Rare "Indy" engine family, one hardware generation apart.
|
||||
|
||||
**Status: v0.2.0 pre-release.** The full single-player campaign runs at a
|
||||
steady 60 fps with no known crashes, and audio (music + SFX) plays throughout.
|
||||
**Status: v0.2.0** — to our knowledge, the first open-source port of the
|
||||
original N64 game to ship as a public release. The full single-player
|
||||
campaign runs at a steady 60 fps with no known crashes, and audio (music +
|
||||
SFX) plays throughout.
|
||||
Known rough edges — cutscenes still glitch, a few music tracks sound wrong, a
|
||||
handful of cosmetic rendering defects remain — are listed plainly under
|
||||
[Honest status](#honest-status).
|
||||
|
||||
<p align="center">
|
||||
<img src="media/goldeneye-demo.gif" width="70%" alt="~15 s of the port running: mission dossier, Facility, Silo, Jungle, Archives">
|
||||
<br><em>~15 s of the port running: mission dossier → Facility → Silo → Jungle → Archives (the clip has no audio track).</em>
|
||||
<video src="media/goldeneye-gh-preview.mp4" width="70%" controls muted loop></video>
|
||||
<br><em>~32 s gameplay montage from live play sessions, running in the port (the clip has no audio track).</em>
|
||||
</p>
|
||||
|
||||
## Download
|
||||
@@ -132,12 +134,12 @@ without a ROM you already own.
|
||||
</p>
|
||||
</details>
|
||||
|
||||
More captures land here as playtesting continues (Steam Deck session next).
|
||||
More captures may land here as playtesting continues.
|
||||
|
||||
## Play it — or take it apart
|
||||
|
||||
- **Try it yourself** — grab a bundle above, bring your own ROM, and play the
|
||||
campaign. It's a pre-release cut for exactly this: if something breaks, an
|
||||
campaign. It's an early cut for exactly this: if something breaks, an
|
||||
[issue](https://github.com/jkdansereau/goldeneye-pc-port/issues) with what
|
||||
you were doing is genuinely useful.
|
||||
- **Read the code** — the game logic in `src/` is unmodified decompilation;
|
||||
@@ -177,6 +179,8 @@ catalogue: [Porting notes](porting-notes.md).
|
||||
- Particle colours drift through a rainbow palette instead of holding grey/orange.
|
||||
- Water levels show a moving seam; pixel strips at screen edges at non-integer
|
||||
scales; some front-end 3D models mispositioned or absent.
|
||||
- The F10 overlay's bottom row duplicates the selected item; one intermittent
|
||||
Steam Deck SIGSEGV in heavy firefights remains open (D255).
|
||||
- No macOS/ARM support; no controller rebinding UI yet.
|
||||
|
||||
The full list, with root causes and fix status: the
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 27 MiB |
Reference in new issue
Block a user