mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 16:59:35 -04:00
75d729ce40
* refactor: from guest threads to EE scheduler * feat: bad wip mpeg fix for code veronica * feat: cheap copy from host feat: small perf o vsync tick * feat: added EE clock Hz fix: fix MPEG out of sync with new EE refactor * fix: fix lotr tests * fix: fix cri dtx loading fix: fix wrong mmi instruction translation fix: fix thread info params feat: added EE timers decoder and consumer feat: split SFI and IOP memory to prevent collision and overrides * feat: revert wrong changes * refactor: change GS architecture * feat: IOP emulator refactor: codegen to catch callbacks on mips code feat: added a lot of entries or IOP emulator * feat: analyzer resolve the complete constant-producing sequence with five-instruction backward scan stopped at LUI and therefore * feat: remove recompiled version of GetRomName refactor: split IOP emulator logic feat: added more HLE IOP modules feat: added ps2_path * eat: enhance ELF parser with improved callable entry detection and control flow analysis * feat: update memory hint handling and enhance entry point discovery logic * feat: add SET_GPR_ZE32 macro for zero-extending loads with unsigned semantics * refactor: Refactor PS2 IOP Host Adapter and Memory Management feat: Added PS2Vfs for virtual file system operations, including file opening, reading, writing, and path resolution. feat: Improve VIF1 data processing to handle GIF image packets more efficiently. * feat: added a lot of tests * fix: fix texture caching feat: wip multi version on dbcman * feat: remove LLE IOPs
316 lines
14 KiB
C++
316 lines
14 KiB
C++
#include "iop_compat_test_support.h"
|
|
|
|
#include <limits>
|
|
|
|
namespace
|
|
{
|
|
using namespace iop_test;
|
|
constexpr uint32_t dbcSid = 0x80001300u;
|
|
constexpr uint32_t dbcVersion = 0x80001363u;
|
|
constexpr uint32_t mcSid = 0x80000400u;
|
|
|
|
void dbcDefault()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(!iop.canBindRpc(dbcSid), "unloaded DBCMAN must stay dormant");
|
|
require(iop.loadModule("rom0:DBCMAN").moduleId > 0, "DBCMAN load failed");
|
|
auto query = request(dbcSid, dbcVersion);
|
|
require(iop.handleRpc(query).handled, "version RPC not handled");
|
|
for (uint32_t i = 0u; i < 4u; ++i)
|
|
require(host.word(0x800u + i * 4u) == 0x0310u, "DBCMAN target version changed");
|
|
}
|
|
|
|
|
|
|
|
void dbcResetAndReconfigure()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:DBCMAN").moduleId > 0, "load failed");
|
|
require(iop.handleRpc(request(dbcSid, dbcVersion)).handled, "RPC failed");
|
|
require(host.word(0x800u) == 0x0310u, "unexpected DBCMAN version");
|
|
iop.reset();
|
|
require(!iop.canBindRpc(dbcSid), "reset retained a module route");
|
|
require(metric(iop, "dbcman", "version_queries") == 0u, "query counter not reset");
|
|
require(iop.loadModule("rom0:DBCMAN").moduleId > 0, "reload failed");
|
|
require(iop.handleRpc(request(dbcSid, dbcVersion)).handled, "RPC failed");
|
|
require(host.word(0x800u) == 0x0310u, "IOP reboot changed target version");
|
|
}
|
|
|
|
|
|
void dbcReplyBounds()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:DBCMAN").moduleId > 0, "load failed");
|
|
for (uint32_t size = 0u; size <= 24u; ++size)
|
|
{
|
|
host.fill(0x7FCu, 40u);
|
|
require(iop.handleRpc(request(dbcSid, dbcVersion, size)).handled, "RPC failed");
|
|
const uint32_t written = std::min(size / 4u, 4u) * 4u;
|
|
for (uint32_t offset = written; offset < 32u; ++offset)
|
|
require(host.guest[0x800u + offset] == 0xCCu, "reply wrote past whole-word payload");
|
|
require(host.word(0x7FCu) == 0xCCCCCCCCu, "reply underflow");
|
|
}
|
|
auto query = request(dbcSid, dbcVersion);
|
|
query.receive.address = 0u;
|
|
const size_t writes = host.guestWrites;
|
|
require(iop.handleRpc(query).handled && host.guestWrites == writes, "null reply was written");
|
|
}
|
|
|
|
void dbcNoAddressWrap()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:DBCMAN").moduleId > 0, "load failed");
|
|
auto query = request(dbcSid, dbcVersion);
|
|
query.receive.address = 0xFFFFFFF8u;
|
|
require(iop.handleRpc(query).handled, "RPC failed");
|
|
require(host.word(0u) == 0xCCCCCCCCu && host.word(4u) == 0xCCCCCCCCu,
|
|
"overflowed reply corrupted low guest addresses");
|
|
require(metric(iop, "dbcman", "failed_version_replies") == 1u, "invalid reply not recorded");
|
|
}
|
|
|
|
void dbcNoRequestGuessing()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:DBCMAN").moduleId > 0, "load failed");
|
|
auto query = request(dbcSid, dbcVersion);
|
|
const std::array<uint32_t, 4> randomArguments{0x0310u, 0x00010000u, 0u, 0xFFFFu};
|
|
require(host.writeGuest(0x600u, randomArguments.data(), sizeof(randomArguments)), "write failed");
|
|
query.send = {0x600u, sizeof(randomArguments)};
|
|
require(iop.handleRpc(query).handled, "RPC failed");
|
|
require(host.word(0x800u) == 0x0310u, "send buffer was guessed to be a requested version");
|
|
}
|
|
|
|
void dbcPhysicalServerWins()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:DBCMAN").moduleId > 0, "HLE load failed");
|
|
auto image = rpcServer(dbcSid, 0xDEADBEEFu);
|
|
image.install(host);
|
|
auto physical = iop.loadModuleBuffer(0x1000u);
|
|
require(physical.moduleId > 0 && physical.startResult == 0, "physical IRX failed");
|
|
require(iop.handleRpc(request(dbcSid, dbcVersion)).handled, "physical RPC not handled");
|
|
require(host.word(0x800u) == 0xDEADBEEFu, "HLE overwrote physical server version");
|
|
require(metric(iop, "dbcman", "version_queries") == 0u, "HLE ran after physical service");
|
|
require(iop.stopModule(physical.moduleId), "physical stop failed");
|
|
require(iop.handleRpc(request(dbcSid, dbcVersion)).handled, "HLE fallback not restored");
|
|
require(host.word(0x800u) == 0x0310u, "wrong HLE version after physical stop");
|
|
}
|
|
|
|
void mcNewInit()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:XMCSERV").moduleId > 0, "XMCSERV load failed");
|
|
require(iop.handleRpc(request(mcSid, 0xFEu, 16u)).handled, "init RPC failed");
|
|
require(host.word(0x800u) == 0u && host.word(0x804u) == 0x0205u && host.word(0x808u) == 0x0206u,
|
|
"new memory-card init layout changed");
|
|
require(host.word(0x80Cu) == 0xCCCCCCCCu, "new init wrote beyond 12-byte response");
|
|
}
|
|
|
|
void mcOldInit()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:MCSERV").moduleId > 0, "MCSERV load failed");
|
|
require(iop.handleRpc(request(mcSid, 0x70u, 16u)).handled, "init RPC failed");
|
|
require(host.word(0x800u) == 0u && host.word(0x804u) == 0xCCCCCCCCu,
|
|
"old init leaked extended protocol versions");
|
|
}
|
|
|
|
void mcInitFailure()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:MCSERV").moduleId > 0, "MCSERV load failed");
|
|
host.initResult = -5;
|
|
for (uint32_t operation : {0x70u, 0xFEu})
|
|
{
|
|
require(iop.handleRpc(request(mcSid, operation)).handled, "init RPC failed");
|
|
require(static_cast<int32_t>(host.word(0x800u)) == -5, "init failure reported as success");
|
|
}
|
|
}
|
|
|
|
void mcReplyBounds()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:MCSERV").moduleId > 0, "MCSERV load failed");
|
|
for (uint32_t operation : {0x70u, 0xFEu})
|
|
for (uint32_t size = 0u; size <= 20u; ++size)
|
|
{
|
|
host.fill(0x7FCu, 32u);
|
|
require(iop.handleRpc(request(mcSid, operation, size)).handled, "init RPC failed");
|
|
const uint32_t words = operation == 0xFEu ? 3u : 1u;
|
|
const uint32_t written = std::min(size / 4u, words) * 4u;
|
|
for (uint32_t offset = written; offset < 24u; ++offset)
|
|
require(host.guest[0x800u + offset] == 0xCCu, "init clobbered response tail");
|
|
require(host.word(0x7FCu) == 0xCCCCCCCCu, "init underflowed buffer");
|
|
}
|
|
auto query = request(mcSid, 0xFEu);
|
|
query.receive.address = 0xFFFFFFF8u;
|
|
require(iop.handleRpc(query).handled, "RPC failed");
|
|
require(host.word(0u) == 0xCCCCCCCCu, "init overflowed guest address");
|
|
}
|
|
|
|
void mcShortNamePacket()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:MCSERV").moduleId > 0, "MCSERV load failed");
|
|
auto query = request(mcSid, 0x02u, 4u);
|
|
query.send = {0x1000u, 20u};
|
|
host.fill(0x1000u, 1044u, 0u);
|
|
const size_t calls = host.cardCalls.size();
|
|
require(iop.handleRpc(query).handled, "RPC failed");
|
|
require(host.cardCalls.size() == calls, "short packet read a filename beyond send.size");
|
|
require(static_cast<int32_t>(host.word(0x800u)) == -5, "short packet not rejected");
|
|
}
|
|
|
|
void mcFullNamePacket()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:MCSERV").moduleId > 0, "MCSERV load failed");
|
|
const std::array<uint32_t, 5> header{1u, 0u, 1u, 0u, 0u};
|
|
host.fill(0x1000u, 1044u, 0u);
|
|
require(host.writeGuest(0x1000u, header.data(), sizeof(header)), "packet header write failed");
|
|
constexpr char name[] = "/save.dat";
|
|
require(host.writeGuest(0x1014u, name, sizeof(name)), "packet filename write failed");
|
|
for (uint32_t operation : {0x02u, 0x71u})
|
|
{
|
|
auto query = request(mcSid, operation, 4u);
|
|
query.send = {0x1000u, 1044u};
|
|
const size_t before = host.cardCalls.size();
|
|
require(iop.handleRpc(query).handled, "open RPC failed");
|
|
require(host.cardCalls.size() == before + 1u, "valid packet not dispatched");
|
|
const auto &call = host.cardCalls.back();
|
|
require(call.operation == MemoryCardOperation::Open &&
|
|
call.arguments[0] == 1u && call.arguments[1] == 0u &&
|
|
call.arguments[2] == 0x1014u && call.arguments[3] == 1u,
|
|
"valid name packet decoded incorrectly");
|
|
}
|
|
}
|
|
|
|
void mcStatusBounds()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:MCSERV").moduleId > 0, "MCSERV load failed");
|
|
for (uint32_t size = 0u; size <= 20u; ++size)
|
|
{
|
|
host.fill(0x7FCu, 32u);
|
|
require(iop.handleRpc(request(mcSid, 0xFFFFFFFFu, size)).handled, "RPC failed");
|
|
const uint32_t written = size >= 4u ? 4u : 0u;
|
|
if (written != 0u)
|
|
require(static_cast<int32_t>(host.word(0x800u)) == -5, "missing error status");
|
|
for (uint32_t offset = written; offset < 24u; ++offset)
|
|
require(host.guest[0x800u + offset] == 0xCCu, "status clobbered receive tail");
|
|
require(host.word(0x7FCu) == 0xCCCCCCCCu, "status underflowed receive buffer");
|
|
}
|
|
auto query = request(mcSid, 0xFFFFFFFFu, 16u);
|
|
query.receive.address = 0xFFFFFFFCu;
|
|
require(iop.handleRpc(query).handled, "RPC failed");
|
|
require(host.word(0u) == 0xCCCCCCCCu && host.word(4u) == 0xCCCCCCCCu,
|
|
"status reply wrapped and zeroed low guest memory");
|
|
}
|
|
|
|
void moduleAliases()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
for (const char *path : {"rom0:XSIO2MAN", "rom0:XPADMAN", "rom0:XMCMAN"})
|
|
{
|
|
auto result = iop.loadModule(path);
|
|
require(result.moduleId > 0 && result.startResult == 0, "known extended module rejected");
|
|
}
|
|
require(!iop.canBindRpc(mcSid), "XMCMAN alone enabled a memory-card RPC server");
|
|
const auto module = iop.loadModule("CDROM0:\\IOP\\xMcSeRv.IrX;1");
|
|
require(module.moduleId > 0 && iop.canBindRpc(mcSid), "normalized XMCSERV alias not activated");
|
|
require(iop.stopModule(module.moduleId) && !iop.canBindRpc(mcSid), "stopped alias remained active");
|
|
}
|
|
|
|
void unknownModulesStayUnknown()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
for (const char *name : {"MC2_D.IRX", "DS2U_D.IRX", "CDVDSTM.IRX", "SDRDRV.IRX", "EZPCM.IRX", "ANYTHING_D.IRX"})
|
|
{
|
|
const auto result = iop.loadModule(std::string("host0:IOPModules/") + name);
|
|
require(result.moduleId < 0 && result.startResult < 0, "unsupported module got a fake success");
|
|
}
|
|
require(!iop.canBindRpc(0x19740512u), "game-specific SDRDRV activated globally");
|
|
}
|
|
|
|
void moduleLifetime()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
const auto a = iop.loadModule("rom0:DBCMAN");
|
|
const auto b = iop.loadModule("rom0:dbcman.irx");
|
|
const auto alias = iop.loadModule("rom0:DBCM");
|
|
require(a.moduleId > 0 && a.moduleId == b.moduleId && alias.moduleId > 0, "module IDs unstable");
|
|
require(iop.stopModule(a.moduleId) && iop.canBindRpc(dbcSid), "first release removed shared route");
|
|
require(iop.stopModule(b.moduleId) && iop.canBindRpc(dbcSid), "remaining alias not honored");
|
|
require(iop.stopModule(alias.moduleId) && !iop.canBindRpc(dbcSid), "last release retained route");
|
|
}
|
|
|
|
void loaderDiagnostics()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("host0:LIBSD.IRX").moduleId > 0, "LIBSD fallback failed");
|
|
require(iop.loadModule("host0:MISSING.IRX").moduleId < 0, "unknown load accepted");
|
|
for (unsigned i = 0u; i < 100u; ++i)
|
|
(void)iop.loadModule("host0:MISSING.IRX");
|
|
auto snapshot = iop.debugSnapshot();
|
|
require(snapshot.diagnostics.size() == 2u, "final loader outcomes not deduplicated");
|
|
require(snapshot.diagnostics[0].find("[IOP:HLE]") != std::string::npos, "no fallback diagnostic");
|
|
require(snapshot.diagnostics[1].find("no HLE provider") != std::string::npos, "no final failure diagnostic");
|
|
for (unsigned i = 0u; i < 100u; ++i)
|
|
(void)iop.loadModule("rom0:missing" + std::to_string(i));
|
|
require(iop.debugSnapshot().diagnostics.size() <= 32u, "unbounded module diagnostics");
|
|
iop.reset();
|
|
require(iop.debugSnapshot().diagnostics.empty(), "stale load outcomes survived reset");
|
|
}
|
|
|
|
void libsdUnchanged()
|
|
{
|
|
Host host;
|
|
IopSubsystem iop(host);
|
|
require(iop.loadModule("rom0:LIBSD").moduleId > 0, "LIBSD load failed");
|
|
require(iop.handleRpc(request(0x80000701u, 0x8010u)).handled, "LIBSD RPC not handled");
|
|
require(host.audioCalls == 1u, "DBCMAN option intercepted LIBSD RPC");
|
|
}
|
|
}
|
|
|
|
int main()
|
|
{
|
|
const Test tests[] = {
|
|
{"DBCMAN default and dormant route", dbcDefault},
|
|
{"DBCMAN reboot and reconfiguration", dbcResetAndReconfigure},
|
|
{"DBCMAN bounded whole-word response", dbcReplyBounds},
|
|
{"DBCMAN rejects wrapping reply addresses", dbcNoAddressWrap},
|
|
{"DBCMAN does not infer version from arbitrary RPC payload", dbcNoRequestGuessing},
|
|
{"Physical DBCMAN server wins over configured HLE", dbcPhysicalServerWins},
|
|
{"XMCSERV init status and two version fields", mcNewInit},
|
|
{"Old MCSERV init is status only", mcOldInit},
|
|
{"MCSERV propagates initialization failure", mcInitFailure},
|
|
{"MCSERV response bounds for both dialects", mcReplyBounds},
|
|
{"MCSERV rejects truncated name packet", mcShortNamePacket},
|
|
{"MCSERV accepts complete name packets in both dialects", mcFullNamePacket},
|
|
{"MCSERV status replies preserve bounds and cannot wrap", mcStatusBounds},
|
|
{"Extended module aliases and activation", moduleAliases},
|
|
{"Unsupported debug and game IRX stay unsupported", unknownModulesStayUnknown},
|
|
{"HLE repeated loads and alias lifetime", moduleLifetime},
|
|
{"Loader outcomes are bounded and resettable", loaderDiagnostics},
|
|
{"LIBSD audio dispatch is unchanged", libsdUnchanged},
|
|
};
|
|
return run(tests);
|
|
}
|