Files
rebbarb/docs/bba_compat_todo.md
T
Roflin e10849f73d docs: track the BBA compatibility gaps as TODOs
Records the differences between this implementation and what real GC software
expects, with the evidence and the driver source for each, so they are worked
through rather than rediscovered.  Open items: no hardware RX filtering,
hardcoded ring geometry, NCRB undecoded, and registers that read 0x00 where
real hardware has non-zero defaults.  Also records what is deliberately NOT to
be implemented (the device-initiated challenge/response) and that two of our
registers (TWD, HIPR) do not exist on real hardware.

Notes explicitly that games use the official Nintendo SDK driver, which none of
the three readable drivers is, so "no open driver does this" is weaker evidence
than "the hardware has no such register".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-10 11:40:55 +02:00

6.3 KiB
Raw Blame History

BBA compatibility TODO

Gaps between this implementation and what real GC software expects, found by reading the three drivers that actually drive a BBA:

  • Swiss (in-game patches) — cube/patches/bba/bba.c in emukidid/swiss-gc. Note that directory holds one file per adapter (bba.c is the real DOL-015; enc28j60.c, w5500.c, w6100.c, w6300.c are other hardware) — check which file you are in.
  • libogc2 — lwip/arch/gc/netif/gcif.c in extremscorner/libogc2. This is what Swiss's own menu networking uses, via if_configex().
  • stock devkitPro libogc — same shapes as libogc2; read out of /opt/devkitpro/libogc/lib/cube/libbba.a (gcif.o) with objdump.

⚠️ Games use the official Nintendo SDK driver, which none of us can read. Everything below is derived from the three open drivers plus the register map they document. A game may exercise a mechanism no open driver touches, so "no open driver does this" is weaker evidence than "the hardware has no such register".

The register map referenced throughout is the one in libogc2's gcif.c (BBA_* defines), which is the official one.


1. No hardware RX filtering — the consequential one

Status: OPEN. Needs a decision, not just code.

The real BBA filters received frames. libogc2 configures NCRB = AB|CA|2_PACKETS_PER_INT = 0x52, i.e. PR=0 (not promiscuous), PM=0 (no multicast), AB=1 (accept broadcast) — so the device delivers own-MAC + broadcast only.

We run the W5100 in MACRAW with the MF (MAC filter) bit clear (_S0_MR_MACRAW = 0x04, w5100_parallel_master.py), so every frame on the segment enters the SPRAM ring.

Observed consequence, already seen on hardware: the test app's ring filled to page 15 from what looked like "ambient broadcast traffic" but was in fact all unicast traffic on the segment too. Ring pressure makes RBFI drop frames we DO want, and a game's lean driver may not expect foreign unicast at all.

⚠️ Setting MF is not a one-line fix. MF filters against the W5100's SHAR, and we write SHAR only during the init sequence. libogc2 reads the MAC out of PAR0–5 and never writes it, so SHAR never diverges there — but a game that WRITES PAR0–5 would leave SHAR stale and MF would then drop everything. Doing it properly needs:

  1. decode NCRB (see item 3) so PR/PM can steer the filter;
  2. refresh SHAR when PAR0–5 changes, not only at init;
  3. re-open socket 0 when the filter mode changes (Sn_MR is sampled at OPEN).

The decision: filter correctly (matches real hardware, fixes the ring pressure) at the cost of that machinery, or stay a permissive superset where nothing is ever lost but the ring fills with other hosts' traffic.

2. Ring geometry is hardcoded — silent corruption risk

Status: OPEN.

We fix TX = page 0 and RX = pages 1–15 (_RX_PAGE_FIRST, _RX_PAGE_LAST, _PAGES_TOTAL). libogc2 writes TLBP = 0x0000, BP = 0x0001, RHBP = 0x000F, which matches us exactly — but we ignore those writes.

A game choosing a smaller RX ring (RHBP < 0x0F) would read pages the assembler is still writing, with nothing reporting an error. The minimum honest fix is to honour RHBP/BP, or failing that to detect a value that disagrees with our layout and surface it (a status bit, or at least the UART console) rather than corrupt silently.

3. NCRB (0x01) is not decoded at all

Status: OPEN. Bits, from libogc2:

bit name meaning
0 PR promiscuous mode
1 CA capture effect mode
2 PM pass multicast
3 PB pass bad frame
4 AB accept broadcast
6–7 RXINTC RX interrupt counter: 1 / 2 / … packets per interrupt

We interrupt per frame where libogc2 asks for every 2 packets, which is benign (more interrupts than requested). PR/PM/AB feed item 1.

4. Registers that read 0x00 but have non-zero hardware defaults

Status: OPEN. A driver doing read-modify-write, or sanity-checking a default, sees zero. libogc2 does read NWAYC (0x30) before writing it.

reg name hardware default
0x01 NCRB —
0x04 LTPS last transmitted packet status, RO
0x05 LRPS last received packet status, RO
0x0A/0B BP boundary page pointer
0x0C/0D TLBP TX low boundary page
0x0E/0F TWP TX buffer write page
0x12/13 TRP TX buffer read page
0x14/15 RXINTT RX interrupt timer
0x1A/1B RHBP RX high boundary page
0x30 NWAYC 0x84
0x32 GCA 0x00
0x3D MISC 0x3C
0x3E/3F TXFIFOCNT TX FIFO counter, RO
0x50 MISC2 0x00
0x58–0x5F SI regs libogc2 writes 80 00 03 83 32 FE 1F 1F
0x60 SI_ACTRL2 libogc2 writes 0x00 then 0x74

5. Two of our registers do not exist on real hardware

Status: KNOWN, low risk, do not "fix" blindly.

0x34/0x35 (TWD_LO/TWD_HI, our TX length latch) and 0x3A (HIPR, returning 0x01) are not in the official register map — 0x3A is unassigned there and MISC is 0x3D. They are our own inventions, and it is now confirmed that no driver writes TWD (the TX length is counted at TXDATA instead — see CLAUDE.md). They are harmless unless a game writes those addresses for some other purpose and trips our TX-length latch.

Removing them would break gc_bba_test, which does write TWD, so keep them but do not add more inventions.

6. Lower risk / no action

  • Challenge/response (command regs 0x08/0x09/0x0B) is device-initiated — gated on command status bit 0x10, which we never raise, so the path cannot be entered. Do NOT implement bba_calc_response emulation.
  • No EEPROM: the MAC is a fixed reset value in PAR0–5. Since libogc2 reads the MAC from there rather than writing it, that value is what the console uses. Games keying anything off the MAC see ours.
  • RXINTT (RX interrupt timer) is interrupt coalescing; ignoring it means we interrupt earlier than asked, which is safe.
  • Command register 0x0F may be a reset strobe rather than a register: bba_cmd_in8_slow(0x0F) holds CS for 200 µs between two 10 ms delays and discards the result. Implemented as storage; the NCRA reset follows immediately in that sequence so ignoring any side effect costs nothing today.