Skip to content

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

Open
smmathews wants to merge 3 commits 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 3 commits into
ran-j:mainfrom
smmathews:feature/07-sounddriver-sid-subcommand

Conversation

@smmathews

@smmathews smmathews commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Problem

The TSNDDRV module answers a fixed pair of service ids and a fixed set of subcommand
numbers, held as file-local constexpr values in tsnddrv.cpp (kCommandSid,
kStateSid, kSubmitFunction, kGetStatusAddressFunction,
kGetAddressTableFunction). Its other title-specific numbers already arrive through
TsnddrvBindings from the game's entry in builtin_profiles.cpp: arena geometry,
checksum candidates, busy flag address, completion rules. A title whose driver registers
a different service id, or numbers its subcommands differently, cannot reuse the module
without editing it.

Fix

  • TsnddrvBindings gains commandSid, stateSid, submitFunction,
    getStatusFunction, getAddrTableFunction. Defaults are the deleted constants' values,
    so the shipped profile behaves as before.
  • handleRpc dispatches against the bindings, and sids() is built at construction, so a
    driver that muxes command and state onto one service id registers it once, not twice.
  • Optional subcommands, disabled at 0: streamOpenFunction writes
    streamReadyValue to streamStateAddress; channelConfigFunction sets
    channelAllocFlagTableAddress[channel] = 1, taking channel from send word 0 when
    below 16; stopFunction writes 1 to stopCompletionFlagAddress. Each zeroes the
    receive word and signals nowait completion.
  • A subcommand the module does not recognize, on a service id it owns, writes
    benignStatusValue (default 0xffffff9b) to the receive word and stays unhandled,
    so a guest-registered server function at that service id still runs.
  • createTsnddrvService throws std::invalid_argument when getStatusFunction equals
    getAddrTableFunction, or when an enabled optional subcommand aliases submit,
    getStatus or getAddrTable — either would make dispatch ambiguous.
  • The recvx-us profile (slus_201.84) states its service ids and subcommand numbers
    explicitly rather than inheriting them.

Basis

The numbers are the deleted constants, carried over unchanged: the removed constexpr
block and the profile's new fields are both in this diff. TsnddrvBindings is already
how the module takes its per-title addresses.

Testing

New cases in ps2_iop_tests.cpp:

  • A synthetic profile muxing command and state onto 0x80007701 with subcommands
    0x30/0x31/0x32: sids() reports that id once, the custom getStatus returns a
    nonzero status address, and a request on 1 is refused.
  • Optional subcommands at 0x40/0x41/0x42: byte-level checks that streamReadyValue
    reaches streamStateAddress, that channel 5's slot in channelAllocFlagTableAddress
    becomes 1, and that stopCompletionFlagAddress becomes 1. Subcommand 0x77
    returns unhandled with benignStatusValue in the receive word.
  • Both rejected binding shapes throw.

The sound-status cases in ps2_sif_rpc_tests.cpp spell 1, 0x12 and 0x13 at the
point of use instead of that file's IOP_SID_SNDDRV_* / IOP_RPC_SNDDRV_* aliases,
which no longer track the module. ps2xTest/CMakeLists.txt adds ps2xIOP/src to the
test include path.

Risk and not in scope

  • Behavior change for slus_201.84: an RPC on service id 0 or 1 carrying an
    unrecognized subcommand, with a receive buffer of at least four bytes, now gets
    0xffffff9b written to its receive word before falling through. SifCallRpc's
    finalization in Kernel/Syscalls/RPC.cpp then overwrites that word — copy from send,
    or zero — because the call stays unhandled. It survives only when a guest server
    function at that id is dispatched and returns 0 with the server buffer at 0,
    leaving the result pointer equal to the receive buffer.
  • The optional subcommands match on subcommand number alone, not on which of the two
    service ids the call arrived on. Distinct numbers disambiguate in real drivers, and the
    muxed single-id case depends on it.
  • submitFunction colliding with getStatusFunction or getAddrTableFunction is not
    rejected; they sit on different service ids in the shipped profile.
  • No new title profile is added. The SPU2 transfer-handle pairs and stream
    descriptor a second title would need have no subcommand slot, no trigger and no tests.
  • ps2xRecomp/include/ps2recomp/elf_parser.h gains #include <cstdint>, unrelated to
    sound. That fix landed separately in fix(recomp): include <cstdint> before elfio in elf_parser.h #199, so the line drops out of this branch on the
    next rebase.
TsnddrvBindings SID and subcommand fields — name, default, effect — and the values the recvx-us (slus_201.84) profile sets
Field Default Effect
commandSid 0x00000000 service id carrying submit
stateSid 0x00000001 service id carrying the status / addr-table queries
submitFunction 0x00000000 submit command buffer
getStatusFunction 0x00000012 return statusAddress
getAddrTableFunction 0x00000013 return addressTableAddress
streamOpenFunction 0 (off) write streamReadyValue to streamStateAddress
channelConfigFunction 0 (off) set channelAllocFlagTableAddress[channel] = 1
stopFunction 0 (off) write 1 to stopCompletionFlagAddress
benignStatusValue 0xffffff9b receive word for an unrecognized subcommand
streamStateAddress 0 target of the streamOpen write
streamReadyValue 0 value streamOpen writes
channelAllocFlagTableAddress 0 base of the per-channel allocation flags
stopCompletionFlagAddress 0 target of the stop write

0 disables the three optional subcommands. It does not disable a service id: 0 is a
real service id here, and slus_201.84 uses it.

recvx-us (slus_201.84) sets:

.commandSid           = 0x00000000
.stateSid             = 0x00000001
.submitFunction       = 0x00000000
.getStatusFunction    = 0x00000012
.getAddrTableFunction = 0x00000013

It leaves the optional subcommands and their addresses at their defaults, so its
dispatch is unchanged apart from the benign-status write noted above.

…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
@ran-j

ran-j commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Hey, sorry but I broke your PR

@smmathews

Copy link
Copy Markdown
Contributor Author

Hey, sorry but I broke your PR

no worries, I'm on it

# Conflicts:
#	ps2xRuntime/include/ps2_runtime.h
#	ps2xRuntime/include/runtime/ps2_iop.h
#	ps2xRuntime/src/lib/Kernel/Syscalls/RPC.cpp
#	ps2xRuntime/src/lib/game_overrides.cpp
#	ps2xRuntime/src/lib/ps2_debug_panel.cpp
#	ps2xTest/src/ps2_sif_dma_tests.cpp
#	ps2xTest/src/ps2_sif_rpc_tests.cpp
…e new TSNDDRV module

Upstream's IOP refactor (ran-j#170) deleted the ps2_iop.h/RPC.cpp SoundDriver RPC
implementation this PR was built on and replaced it with a plugin-style
ps2xIOP module system, moving RE:CVX's sound-driver handling into
ps2xIOP/src/modules/tsnddrv.cpp with the SID/subcommand numbers hardcoded as
module constants. Port this PR's feature onto that module instead of the
deleted code:

- TsnddrvBindings gains commandSid/stateSid/submitFunction/getStatusFunction/
  getAddrTableFunction (defaults preserve the previous hardcoded RE:CVX
  values) plus optional streamOpenFunction/channelConfigFunction/stopFunction
  subcommands, their target addresses, and benignStatusValue -- mirroring the
  PS2SoundDriverCompatLayout fields this PR added before the refactor.
- TsnddrvService::handleRpc dispatches off the bindings instead of fixed
  constants, supports muxing command+state on a single SID, serves the three
  optional subcommands when configured, and writes benignStatusValue (without
  marking the RPC handled) for an unrecognized subcommand on a served SID so
  a guest-registered server function at that SID can still run.
- createTsnddrvService validates getStatusFunction != getAddrTableFunction
  and that the optional subcommands don't alias submit/getStatus/getAddrTable
  or each other.
- builtin_profiles.cpp's RE:CVX binding now sets these fields explicitly
  instead of relying on module-internal constants.
- Also includes <cstdint> explicitly in elf_parser.h: ELFIO 3.12's
  elf_types.hpp uses fixed-width integer types without including it, which
  a newer standard library no longer pulls in transitively.

Test coverage for the old RPC.cpp-level implementation (ps2_sif_rpc_tests.cpp,
ps2_sif_dma_tests.cpp) is removed since that code no longer exists; new
module-level coverage for the parameterization and the three optional
subcommands is added in ps2_iop_tests.cpp instead.

ps2x_tests: 335 passed, 0 failed.
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.

2 participants