Skip to content

Commit 56c23d0

Browse files
committed
feat(hle): parameterize SoundDriver RPC service SID and subcommands per 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.
1 parent 61621b8 commit 56c23d0

7 files changed

Lines changed: 441 additions & 24 deletions

File tree

ps2xRuntime/include/ps2_runtime.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,33 @@ struct PS2SoundDriverCompatLayout
265265
std::array<uint32_t, 4> completionCallbacks{};
266266
std::array<uint32_t, 2> clearBusyCallbacks{};
267267

268+
// Service ids (0 = unused; a driver muxing everything on one SID sets both equal).
269+
// 0 is the "unused" sentinel, so a service or fno that is literally 0 can't be
270+
// expressed -- a deliberate tradeoff (real SIF-RPC services are nonzero).
271+
uint32_t commandSid = 0; // service id carrying the submit-command-buffer subcommand
272+
uint32_t stateSid = 0; // service id carrying the status/addr-table queries
273+
274+
// Subcommand (rpcNum -> semantic) mapping. 0 = that semantic is unused.
275+
uint32_t submitFno = 0; // submit-command-buffer
276+
uint32_t getStatusFno = 0; // return statusAddr
277+
uint32_t getAddrTableFno = 0; // return addrTableAddr
278+
uint32_t streamOpenFno = 0; // write streamReadyValue to streamStateAddr
279+
uint32_t channelConfigFno = 0; // set channelAllocFlagTableAddr[channel]=1 (channel from send word 0 if <16)
280+
uint32_t stopFno = 0; // write 1 to stopCompletionFlagAddr
281+
uint32_t benignStatusValue = 0xffffff9bu; // recv[0] for an unknown fno on a served SID
282+
283+
// Addresses/values the stream/channel/stop subcommands operate on.
284+
uint32_t streamStateAddr = 0;
285+
uint32_t streamReadyValue = 0;
286+
uint32_t channelAllocFlagTableAddr = 0;
287+
uint32_t stopCompletionFlagAddr = 0;
288+
289+
[[nodiscard]] bool servesSid(uint32_t sid) const
290+
{
291+
return (commandSid != 0u && sid == commandSid) ||
292+
(stateSid != 0u && sid == stateSid);
293+
}
294+
268295
[[nodiscard]] bool hasChecksumTables() const
269296
{
270297
return primarySeCheckAddr != 0u || primaryMidiCheckAddr != 0u ||

ps2xRuntime/include/runtime/ps2_iop.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,11 @@
55

66
class PS2Runtime;
77

8-
constexpr uint32_t IOP_SID_SNDDRV_COMMAND = 0x00000000u;
9-
constexpr uint32_t IOP_SID_SNDDRV_STATE = 0x00000001u;
108
constexpr uint32_t IOP_SID_LOTR_CLFILE = 0x0000FF01u;
119
constexpr uint32_t IOP_SID_LOTR_SOUND = 0x00012345u;
1210
constexpr uint32_t IOP_SID_LIBSD = 0x80000701u;
1311
constexpr uint32_t IOP_SID_FATAL_FRAME_SDRDRV = 0x19740512u;
1412

15-
constexpr uint32_t IOP_RPC_SNDDRV_SUBMIT = 0x00000000u;
16-
constexpr uint32_t IOP_RPC_SNDDRV_GET_STATUS_ADDR = 0x00000012u;
17-
constexpr uint32_t IOP_RPC_SNDDRV_GET_ADDR_TABLE = 0x00000013u;
18-
1913
class ps2_iop
2014
{
2115
public:

ps2xRuntime/src/lib/Kernel/Syscalls/RPC.cpp

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -539,14 +539,30 @@ namespace ps2_syscalls
539539
return false;
540540
}
541541

542-
if (sid == IOP_SID_SNDDRV_COMMAND && rpcNum == IOP_RPC_SNDDRV_SUBMIT)
542+
PS2SoundDriverCompatLayout compat{};
543+
{
544+
std::lock_guard<std::mutex> lock(g_rpc_mutex);
545+
compat = g_soundDriverCompatLayout;
546+
}
547+
548+
if (compat.commandSid == 0u && compat.stateSid == 0u)
549+
{
550+
return false;
551+
}
552+
553+
if (!compat.servesSid(sid))
554+
{
555+
return false;
556+
}
557+
558+
if (compat.submitFno != 0u && rpcNum == compat.submitFno)
543559
{
544560
handleSoundDriverCommandBuffer(rdram, runtime, sendBuf, sendSize);
545561
return true;
546562
}
547563

548-
if (sid == IOP_SID_SNDDRV_STATE &&
549-
(rpcNum == IOP_RPC_SNDDRV_GET_STATUS_ADDR || rpcNum == IOP_RPC_SNDDRV_GET_ADDR_TABLE))
564+
if ((compat.getStatusFno != 0u && rpcNum == compat.getStatusFno) ||
565+
(compat.getAddrTableFno != 0u && rpcNum == compat.getAddrTableFno))
550566
{
551567
uint32_t responseWord = 0u;
552568
{
@@ -556,8 +572,8 @@ namespace ps2_syscalls
556572
return false;
557573
}
558574
responseWord =
559-
(rpcNum == IOP_RPC_SNDDRV_GET_STATUS_ADDR) ? g_soundDriverRpcState.statusAddr
560-
: g_soundDriverRpcState.addrTableAddr;
575+
(rpcNum == compat.getStatusFno) ? g_soundDriverRpcState.statusAddr
576+
: g_soundDriverRpcState.addrTableAddr;
561577
}
562578

563579
if (recvBuf && recvSize >= sizeof(uint32_t))
@@ -574,6 +590,77 @@ namespace ps2_syscalls
574590
return true;
575591
}
576592

593+
if (compat.streamOpenFno != 0u && rpcNum == compat.streamOpenFno)
594+
{
595+
{
596+
std::lock_guard<std::mutex> lock(g_rpc_mutex);
597+
if (compat.streamStateAddr != 0u)
598+
{
599+
(void)writeGuestU32(rdram, compat.streamStateAddr, compat.streamReadyValue);
600+
}
601+
}
602+
603+
if (recvBuf && recvSize >= sizeof(uint32_t))
604+
{
605+
(void)writeGuestU32(rdram, recvBuf, 0u);
606+
resultPtr = recvBuf;
607+
}
608+
609+
signalNowaitCompletion = true;
610+
return true;
611+
}
612+
613+
if (compat.channelConfigFno != 0u && rpcNum == compat.channelConfigFno)
614+
{
615+
uint32_t channel = 0u;
616+
if (sendBuf != 0u)
617+
{
618+
(void)readGuestU32(rdram, sendBuf, channel);
619+
}
620+
621+
{
622+
std::lock_guard<std::mutex> lock(g_rpc_mutex);
623+
if (compat.channelAllocFlagTableAddr != 0u && channel < 16u)
624+
{
625+
(void)writeGuestU32(rdram, compat.channelAllocFlagTableAddr + channel * sizeof(uint32_t), 1u);
626+
}
627+
}
628+
629+
if (recvBuf && recvSize >= sizeof(uint32_t))
630+
{
631+
(void)writeGuestU32(rdram, recvBuf, 0u);
632+
resultPtr = recvBuf;
633+
}
634+
635+
signalNowaitCompletion = true;
636+
return true;
637+
}
638+
639+
if (compat.stopFno != 0u && rpcNum == compat.stopFno)
640+
{
641+
{
642+
std::lock_guard<std::mutex> lock(g_rpc_mutex);
643+
if (compat.stopCompletionFlagAddr != 0u)
644+
{
645+
(void)writeGuestU32(rdram, compat.stopCompletionFlagAddr, 1u);
646+
}
647+
}
648+
649+
if (recvBuf && recvSize >= sizeof(uint32_t))
650+
{
651+
(void)writeGuestU32(rdram, recvBuf, 0u);
652+
resultPtr = recvBuf;
653+
}
654+
655+
signalNowaitCompletion = true;
656+
return true;
657+
}
658+
659+
if (recvBuf && recvSize >= sizeof(uint32_t))
660+
{
661+
(void)writeGuestU32(rdram, recvBuf, compat.benignStatusValue);
662+
}
663+
577664
return false;
578665
}
579666

ps2xRuntime/src/lib/game_overrides.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,14 @@ namespace
267267
layout.busyFlagAddr = 0x01E212C8u;
268268
layout.completionCallbacks = {0x002EAC20u, 0x002EAC30u, 0x002FAC20u, 0x002FAC30u};
269269
layout.clearBusyCallbacks = {0x002EAC30u, 0x002FAC30u};
270+
271+
// SID + subcommand (fno) numbers RE:CVX's sound driver speaks; carried per-game
272+
// so its getStatus RPC provisions the status/addr-table pool the
273+
// sceSifGetOtherData checksum backfill depends on. The submit path (SID 0 /
274+
// fno 0, never a live service) is intentionally left unconfigured.
275+
layout.stateSid = 1u;
276+
layout.getStatusFno = 0x12u;
277+
layout.getAddrTableFno = 0x13u;
270278
ps2_syscalls::setSoundDriverCompatLayout(layout);
271279
}
272280

ps2xRuntime/src/lib/ps2_debug_panel.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,6 @@ namespace
7575
{
7676
switch (sid)
7777
{
78-
case IOP_SID_SNDDRV_COMMAND:
79-
return "SNDDRV command";
80-
case IOP_SID_SNDDRV_STATE:
81-
return "SNDDRV state";
8278
case IOP_SID_LIBSD:
8379
return "LIBSD";
8480
case IOP_SID_FATAL_FRAME_SDRDRV:
@@ -1085,8 +1081,6 @@ namespace
10851081
bool dynamic;
10861082
};
10871083
const ServiceRow services[] = {
1088-
{"SNDDRV command", IOP_SID_SNDDRV_COMMAND, false},
1089-
{"SNDDRV state", IOP_SID_SNDDRV_STATE, false},
10901084
{"LIBSD", IOP_SID_LIBSD, false},
10911085
{"Fatal Frame SDRDRV", IOP_SID_FATAL_FRAME_SDRDRV, false},
10921086
{"LOTR SOUND", IOP_SID_LOTR_SOUND, false},

ps2xTest/src/ps2_sif_dma_tests.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "ps2_runtime.h"
33
#include "ps2_syscalls.h"
44
#include "ps2_stubs.h"
5+
#include "game_overrides.h"
56

67
#include <array>
78
#include <cstdint>
@@ -898,6 +899,8 @@ void register_ps2_sif_dma_tests()
898899
PS2SoundDriverCompatLayout compat{};
899900
compat.primarySeCheckAddr = kPrimarySeCheckAddr;
900901
compat.primaryMidiCheckAddr = kPrimaryMidiCheckAddr;
902+
compat.stateSid = 1u;
903+
compat.getStatusFno = 0x12u;
901904
ps2_syscalls::setSoundDriverCompatLayout(compat);
902905

903906
constexpr uint32_t kClientAddr = 0x00023500u;
@@ -947,6 +950,84 @@ void register_ps2_sif_dma_tests()
947950
"live midi_sum for the active bank should not be clobbered by compat check arrays");
948951
});
949952

953+
tc.Run("RE:CVX sound-driver override provisions status pool for sceSifGetOtherData backfill", [](TestCase &t)
954+
{
955+
// Real-path guard for the RE:CVX override: with the layout supplied only by
956+
// applyMatching, drives the actual SifCallRpc(getStatus) -> sceSifGetOtherData
957+
// path and asserts the check-array -> status backfill ran.
958+
TestEnv env;
959+
960+
constexpr uint32_t kRdAddr = 0x00024300u;
961+
constexpr uint32_t kDstAddr = 0x00024400u;
962+
constexpr uint32_t kSize = 0x42u;
963+
constexpr uint32_t kPrimarySeCheckAddr = 0x01E0EF10u; // matches the RE:CVX override
964+
constexpr uint32_t kPrimaryMidiCheckAddr = 0x01E0EF20u; // matches the RE:CVX override
965+
constexpr uint32_t kMidiSumOffset = 0x1Eu;
966+
constexpr uint32_t kSeSumOffset = 0x26u;
967+
constexpr uint32_t kLiveBank = 0u;
968+
constexpr uint32_t kPendingBank = 1u;
969+
970+
// Provision the layout via the real RE:CVX game override (elf slus_201.84).
971+
ps2_game_overrides::applyMatching(env.runtime, "slus_201.84", 0u);
972+
973+
constexpr uint32_t kClientAddr = 0x00024500u;
974+
constexpr uint32_t kRecvAddr = 0x00024600u;
975+
constexpr uint32_t kSid = 1u;
976+
977+
ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime);
978+
setRegU32(env.ctx, 4, kClientAddr);
979+
setRegU32(env.ctx, 5, kSid);
980+
setRegU32(env.ctx, 6, 0u);
981+
ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime);
982+
t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for sound-driver sid");
983+
984+
setRegU32(env.ctx, 4, kClientAddr);
985+
setRegU32(env.ctx, 5, 0x12u);
986+
setRegU32(env.ctx, 6, 0u);
987+
setRegU32(env.ctx, 7, 0u);
988+
setRegU32(env.ctx, 8, 0u);
989+
setRegU32(env.ctx, 9, kRecvAddr);
990+
setRegU32(env.ctx, 10, 4u);
991+
setRegU32(env.ctx, 11, 0u);
992+
ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime);
993+
const uint32_t kSrcAddr = readGuestU32(env.rdram.data(), kRecvAddr);
994+
995+
// The override must have provisioned a nonzero status pool address; if
996+
// stateSid/getStatusFno are unset the getStatus RPC is unhandled and this is 0.
997+
t.IsTrue(kSrcAddr != 0u,
998+
"getStatus RPC (served only via the migrated override) must return a nonzero status address");
999+
1000+
std::memset(env.rdram.data() + kDstAddr, 0, kSize);
1001+
std::memset(env.rdram.data() + kRdAddr, 0, sizeof(SifRpcReceiveData));
1002+
1003+
writeGuestS16(env.rdram.data(), kSrcAddr + kSeSumOffset + (kLiveBank * 2u), static_cast<int16_t>(0x1111));
1004+
writeGuestS16(env.rdram.data(), kSrcAddr + kMidiSumOffset + (kLiveBank * 2u), static_cast<int16_t>(0x2222));
1005+
1006+
writeGuestS16(env.rdram.data(), kPrimarySeCheckAddr + (kPendingBank * 2u), static_cast<int16_t>(0x3333));
1007+
writeGuestS16(env.rdram.data(), kPrimaryMidiCheckAddr + (kPendingBank * 2u), static_cast<int16_t>(0x4444));
1008+
1009+
setRegU32(env.ctx, 4, kRdAddr);
1010+
setRegU32(env.ctx, 5, kSrcAddr);
1011+
setRegU32(env.ctx, 6, kDstAddr);
1012+
setRegU32(env.ctx, 7, kSize);
1013+
ps2_stubs::sceSifGetOtherData(env.rdram.data(), &env.ctx, &env.runtime);
1014+
1015+
t.Equals(getRegS32(env.ctx, 2), 0,
1016+
"sceSifGetOtherData should succeed for sound-status transfer");
1017+
t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kSeSumOffset + (kLiveBank * 2u)),
1018+
static_cast<int16_t>(0x1111),
1019+
"existing live se_sum values should remain intact");
1020+
t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kMidiSumOffset + (kLiveBank * 2u)),
1021+
static_cast<int16_t>(0x2222),
1022+
"existing live midi_sum values should remain intact");
1023+
t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kSeSumOffset + (kPendingBank * 2u)),
1024+
static_cast<int16_t>(0x3333),
1025+
"zero se_sum slots should backfill from the override's compat tables (proves the status pool was provisioned)");
1026+
t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kMidiSumOffset + (kPendingBank * 2u)),
1027+
static_cast<int16_t>(0x4444),
1028+
"zero midi_sum slots should backfill from the override's compat tables (proves the status pool was provisioned)");
1029+
});
1030+
9501031
tc.Run("sceSifGetOtherData backfills zero sound-status sums for later banks", [](TestCase &t)
9511032
{
9521033
TestEnv env;
@@ -964,6 +1045,8 @@ void register_ps2_sif_dma_tests()
9641045
PS2SoundDriverCompatLayout compat{};
9651046
compat.primarySeCheckAddr = kPrimarySeCheckAddr;
9661047
compat.primaryMidiCheckAddr = kPrimaryMidiCheckAddr;
1048+
compat.stateSid = 1u;
1049+
compat.getStatusFno = 0x12u;
9671050
ps2_syscalls::setSoundDriverCompatLayout(compat);
9681051

9691052
constexpr uint32_t kClientAddr = 0x00023900u;

0 commit comments

Comments
 (0)