Skip to content

feat(hle): parameterize SoundDriver RPC service SID + subcommands per game#154

Open
smmathews wants to merge 1 commit into
ran-j:mainfrom
smmathews:feature/07-sounddriver-sid-subcommand
Open

feat(hle): parameterize SoundDriver RPC service SID + subcommands per game#154
smmathews wants to merge 1 commit into
ran-j:mainfrom
smmathews:feature/07-sounddriver-sid-subcommand

Conversation

@smmathews

Copy link
Copy Markdown
Contributor

Summary

Moves the SoundDriver RPC HLE's service SID and subcommand (fno) numbers out of hardcoded constants into the per-game compat layout. Upstream already parameterized the per-game addresses via PS2SoundDriverCompatLayout; the service SID and subcommand (fno) numbers were still hardcoded placeholder constants in ps2_iop.h. This change moves the SID(s) and subcommand numbers into the compat layout so a title whose sound driver registers a different SID or different subcommand numbers is served without editing deps.

What changed

  • PS2SoundDriverCompatLayout (ps2_runtime.h) gains commandSid/stateSid + a servesSid() helper, the subcommand fno fields (submitFno, getStatusFno, getAddrTableFno, streamOpenFno, channelConfigFno, stopFno), benignStatusValue (default 0xffffff9b), and the extra address fields (streamStateAddr, streamReadyValue, channelAllocFlagTableAddr, stopCompletionFlagAddr).
  • handleSoundDriverRpcServiceImpl (RPC.cpp) now:
    • snapshots the layout under g_rpc_mutex (no fiber/scheduler interaction);
    • returns false immediately without touching guest memory when the layout is unconfigured (commandSid == 0 && stateSid == 0);
    • gates on servesSid(sid), then dispatches by matching rpcNum against the layout's fno fields (each match nonzero-guarded) — submit / getStatus / getAddrTable (existing semantics, re-keyed) plus the new streamOpen / channelConfig / stop writes;
    • for an unknown fno on a served SID, writes benignStatusValue to recv[0] and returns false (falls through so LIBSD / game handlers still run).
  • Removed the IOP_SID_SNDDRV_* / IOP_RPC_SNDDRV_* placeholder constants from ps2_iop.h and their references in ps2_debug_panel.cpp. IOP_SID_LIBSD is kept (still used by the LIBSD fast path).
  • RE:CVX (slus_201.84) game override (game_overrides.cpp) now carries the SID/fno numbers it relies on: it sets stateSid = 1, getStatusFno = 0x12, getAddrTableFno = 0x13 (the values of the deleted IOP_SID_SNDDRV_STATE / IOP_RPC_SNDDRV_GET_STATUS_ADDR / _GET_ADDR_TABLE constants). RE:CVX's getStatus RPC (sid=1, fno=0x12) provisions g_soundDriverRpcState.statusAddr, and the sceSifGetOtherData checksum backfill fires only when srcAddr == statusAddr; without these values the layout would be unconfigured (commandSid == stateSid == 0), the handler's unconfigured guard would return false, statusAddr would never be provisioned, and the checksum backfill would silently stop. This is the only production override that speaks the sound-driver RPC service; the LotR override uses only completionCallbacks (a separate, non-SID-gated path) and correctly needs no SID/fno configuration. (RE:CVX's old submit path used placeholder SID 0 / fno 0 — a non-real service that never matched a live call — and is intentionally left unconfigured.)
  • Tests (ps2_sif_rpc_tests.cpp, ps2_sif_dma_tests.cpp): migrated the pre-existing snddrv-state / sound-status unit tests (which relied on the deleted placeholder SID 1 and fnos 0x12/0x13) to register a layout that maps those numbers. Added regression tests: one exercising every subcommand semantic + the benign unknown-fno fall-through; one proving the inert unconfigured layout plus two games routing independently through the single global layout slot; and a real-path test that provisions the layout solely through ps2_game_overrides::applyMatching("slus_201.84") and drives the actual SifCallRpc(getStatus)sceSifGetOtherData path, asserting the check-array → status backfill ran and that getStatus returns a nonzero status address.

Known limitations

  • 0-sentinel (SID 0 / fno 0 inexpressible). 0 is the "unused" sentinel for every SID and fno field, so a real service whose SID is literally 0 or a subcommand whose fno is literally 0 cannot be expressed. This is a deliberate tradeoff and adequate in practice — real SIF-RPC services are nonzero, and the deleted placeholder constants that were 0 (IOP_SID_SNDDRV_COMMAND / _SUBMIT) were never live services. Documented in a code comment on the struct; if a title ever needs SID 0 / fno 0, add explicit has-value flags rather than overloading the sentinel.
  • benignStatusValue is handler-local, not guest-visible on the normal path. For an unknown fno on a served SID the handler writes benignStatusValue to recv[0] and returns false; in the real SifCallRpc path the outer recv finalization then overwrites recv[0] (copy-from-send / zero) before the guest observes it. The unknown-fno unit test asserts the handler-local write directly and is annotated to say so — it locks the handler's documented contract, not guest-observable state.

Verification

  • Full ps2x_tests suite: 299 passed / 0 failed. Rebuilt clean (GCC 16) and re-run.
  • Byte-level assertions confirm: streamOpen writes streamReadyValue to streamStateAddr and signals nowait; channelConfig sets channelAllocFlagTableAddr[channel]=1 (channel from send word 0, <16); stop writes 1 to stopCompletionFlagAddr; getStatus/getAddrTable return distinct nonzero addresses; unknown fno returns false with handler-local recv[0]==0xffffff9b; unconfigured layout leaves guest memory untouched and returns false.
  • Revert check: reverting only the game_overrides.cpp override migration makes the new real-path regression test fail (getStatus returns 0, backfill does not run); restoring it returns the suite to green.

Scope notes

  • Additional files touched: game_overrides.cpp (the RE:CVX override migration, above) and ps2_sif_dma_tests.cpp (two existing tests issued SifCallRpc with sid=1, rpcNum=0x12 relying on the old hardcoded constants and never configured a layout; updated to set stateSid=1/getStatusFno=0x12, plus the new real-path regression test). Necessary collateral of the layout-gated dispatch. The debug-panel edits are likewise required collateral of deleting the constants.
  • DQ8 end-to-end is not fully served by this struct yet (the DQ8 case is referenced here only as an illustrative example). The SPU2 transfer-handle value pairs (0x410b64 / 0x410b78) and the stream descriptor (0x41b550) are not implemented — they have no enumerated *Fno semantic slot, no defined trigger, and no test coverage. A real DQ8 port would need those added.

Known interactions

  • An unknown fno on a served SID writes benignStatusValue (0xffffff9b, the documented "no audio work required" discard sentinel) to recv[0] and then falls through. For a game that muxes its driver onto the LIBSD SID, this means every unknown-fno LIBSD call transiently gets 0xffffff9b in recv[0] before the LIBSD backend runs and overwrites it. This is the intended fall-through design; flagged here for whoever later wires a real game onto that SID.
  • After servesSid passes, rpcNum is matched against all *Fno fields regardless of whether the call arrived on commandSid vs stateSid. This is by design (servesSid passes, then rpcNum is compared to the *Fno fields); distinct fno numbers disambiguate in real drivers, and the single-SID muxed case relies on it.

…er game

Move the hardcoded SoundDriver service SID(s) and subcommand (fno) numbers out
of ps2_iop.h and into the per-game PS2SoundDriverCompatLayout struct, so a title
whose sound driver registers a different SID or different subcommand numbers can
be served without editing deps. Upstream already parameterized the per-game
addresses; this extends the layout to the SID and fno numbers too.

- PS2SoundDriverCompatLayout gains commandSid/stateSid + a servesSid() helper,
  the submit/getStatus/getAddrTable/streamOpen/channelConfig/stop fno fields,
  benignStatusValue (default 0xffffff9b), and the stream/channel/stop address
  fields.
- handleSoundDriverRpcServiceImpl snapshots the layout under g_rpc_mutex,
  returns false without touching guest memory when unconfigured
  (commandSid == stateSid == 0), gates on servesSid, then dispatches by matching
  rpcNum against the layout's fno fields (each nonzero-guarded) -- submit /
  getStatus / getAddrTable (existing semantics, re-keyed) plus new streamOpen /
  channelConfig / stop writes. Unknown fno on a served SID writes
  benignStatusValue to recv[0] and falls through (returns false) so LIBSD/game
  handlers still run.
- Remove the IOP_SID_SNDDRV_* / IOP_RPC_SNDDRV_* placeholder constants and their
  references in the debug panel. IOP_SID_LIBSD is kept (LIBSD fast path).
- Migrate the RE:CVX (slus_201.84) override, which relied on the deleted
  state-SID path (sid=1, fno 0x12/0x13) to provision the sound-driver
  status/addr-table pool: applyRecvxSoundDriverCompat now sets stateSid=1,
  getStatusFno=0x12, getAddrTableFno=0x13. Without this the handler's
  unconfigured guard returns false, statusAddr is never provisioned, and the
  sceSifGetOtherData checksum backfill (fires only when srcAddr==statusAddr)
  silently stops. The old submit path used placeholder SID 0 / fno 0 (a non-real
  service) and is left unconfigured. LotR override audited: uses only
  completionCallbacks (a separate, non-SID-gated path) and needs no migration.

0-sentinel limitation: 0 is the "unused" value for every SID/fno field, so a
service whose SID is literally 0 or a subcommand whose fno is literally 0 cannot
be expressed. Deliberate tradeoff -- real SIF-RPC services are nonzero, and the
deleted placeholder constants that were 0 were never live services.

Tests: migrate the existing snddrv-state RPC unit tests to register a layout;
add regression tests covering every subcommand semantic, the benign unknown-fno
fall-through, the inert unconfigured layout, two games routing independently
through the single global layout slot, and a real-path test provisioning the
layout solely via applyMatching("slus_201.84") and driving the actual
SifCallRpc(getStatus) -> sceSifGetOtherData backfill (fails if the override
migration is reverted).

ps2x_tests: 299 passed, 0 failed.
@smmathews smmathews force-pushed the feature/07-sounddriver-sid-subcommand branch from d6c325f to 56c23d0 Compare July 7, 2026 18:58
@smmathews smmathews marked this pull request as ready for review July 7, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant