Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions ps2xRuntime/include/ps2_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,37 @@ struct PS2SoundDriverCompatLayout
std::array<uint32_t, 4> completionCallbacks{};
std::array<uint32_t, 2> clearBusyCallbacks{};

// Service ids (0 = unused). A driver that muxes everything on one SID sets both equal.
// KNOWN LIMITATION: 0 is the "unused" sentinel, 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); the
// deleted placeholder constants that were 0 (IOP_SID_SNDDRV_COMMAND / _SUBMIT) were
// never live services. If a title ever needs SID 0 / fno 0, add explicit has-value
// flags rather than overloading the 0 sentinel.
uint32_t commandSid = 0; // service id carrying the submit-command-buffer subcommand
uint32_t stateSid = 0; // service id carrying the status/addr-table queries

// Subcommand (rpcNum -> semantic) mapping. 0 = that semantic is unused.
uint32_t submitFno = 0; // submit-command-buffer
uint32_t getStatusFno = 0; // return statusAddr
uint32_t getAddrTableFno = 0; // return addrTableAddr
uint32_t streamOpenFno = 0; // write streamReadyValue to streamStateAddr
uint32_t channelConfigFno = 0; // set channelAllocFlagTableAddr[channel]=1 (channel from send word 0 if <16)
uint32_t stopFno = 0; // write 1 to stopCompletionFlagAddr
uint32_t benignStatusValue = 0xffffff9bu; // recv[0] for an unknown fno on a served SID

// Extra addresses the new semantics need.
uint32_t streamStateAddr = 0;
uint32_t streamReadyValue = 0;
uint32_t channelAllocFlagTableAddr = 0;
uint32_t stopCompletionFlagAddr = 0;

[[nodiscard]] bool servesSid(uint32_t sid) const
{
return (commandSid != 0u && sid == commandSid) ||
(stateSid != 0u && sid == stateSid);
}

[[nodiscard]] bool hasChecksumTables() const
{
return primarySeCheckAddr != 0u || primaryMidiCheckAddr != 0u ||
Expand Down
6 changes: 0 additions & 6 deletions ps2xRuntime/include/runtime/ps2_iop.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,11 @@

class PS2Runtime;

constexpr uint32_t IOP_SID_SNDDRV_COMMAND = 0x00000000u;
constexpr uint32_t IOP_SID_SNDDRV_STATE = 0x00000001u;
constexpr uint32_t IOP_SID_LOTR_CLFILE = 0x0000FF01u;
constexpr uint32_t IOP_SID_LOTR_SOUND = 0x00012345u;
constexpr uint32_t IOP_SID_LIBSD = 0x80000701u;
constexpr uint32_t IOP_SID_FATAL_FRAME_SDRDRV = 0x19740512u;

constexpr uint32_t IOP_RPC_SNDDRV_SUBMIT = 0x00000000u;
constexpr uint32_t IOP_RPC_SNDDRV_GET_STATUS_ADDR = 0x00000012u;
constexpr uint32_t IOP_RPC_SNDDRV_GET_ADDR_TABLE = 0x00000013u;

class ps2_iop
{
public:
Expand Down
97 changes: 92 additions & 5 deletions ps2xRuntime/src/lib/Kernel/Syscalls/RPC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -539,14 +539,30 @@ namespace ps2_syscalls
return false;
}

if (sid == IOP_SID_SNDDRV_COMMAND && rpcNum == IOP_RPC_SNDDRV_SUBMIT)
PS2SoundDriverCompatLayout compat{};
{
std::lock_guard<std::mutex> lock(g_rpc_mutex);
compat = g_soundDriverCompatLayout;
}

if (compat.commandSid == 0u && compat.stateSid == 0u)
{
return false;
}

if (!compat.servesSid(sid))
{
return false;
}

if (compat.submitFno != 0u && rpcNum == compat.submitFno)
{
handleSoundDriverCommandBuffer(rdram, runtime, sendBuf, sendSize);
return true;
}

if (sid == IOP_SID_SNDDRV_STATE &&
(rpcNum == IOP_RPC_SNDDRV_GET_STATUS_ADDR || rpcNum == IOP_RPC_SNDDRV_GET_ADDR_TABLE))
if ((compat.getStatusFno != 0u && rpcNum == compat.getStatusFno) ||
(compat.getAddrTableFno != 0u && rpcNum == compat.getAddrTableFno))
{
uint32_t responseWord = 0u;
{
Expand All @@ -556,8 +572,8 @@ namespace ps2_syscalls
return false;
}
responseWord =
(rpcNum == IOP_RPC_SNDDRV_GET_STATUS_ADDR) ? g_soundDriverRpcState.statusAddr
: g_soundDriverRpcState.addrTableAddr;
(rpcNum == compat.getStatusFno) ? g_soundDriverRpcState.statusAddr
: g_soundDriverRpcState.addrTableAddr;
}

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

if (compat.streamOpenFno != 0u && rpcNum == compat.streamOpenFno)
{
{
std::lock_guard<std::mutex> lock(g_rpc_mutex);
if (compat.streamStateAddr != 0u)
{
(void)writeGuestU32(rdram, compat.streamStateAddr, compat.streamReadyValue);
}
}

if (recvBuf && recvSize >= sizeof(uint32_t))
{
(void)writeGuestU32(rdram, recvBuf, 0u);
resultPtr = recvBuf;
}

signalNowaitCompletion = true;
return true;
}

if (compat.channelConfigFno != 0u && rpcNum == compat.channelConfigFno)
{
uint32_t channel = 0u;
if (sendBuf != 0u)
{
(void)readGuestU32(rdram, sendBuf, channel);
}

{
std::lock_guard<std::mutex> lock(g_rpc_mutex);
if (compat.channelAllocFlagTableAddr != 0u && channel < 16u)
{
(void)writeGuestU32(rdram, compat.channelAllocFlagTableAddr + channel * sizeof(uint32_t), 1u);
}
}

if (recvBuf && recvSize >= sizeof(uint32_t))
{
(void)writeGuestU32(rdram, recvBuf, 0u);
resultPtr = recvBuf;
}

signalNowaitCompletion = true;
return true;
}

if (compat.stopFno != 0u && rpcNum == compat.stopFno)
{
{
std::lock_guard<std::mutex> lock(g_rpc_mutex);
if (compat.stopCompletionFlagAddr != 0u)
{
(void)writeGuestU32(rdram, compat.stopCompletionFlagAddr, 1u);
}
}

if (recvBuf && recvSize >= sizeof(uint32_t))
{
(void)writeGuestU32(rdram, recvBuf, 0u);
resultPtr = recvBuf;
}

signalNowaitCompletion = true;
return true;
}

if (recvBuf && recvSize >= sizeof(uint32_t))
{
(void)writeGuestU32(rdram, recvBuf, compat.benignStatusValue);
}

return false;
}

Expand Down
12 changes: 12 additions & 0 deletions ps2xRuntime/src/lib/game_overrides.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,18 @@ namespace
layout.busyFlagAddr = 0x01E212C8u;
layout.completionCallbacks = {0x002EAC20u, 0x002EAC30u, 0x002FAC20u, 0x002FAC30u};
layout.clearBusyCallbacks = {0x002EAC30u, 0x002FAC30u};

// Service SID + subcommand (fno) numbers RE:CVX's sound driver speaks.
// On upstream main these were the hardcoded placeholder constants
// IOP_SID_SNDDRV_STATE / IOP_RPC_SNDDRV_GET_STATUS_ADDR / _GET_ADDR_TABLE;
// they must now be carried per-game so the getStatus RPC provisions the
// status/addr-table pool (which the sceSifGetOtherData checksum backfill
// depends on). The old submit path used placeholder SID 0 / fno 0 (a
// non-real service that never matched a live call) and is intentionally
// left unconfigured — the 0-sentinel design cannot express SID 0 / fno 0.
layout.stateSid = 1u;
layout.getStatusFno = 0x12u;
layout.getAddrTableFno = 0x13u;
ps2_syscalls::setSoundDriverCompatLayout(layout);
}

Expand Down
6 changes: 0 additions & 6 deletions ps2xRuntime/src/lib/ps2_debug_panel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,6 @@ namespace
{
switch (sid)
{
case IOP_SID_SNDDRV_COMMAND:
return "SNDDRV command";
case IOP_SID_SNDDRV_STATE:
return "SNDDRV state";
case IOP_SID_LIBSD:
return "LIBSD";
case IOP_SID_FATAL_FRAME_SDRDRV:
Expand Down Expand Up @@ -1085,8 +1081,6 @@ namespace
bool dynamic;
};
const ServiceRow services[] = {
{"SNDDRV command", IOP_SID_SNDDRV_COMMAND, false},
{"SNDDRV state", IOP_SID_SNDDRV_STATE, false},
{"LIBSD", IOP_SID_LIBSD, false},
{"Fatal Frame SDRDRV", IOP_SID_FATAL_FRAME_SDRDRV, false},
{"LOTR SOUND", IOP_SID_LOTR_SOUND, false},
Expand Down
87 changes: 87 additions & 0 deletions ps2xTest/src/ps2_sif_dma_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "ps2_runtime.h"
#include "ps2_syscalls.h"
#include "ps2_stubs.h"
#include "game_overrides.h"

#include <array>
#include <cstdint>
Expand Down Expand Up @@ -898,6 +899,8 @@ void register_ps2_sif_dma_tests()
PS2SoundDriverCompatLayout compat{};
compat.primarySeCheckAddr = kPrimarySeCheckAddr;
compat.primaryMidiCheckAddr = kPrimaryMidiCheckAddr;
compat.stateSid = 1u;
compat.getStatusFno = 0x12u;
ps2_syscalls::setSoundDriverCompatLayout(compat);

constexpr uint32_t kClientAddr = 0x00023500u;
Expand Down Expand Up @@ -947,6 +950,88 @@ void register_ps2_sif_dma_tests()
"live midi_sum for the active bank should not be clobbered by compat check arrays");
});

tc.Run("RE:CVX sound-driver override provisions status pool for sceSifGetOtherData backfill", [](TestCase &t)
{
// Real-path regression guard for the RE:CVX override migration.
// Drives the actual SifCallRpc(getStatus) -> sceSifGetOtherData path with the
// layout supplied ONLY by the game override (applyMatching), and asserts the
// check-array -> status backfill actually ran. If applyRecvxSoundDriverCompat
// is not migrated to carry stateSid/getStatusFno, the getStatus RPC is not
// served, g_soundDriverRpcState.statusAddr is never provisioned (getStatus
// returns a zero src address), the backfill never fires, and this test fails.
TestEnv env;

constexpr uint32_t kRdAddr = 0x00024300u;
constexpr uint32_t kDstAddr = 0x00024400u;
constexpr uint32_t kSize = 0x42u;
constexpr uint32_t kPrimarySeCheckAddr = 0x01E0EF10u; // matches the RE:CVX override
constexpr uint32_t kPrimaryMidiCheckAddr = 0x01E0EF20u; // matches the RE:CVX override
constexpr uint32_t kMidiSumOffset = 0x1Eu;
constexpr uint32_t kSeSumOffset = 0x26u;
constexpr uint32_t kLiveBank = 0u;
constexpr uint32_t kPendingBank = 1u;

// Provision the layout via the real RE:CVX game override (elf slus_201.84).
ps2_game_overrides::applyMatching(env.runtime, "slus_201.84", 0u);

constexpr uint32_t kClientAddr = 0x00024500u;
constexpr uint32_t kRecvAddr = 0x00024600u;
constexpr uint32_t kSid = 1u;

ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime);
setRegU32(env.ctx, 4, kClientAddr);
setRegU32(env.ctx, 5, kSid);
setRegU32(env.ctx, 6, 0u);
ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime);
t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for sound-driver sid");

setRegU32(env.ctx, 4, kClientAddr);
setRegU32(env.ctx, 5, 0x12u);
setRegU32(env.ctx, 6, 0u);
setRegU32(env.ctx, 7, 0u);
setRegU32(env.ctx, 8, 0u);
setRegU32(env.ctx, 9, kRecvAddr);
setRegU32(env.ctx, 10, 4u);
setRegU32(env.ctx, 11, 0u);
ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime);
const uint32_t kSrcAddr = readGuestU32(env.rdram.data(), kRecvAddr);

// The override must have provisioned a nonzero status pool address; without the
// stateSid/getStatusFno migration the getStatus RPC is unhandled and this is 0.
t.IsTrue(kSrcAddr != 0u,
"getStatus RPC (served only via the migrated override) must return a nonzero status address");

std::memset(env.rdram.data() + kDstAddr, 0, kSize);
std::memset(env.rdram.data() + kRdAddr, 0, sizeof(SifRpcReceiveData));

writeGuestS16(env.rdram.data(), kSrcAddr + kSeSumOffset + (kLiveBank * 2u), static_cast<int16_t>(0x1111));
writeGuestS16(env.rdram.data(), kSrcAddr + kMidiSumOffset + (kLiveBank * 2u), static_cast<int16_t>(0x2222));

writeGuestS16(env.rdram.data(), kPrimarySeCheckAddr + (kPendingBank * 2u), static_cast<int16_t>(0x3333));
writeGuestS16(env.rdram.data(), kPrimaryMidiCheckAddr + (kPendingBank * 2u), static_cast<int16_t>(0x4444));

setRegU32(env.ctx, 4, kRdAddr);
setRegU32(env.ctx, 5, kSrcAddr);
setRegU32(env.ctx, 6, kDstAddr);
setRegU32(env.ctx, 7, kSize);
ps2_stubs::sceSifGetOtherData(env.rdram.data(), &env.ctx, &env.runtime);

t.Equals(getRegS32(env.ctx, 2), 0,
"sceSifGetOtherData should succeed for sound-status transfer");
t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kSeSumOffset + (kLiveBank * 2u)),
static_cast<int16_t>(0x1111),
"existing live se_sum values should remain intact");
t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kMidiSumOffset + (kLiveBank * 2u)),
static_cast<int16_t>(0x2222),
"existing live midi_sum values should remain intact");
t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kSeSumOffset + (kPendingBank * 2u)),
static_cast<int16_t>(0x3333),
"zero se_sum slots should backfill from the override's compat tables (proves the status pool was provisioned)");
t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kMidiSumOffset + (kPendingBank * 2u)),
static_cast<int16_t>(0x4444),
"zero midi_sum slots should backfill from the override's compat tables (proves the status pool was provisioned)");
});

tc.Run("sceSifGetOtherData backfills zero sound-status sums for later banks", [](TestCase &t)
{
TestEnv env;
Expand All @@ -964,6 +1049,8 @@ void register_ps2_sif_dma_tests()
PS2SoundDriverCompatLayout compat{};
compat.primarySeCheckAddr = kPrimarySeCheckAddr;
compat.primaryMidiCheckAddr = kPrimaryMidiCheckAddr;
compat.stateSid = 1u;
compat.getStatusFno = 0x12u;
ps2_syscalls::setSoundDriverCompatLayout(compat);

constexpr uint32_t kClientAddr = 0x00023900u;
Expand Down
Loading