From 49d7e8fcff6ea1deaa972d7e509f17305f768443 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Sat, 10 Oct 2026 11:18:09 +0200 Subject: [PATCH] Correct the framing: the two command forms are padding, not protocols My earlier note read as "Swiss's menu and a patched game speak different protocols to the same chip", which is both suspicious-sounding and wrong. Both send the IDENTICAL 2-byte header [0x40|reg, 0x00] and differ only in how many bytes follow, which the device never cared about -- the data phase runs until CS, exactly as in register space. Swiss's 4-byte form is an EXI efficiency choice: one exi_imm_read_write(..., 4) is a single EXI immediate operation, where libogc2 spends two separate EXI_ImmEx calls. For code injected into a running game, halving the EXI operations per register access is worth a padding byte. Menu vs game is not a protocol split either, just which library is linked: cube/swiss/source/bba.c contains no BBA register access at all -- its header exposes only network config, detection and display over -- so the menu is entirely libogc2, and the patch driver is Swiss's own leaner code. Verified: the only menu-side device access is EXI_GetID for detection and MX98730EC_GetExiSpeed, which just returns EXI_SPEED32MHZ (confirming Swiss hardcodes speed index 5 for the real BBA), while bba_requires_init() returns id != EXI_MX98730EC_ID, i.e. the real BBA needs no Swiss-side init. Also sharpens the multi-byte caveat. No single rule satisfies both Swiss's padded single-byte write and libogc2's bba_cmd_outs(0x04, &devid, 2), so the real device's behaviour there cannot be deduced from the drivers -- and it is unobservable in them anyway: devid is the hardcoded constant 0xD107, is never read back, and bba_cmd_out8(0x05, acstart) on the next line overwrites half of it under auto-increment. Settling it would need a real BBA on a logic analyser, so the note now says not to try it from source. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6d5bb35..0f2bb26 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1086,6 +1086,24 @@ Sources, so this can be re-checked rather than re-guessed: - stock libogc — the same, read out of `libbba.a` (`gcif.o`, `__bba_init`): `EXI_Imm(len=2)` of `42 00`, then `EXI_ImmEx(len=1)` of the data. +**They are NOT two protocols — only two amounts of padding.** This is worth +stating plainly, because "the menu and a game speak different protocols to the +same chip" is rightly suspicious and is not what happens. Both send the +*identical* 2-byte header `[0x40|reg, 0x00]`. They differ only in how many +bytes follow, and the device is length-agnostic — the data phase runs until CS, +exactly as in register space. Swiss's 4-byte version is an **EXI efficiency +choice**, not a different protocol: one `exi_imm_read_write(…, 4)` is a single +EXI immediate operation (two register pokes), where libogc2 spends two separate +`EXI_ImmEx` calls. For code injected into a running game, halving the EXI +operations per register access is worth a padding byte. The real BBA tolerates +both because it never cared about the length in the first place. + +Menu vs game is therefore not a protocol split, it is just which library is +linked: `cube/swiss/source/bba.c` contains **no BBA register access at all** +(its header exposes only network config, detection and display, over +``), so the menu is 100% libogc2; the patch driver is Swiss's own +code and spends fewer EXI operations. + **How one decoder serves both:** they share bytes 0 and 1, and differ only in how many bytes follow. So the device takes the **LAST data byte before CS rise** as the write value (Swiss sends `[00, val]`, libogc sends `[val]` — @@ -1132,8 +1150,17 @@ data phase has two meanings that cannot be told apart on the wire: libogc2 really does multi-byte command writes — `bba_cmd_outs(0x04, &priv->devid, 2)` runs unconditionally in its init. "Last byte wins" is wrong -for those, and auto-increment is wrong for Swiss. It does not matter today -because: +for those, and auto-increment is wrong for Swiss, so **no single rule satisfies +both** and the real device's behaviour here cannot be deduced from the drivers. + +⚠️ **It is also unobservable in them**, which is why it has never mattered: +`priv->devid` is the hardcoded constant `0xD107`, is never read back from the +device, and `bba_cmd_out8(0x05, priv->acstart)` on the very next line writes +`0x05` separately anyway — so under auto-increment half of that write is +immediately overwritten. Neither driver ever reads `0x04`/`0x05`. Do not try to +settle this from source; it would need a real BBA on a logic analyser. + +It does not matter for us because: - the only command registers implemented are **0x02** (IMR) and **0x03** (IR), and BOTH drivers access those a single byte at a time; and - libogc2's multi-byte command accesses target `0x04`/`0x05` and