mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
93e221feaa
* feat: remove memory and pad from stub section * feat: add support for resume entry targets in CodeGenerator (this allow jumps in address outside function) feat: refactor entry point discovery one more try to reduce big generated file * feat: optmizations for release build * feat: remove unused file * feat: added some test cases for code gen * feat: added log macro and remove win specific code * feat: refactor runtime folder structure feat: added reset sound driver RPC state and compatibility layout feat: rename and added new test feat: update RPC calls to use defined constants feat: added more PSMC(16, 32) feat: change cd read to try find the asset ignoring case sensitive fix: fix some render problems feat: add logs on pad feat: added more RPC handles * feat: added game override for code veronica * feat: apply vita patch * feat: flags to disable build * feat: fix merges feat: break a lot of tests * feat: better throw error on empty cd path feat: remove recompiler unusde function feat: apply missing patch * feat: gamedp is now part of lib feat: missing file * feat: small cleanup * feat: missing vita changes * feat: fix more merge * feat: fix tests * feat: last missing feature * feat: added missing import * feat: rename test local functions * feat: init syscall on ps2 list * feat: added DMA helpers * feat: faster builds feat: more implement for darkcloud * feat: back missing file * feat: added missing includes * feat: remove test * feat: missing include * feat: read register funtion * feat: build fix * feat: force exit on detach thread
625 lines
26 KiB
C++
625 lines
26 KiB
C++
#include "MiniTest.h"
|
|
#include "ps2_runtime.h"
|
|
#include "ps2_syscalls.h"
|
|
#include "ps2_stubs.h"
|
|
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <vector>
|
|
#include <cstring>
|
|
#include <chrono>
|
|
|
|
using namespace ps2_syscalls;
|
|
|
|
namespace
|
|
{
|
|
// Guest memory address ranges for test data
|
|
constexpr uint32_t GUEST_STRING_AREA_START = 0x1000;
|
|
constexpr uint32_t GUEST_BUFFER_AREA_START = 0x2000;
|
|
constexpr uint32_t GUEST_STACK_AREA_START = 0x6000;
|
|
constexpr uint32_t GUEST_MC_SYNC_CMD_ADDR = GUEST_BUFFER_AREA_START + 0x1C00;
|
|
constexpr uint32_t GUEST_MC_SYNC_RESULT_ADDR = GUEST_BUFFER_AREA_START + 0x1C04;
|
|
constexpr uint32_t GUEST_MC_TABLE_ADDR = GUEST_BUFFER_AREA_START + 0x2000;
|
|
|
|
// Common file I/O flag combinations
|
|
constexpr uint32_t PS2_FIO_WRITE_CREATE_TRUNC =
|
|
PS2_FIO_O_WRONLY | PS2_FIO_O_CREAT | PS2_FIO_O_TRUNC;
|
|
|
|
struct SceMcStDateTime
|
|
{
|
|
uint8_t resv2;
|
|
uint8_t sec;
|
|
uint8_t min;
|
|
uint8_t hour;
|
|
uint8_t day;
|
|
uint8_t month;
|
|
uint16_t year;
|
|
};
|
|
|
|
struct SceMcTblGetDir
|
|
{
|
|
SceMcStDateTime create;
|
|
SceMcStDateTime modify;
|
|
uint32_t fileSizeByte;
|
|
uint16_t attrFile;
|
|
uint16_t reserve1;
|
|
uint32_t reserve2;
|
|
uint32_t pdaAplNo;
|
|
char entryName[32];
|
|
};
|
|
|
|
static_assert(sizeof(SceMcTblGetDir) == 64, "sceMcTblGetDir size mismatch");
|
|
|
|
void setRegU32(R5900Context &ctx, int reg, uint32_t value)
|
|
{
|
|
ctx.r[reg] = _mm_set_epi64x(0, static_cast<int64_t>(value));
|
|
}
|
|
|
|
int32_t getRegS32(const R5900Context *ctx, int reg)
|
|
{
|
|
return static_cast<int32_t>(::getRegU32(ctx, reg));
|
|
}
|
|
|
|
void writeGuestString(uint8_t *rdram, uint32_t addr, const std::string &value)
|
|
{
|
|
std::memcpy(rdram + addr, value.c_str(), value.size() + 1);
|
|
}
|
|
|
|
void writeGuestU32(uint8_t *rdram, uint32_t addr, uint32_t value)
|
|
{
|
|
std::memcpy(rdram + addr, &value, sizeof(value));
|
|
}
|
|
|
|
int32_t readGuestS32(const uint8_t *rdram, uint32_t addr)
|
|
{
|
|
int32_t value = 0;
|
|
std::memcpy(&value, rdram + addr, sizeof(value));
|
|
return value;
|
|
}
|
|
|
|
uint32_t readGuestU32(const uint8_t *rdram, uint32_t addr)
|
|
{
|
|
uint32_t value = 0;
|
|
std::memcpy(&value, rdram + addr, sizeof(value));
|
|
return value;
|
|
}
|
|
|
|
void clearContext(R5900Context &ctx)
|
|
{
|
|
std::memset(&ctx, 0, sizeof(ctx));
|
|
}
|
|
|
|
void writeStackArg(std::vector<uint8_t> &rdram, R5900Context &ctx, uint32_t slotIndex, uint32_t value)
|
|
{
|
|
const uint32_t sp = ::getRegU32(&ctx, 29);
|
|
writeGuestU32(rdram.data(), sp + 16u + slotIndex * sizeof(uint32_t), value);
|
|
}
|
|
|
|
int32_t syncMc(std::vector<uint8_t> &rdram, int32_t *cmdOut = nullptr)
|
|
{
|
|
R5900Context syncCtx{};
|
|
setRegU32(syncCtx, 4, 0u);
|
|
setRegU32(syncCtx, 5, GUEST_MC_SYNC_CMD_ADDR);
|
|
setRegU32(syncCtx, 6, GUEST_MC_SYNC_RESULT_ADDR);
|
|
ps2_stubs::sceMcSync(rdram.data(), &syncCtx, nullptr);
|
|
|
|
if (cmdOut)
|
|
{
|
|
*cmdOut = readGuestS32(rdram.data(), GUEST_MC_SYNC_CMD_ADDR);
|
|
}
|
|
return readGuestS32(rdram.data(), GUEST_MC_SYNC_RESULT_ADDR);
|
|
}
|
|
|
|
struct TempPaths
|
|
{
|
|
std::filesystem::path base;
|
|
std::filesystem::path mcRoot;
|
|
std::filesystem::path cdRoot;
|
|
|
|
~TempPaths()
|
|
{
|
|
std::error_code ec;
|
|
std::filesystem::remove_all(base, ec);
|
|
}
|
|
};
|
|
|
|
TempPaths makeTempPaths()
|
|
{
|
|
TempPaths paths;
|
|
const auto now = std::chrono::steady_clock::now().time_since_epoch().count();
|
|
paths.base = std::filesystem::temp_directory_path()
|
|
/ ("ps2recomp-mc0-" + std::to_string(now));
|
|
paths.mcRoot = paths.base / "mcroot";
|
|
paths.cdRoot = paths.base / "cdroot";
|
|
std::filesystem::create_directories(paths.mcRoot);
|
|
std::filesystem::create_directories(paths.cdRoot);
|
|
return paths;
|
|
}
|
|
|
|
struct TestContext
|
|
{
|
|
TempPaths paths;
|
|
std::vector<uint8_t> rdram;
|
|
R5900Context ctx;
|
|
|
|
TestContext() : paths(makeTempPaths()), rdram(PS2_RAM_SIZE, 0)
|
|
{
|
|
PS2Runtime::IoPaths ioPaths;
|
|
ioPaths.elfDirectory = paths.cdRoot;
|
|
ioPaths.hostRoot = paths.cdRoot;
|
|
ioPaths.cdRoot = paths.cdRoot;
|
|
ioPaths.mcRoot = paths.mcRoot;
|
|
PS2Runtime::setIoPaths(ioPaths);
|
|
}
|
|
};
|
|
}
|
|
|
|
void register_ps2_runtime_io_tests()
|
|
{
|
|
MiniTest::Case("PS2RuntimeIO", [](TestCase &tc)
|
|
{
|
|
tc.Run("mc0 directory creation", [](TestCase &t)
|
|
{
|
|
TestContext test;
|
|
|
|
const std::string dirPath = "mc0:/SAVEDATA";
|
|
const uint32_t dirAddr = GUEST_STRING_AREA_START;
|
|
writeGuestString(test.rdram.data(), dirAddr, dirPath);
|
|
|
|
setRegU32(test.ctx, 4, dirAddr);
|
|
fioMkdir(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
const int32_t result = getRegS32(&test.ctx, 2);
|
|
t.IsTrue(result >= 0, "fioMkdir should succeed for mc0: directory");
|
|
|
|
const std::filesystem::path expected = test.paths.mcRoot / "SAVEDATA";
|
|
t.IsTrue(std::filesystem::exists(expected),
|
|
"Directory should exist under mcRoot");
|
|
t.IsTrue(std::filesystem::is_directory(expected),
|
|
"Created path should be a directory");
|
|
});
|
|
|
|
tc.Run("mc0 file write operations", [](TestCase &t)
|
|
{
|
|
TestContext test;
|
|
|
|
// Setup: create directory first
|
|
const std::string dirPath = "mc0:/SAVEDATA";
|
|
const uint32_t dirAddr = GUEST_STRING_AREA_START;
|
|
writeGuestString(test.rdram.data(), dirAddr, dirPath);
|
|
setRegU32(test.ctx, 4, dirAddr);
|
|
fioMkdir(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
// Test: open file for writing
|
|
const std::string filePath = "mc0:/SAVEDATA/test.txt";
|
|
const uint32_t fileAddr = GUEST_STRING_AREA_START + 0x100;
|
|
writeGuestString(test.rdram.data(), fileAddr, filePath);
|
|
|
|
setRegU32(test.ctx, 4, fileAddr);
|
|
setRegU32(test.ctx, 5, PS2_FIO_WRITE_CREATE_TRUNC);
|
|
fioOpen(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
const int32_t fd = getRegS32(&test.ctx, 2);
|
|
t.IsTrue(fd >= 0, "fioOpen should return valid file descriptor");
|
|
|
|
// Write payload
|
|
const std::string payload = "hello mc0";
|
|
const uint32_t bufAddr = GUEST_BUFFER_AREA_START;
|
|
std::memcpy(test.rdram.data() + bufAddr, payload.data(), payload.size());
|
|
|
|
setRegU32(test.ctx, 4, static_cast<uint32_t>(fd));
|
|
setRegU32(test.ctx, 5, bufAddr);
|
|
setRegU32(test.ctx, 6, static_cast<uint32_t>(payload.size()));
|
|
fioWrite(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
const int32_t bytesWritten = getRegS32(&test.ctx, 2);
|
|
t.Equals(bytesWritten, static_cast<int32_t>(payload.size()),
|
|
"fioWrite should write all bytes");
|
|
|
|
// Close file
|
|
setRegU32(test.ctx, 4, static_cast<uint32_t>(fd));
|
|
fioClose(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
const int32_t closeResult = getRegS32(&test.ctx, 2);
|
|
t.IsTrue(closeResult >= 0, "fioClose should succeed");
|
|
|
|
// Verify on host filesystem
|
|
const std::filesystem::path expectedPath =
|
|
test.paths.mcRoot / "SAVEDATA" / "test.txt";
|
|
t.IsTrue(std::filesystem::exists(expectedPath),
|
|
"File should exist under mcRoot");
|
|
|
|
std::ifstream in(expectedPath, std::ios::binary);
|
|
std::string readback(
|
|
(std::istreambuf_iterator<char>(in)),
|
|
std::istreambuf_iterator<char>());
|
|
t.Equals(readback, payload, "File content should match written payload");
|
|
});
|
|
|
|
tc.Run("mc0 file read operations", [](TestCase &t)
|
|
{
|
|
TestContext test;
|
|
|
|
// Setup: create directory and write file
|
|
const std::string dirPath = "mc0:/SAVEDATA";
|
|
const uint32_t dirAddr = GUEST_STRING_AREA_START;
|
|
writeGuestString(test.rdram.data(), dirAddr, dirPath);
|
|
setRegU32(test.ctx, 4, dirAddr);
|
|
fioMkdir(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
const std::string filePath = "mc0:/SAVEDATA/test.txt";
|
|
const uint32_t fileAddr = GUEST_STRING_AREA_START + 0x100;
|
|
writeGuestString(test.rdram.data(), fileAddr, filePath);
|
|
|
|
// Write data
|
|
const std::string payload = "hello mc0 read test";
|
|
const uint32_t writeBufAddr = GUEST_BUFFER_AREA_START;
|
|
std::memcpy(test.rdram.data() + writeBufAddr, payload.data(), payload.size());
|
|
|
|
setRegU32(test.ctx, 4, fileAddr);
|
|
setRegU32(test.ctx, 5, PS2_FIO_WRITE_CREATE_TRUNC);
|
|
fioOpen(test.rdram.data(), &test.ctx, nullptr);
|
|
int32_t fd = getRegS32(&test.ctx, 2);
|
|
|
|
setRegU32(test.ctx, 4, static_cast<uint32_t>(fd));
|
|
setRegU32(test.ctx, 5, writeBufAddr);
|
|
setRegU32(test.ctx, 6, static_cast<uint32_t>(payload.size()));
|
|
fioWrite(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
setRegU32(test.ctx, 4, static_cast<uint32_t>(fd));
|
|
fioClose(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
// Test: read back via fioRead
|
|
setRegU32(test.ctx, 4, fileAddr);
|
|
setRegU32(test.ctx, 5, PS2_FIO_O_RDONLY);
|
|
fioOpen(test.rdram.data(), &test.ctx, nullptr);
|
|
fd = getRegS32(&test.ctx, 2);
|
|
t.IsTrue(fd >= 0, "fioOpen for reading should succeed");
|
|
|
|
// Read into different buffer area
|
|
const uint32_t readBufAddr = GUEST_BUFFER_AREA_START + 0x1000;
|
|
std::memset(test.rdram.data() + readBufAddr, 0, payload.size());
|
|
|
|
setRegU32(test.ctx, 4, static_cast<uint32_t>(fd));
|
|
setRegU32(test.ctx, 5, readBufAddr);
|
|
setRegU32(test.ctx, 6, static_cast<uint32_t>(payload.size()));
|
|
fioRead(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
const int32_t bytesRead = getRegS32(&test.ctx, 2);
|
|
t.Equals(bytesRead, static_cast<int32_t>(payload.size()),
|
|
"fioRead should read all bytes");
|
|
|
|
std::string readback(
|
|
reinterpret_cast<const char*>(test.rdram.data() + readBufAddr),
|
|
payload.size()
|
|
);
|
|
t.Equals(readback, payload, "fioRead content should match original");
|
|
|
|
setRegU32(test.ctx, 4, static_cast<uint32_t>(fd));
|
|
fioClose(test.rdram.data(), &test.ctx, nullptr);
|
|
});
|
|
|
|
tc.Run("mc0 paths isolated from cdRoot", [](TestCase &t)
|
|
{
|
|
TestContext test;
|
|
|
|
const std::string dirPath = "mc0:/ISOLATED";
|
|
const std::string filePath = "mc0:/ISOLATED/test.txt";
|
|
const uint32_t dirAddr = GUEST_STRING_AREA_START;
|
|
const uint32_t fileAddr = GUEST_STRING_AREA_START + 0x100;
|
|
|
|
writeGuestString(test.rdram.data(), dirAddr, dirPath);
|
|
writeGuestString(test.rdram.data(), fileAddr, filePath);
|
|
|
|
// Create directory and file on mc0:
|
|
setRegU32(test.ctx, 4, dirAddr);
|
|
fioMkdir(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
setRegU32(test.ctx, 4, fileAddr);
|
|
setRegU32(test.ctx, 5, PS2_FIO_WRITE_CREATE_TRUNC);
|
|
fioOpen(test.rdram.data(), &test.ctx, nullptr);
|
|
const int32_t fd = getRegS32(&test.ctx, 2);
|
|
|
|
const std::string payload = "isolation test";
|
|
const uint32_t bufAddr = GUEST_BUFFER_AREA_START;
|
|
std::memcpy(test.rdram.data() + bufAddr, payload.data(), payload.size());
|
|
|
|
setRegU32(test.ctx, 4, static_cast<uint32_t>(fd));
|
|
setRegU32(test.ctx, 5, bufAddr);
|
|
setRegU32(test.ctx, 6, static_cast<uint32_t>(payload.size()));
|
|
fioWrite(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
setRegU32(test.ctx, 4, static_cast<uint32_t>(fd));
|
|
fioClose(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
// Verify isolation
|
|
const std::filesystem::path expectedMc =
|
|
test.paths.mcRoot / "ISOLATED" / "test.txt";
|
|
const std::filesystem::path unexpectedCd =
|
|
test.paths.cdRoot / "ISOLATED" / "test.txt";
|
|
|
|
t.IsTrue(std::filesystem::exists(expectedMc),
|
|
"mc0: file should exist under mcRoot");
|
|
t.IsFalse(std::filesystem::exists(unexpectedCd),
|
|
"mc0: file should NOT exist under cdRoot");
|
|
|
|
// Verify mcRoot directory structure
|
|
t.IsTrue(std::filesystem::exists(test.paths.mcRoot / "ISOLATED"),
|
|
"mc0: directory should exist under mcRoot");
|
|
t.IsFalse(std::filesystem::exists(test.paths.cdRoot / "ISOLATED"),
|
|
"mc0: directory should NOT exist under cdRoot");
|
|
});
|
|
|
|
tc.Run("sceMc open write read and close roundtrip through sync", [](TestCase &t)
|
|
{
|
|
TestContext test;
|
|
|
|
const uint32_t dirAddr = GUEST_STRING_AREA_START + 0x400;
|
|
const uint32_t fileAddr = GUEST_STRING_AREA_START + 0x500;
|
|
const uint32_t writeBufAddr = GUEST_BUFFER_AREA_START + 0x300;
|
|
const uint32_t readBufAddr = GUEST_BUFFER_AREA_START + 0x500;
|
|
const std::string payload = "libmc roundtrip";
|
|
|
|
writeGuestString(test.rdram.data(), dirAddr, "/SAVEDATA");
|
|
writeGuestString(test.rdram.data(), fileAddr, "/SAVEDATA/test.bin");
|
|
std::memcpy(test.rdram.data() + writeBufAddr, payload.data(), payload.size());
|
|
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, 0u);
|
|
setRegU32(test.ctx, 5, 0u);
|
|
setRegU32(test.ctx, 6, dirAddr);
|
|
ps2_stubs::sceMcMkdir(test.rdram.data(), &test.ctx, nullptr);
|
|
t.Equals(getRegS32(&test.ctx, 2), 0, "sceMcMkdir should dispatch successfully");
|
|
|
|
int32_t cmd = 0;
|
|
t.Equals(syncMc(test.rdram, &cmd), 0, "sceMcMkdir should finish successfully");
|
|
t.Equals(cmd, 0x0B, "sceMcSync should report MKDIR as the last command");
|
|
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, 0u);
|
|
setRegU32(test.ctx, 5, 0u);
|
|
setRegU32(test.ctx, 6, fileAddr);
|
|
setRegU32(test.ctx, 7, PS2_FIO_O_RDWR | PS2_FIO_O_CREAT | PS2_FIO_O_TRUNC);
|
|
ps2_stubs::sceMcOpen(test.rdram.data(), &test.ctx, nullptr);
|
|
t.Equals(getRegS32(&test.ctx, 2), 0, "sceMcOpen should dispatch successfully");
|
|
|
|
const int32_t fd = syncMc(test.rdram, &cmd);
|
|
t.IsTrue(fd > 0, "sceMcOpen should produce a positive descriptor in sceMcSync");
|
|
t.Equals(cmd, 0x02, "sceMcSync should report OPEN as the last command");
|
|
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, static_cast<uint32_t>(fd));
|
|
setRegU32(test.ctx, 5, writeBufAddr);
|
|
setRegU32(test.ctx, 6, static_cast<uint32_t>(payload.size()));
|
|
ps2_stubs::sceMcWrite(test.rdram.data(), &test.ctx, nullptr);
|
|
t.Equals(syncMc(test.rdram, &cmd), static_cast<int32_t>(payload.size()),
|
|
"sceMcWrite should report the full byte count via sceMcSync");
|
|
t.Equals(cmd, 0x06, "sceMcSync should report WRITE as the last command");
|
|
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, static_cast<uint32_t>(fd));
|
|
setRegU32(test.ctx, 5, 0u);
|
|
setRegU32(test.ctx, 6, PS2_FIO_SEEK_SET);
|
|
ps2_stubs::sceMcSeek(test.rdram.data(), &test.ctx, nullptr);
|
|
t.Equals(syncMc(test.rdram, &cmd), 0, "sceMcSeek should rewind to offset zero");
|
|
t.Equals(cmd, 0x04, "sceMcSync should report SEEK as the last command");
|
|
|
|
std::memset(test.rdram.data() + readBufAddr, 0, payload.size());
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, static_cast<uint32_t>(fd));
|
|
setRegU32(test.ctx, 5, readBufAddr);
|
|
setRegU32(test.ctx, 6, static_cast<uint32_t>(payload.size()));
|
|
ps2_stubs::sceMcRead(test.rdram.data(), &test.ctx, nullptr);
|
|
t.Equals(syncMc(test.rdram, &cmd), static_cast<int32_t>(payload.size()),
|
|
"sceMcRead should report the full byte count via sceMcSync");
|
|
t.Equals(cmd, 0x05, "sceMcSync should report READ as the last command");
|
|
|
|
std::string readback(reinterpret_cast<const char *>(test.rdram.data() + readBufAddr), payload.size());
|
|
t.Equals(readback, payload, "sceMcRead should fill the guest buffer with the written payload");
|
|
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, static_cast<uint32_t>(fd));
|
|
ps2_stubs::sceMcClose(test.rdram.data(), &test.ctx, nullptr);
|
|
t.Equals(syncMc(test.rdram, &cmd), 0, "sceMcClose should finish successfully");
|
|
t.Equals(cmd, 0x03, "sceMcSync should report CLOSE as the last command");
|
|
|
|
const std::filesystem::path hostPath = test.paths.mcRoot / "SAVEDATA" / "test.bin";
|
|
t.IsTrue(std::filesystem::exists(hostPath), "sceMcOpen/sceMcWrite should create the host file under mcRoot");
|
|
});
|
|
|
|
tc.Run("sceMcGetDir includes dot entries and file metadata", [](TestCase &t)
|
|
{
|
|
TestContext test;
|
|
|
|
std::filesystem::create_directories(test.paths.mcRoot / "SAVEDATA");
|
|
const std::string hostPayload = "abc123";
|
|
{
|
|
std::ofstream out(test.paths.mcRoot / "SAVEDATA" / "game.dat", std::ios::binary);
|
|
out.write(hostPayload.data(), static_cast<std::streamsize>(hostPayload.size()));
|
|
}
|
|
|
|
const uint32_t patternAddr = GUEST_STRING_AREA_START + 0x700;
|
|
writeGuestString(test.rdram.data(), patternAddr, "/SAVEDATA/*");
|
|
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, 0u);
|
|
setRegU32(test.ctx, 5, 0u);
|
|
setRegU32(test.ctx, 6, patternAddr);
|
|
setRegU32(test.ctx, 7, 0u);
|
|
setRegU32(test.ctx, 29, GUEST_STACK_AREA_START);
|
|
writeStackArg(test.rdram, test.ctx, 0u, 8u);
|
|
writeStackArg(test.rdram, test.ctx, 1u, GUEST_MC_TABLE_ADDR);
|
|
|
|
ps2_stubs::sceMcGetDir(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
int32_t cmd = 0;
|
|
const int32_t entryCount = syncMc(test.rdram, &cmd);
|
|
t.Equals(cmd, 0x0D, "sceMcSync should report GETDIR as the last command");
|
|
t.Equals(entryCount, 3, "sceMcGetDir should return '.', '..', and the matching file");
|
|
|
|
const auto *entries = reinterpret_cast<const SceMcTblGetDir *>(test.rdram.data() + GUEST_MC_TABLE_ADDR);
|
|
t.Equals(std::string(entries[0].entryName), std::string("."), "sceMcGetDir should return '.' first");
|
|
t.Equals(std::string(entries[1].entryName), std::string(".."), "sceMcGetDir should return '..' second");
|
|
t.Equals(std::string(entries[2].entryName), std::string("game.dat"), "sceMcGetDir should include the matching file entry");
|
|
t.Equals(entries[2].fileSizeByte, static_cast<uint32_t>(hostPayload.size()),
|
|
"sceMcGetDir should report the host file size");
|
|
t.IsTrue((entries[2].attrFile & 0x0080u) != 0u,
|
|
"sceMcGetDir file entries should carry the closed-file attribute");
|
|
});
|
|
|
|
tc.Run("sceMcGetInfo reports formatted and unformatted states", [](TestCase &t)
|
|
{
|
|
TestContext test;
|
|
|
|
constexpr uint32_t typeAddr = GUEST_BUFFER_AREA_START + 0x900;
|
|
constexpr uint32_t freeAddr = GUEST_BUFFER_AREA_START + 0x904;
|
|
constexpr uint32_t formatAddr = GUEST_BUFFER_AREA_START + 0x908;
|
|
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, 0u);
|
|
setRegU32(test.ctx, 5, 0u);
|
|
setRegU32(test.ctx, 6, typeAddr);
|
|
setRegU32(test.ctx, 7, freeAddr);
|
|
setRegU32(test.ctx, 29, GUEST_STACK_AREA_START);
|
|
writeStackArg(test.rdram, test.ctx, 0u, formatAddr);
|
|
ps2_stubs::sceMcGetInfo(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
int32_t cmd = 0;
|
|
t.Equals(syncMc(test.rdram, &cmd), 0, "formatted cards should report success through sceMcSync");
|
|
t.Equals(cmd, 0x01, "sceMcSync should report GETINFO as the last command");
|
|
t.Equals(readGuestS32(test.rdram.data(), typeAddr), 2, "sceMcGetInfo should report a PS2 memory card");
|
|
t.Equals(readGuestS32(test.rdram.data(), formatAddr), 1, "sceMcGetInfo should report a formatted card");
|
|
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, 0u);
|
|
setRegU32(test.ctx, 5, 0u);
|
|
ps2_stubs::sceMcUnformat(test.rdram.data(), &test.ctx, nullptr);
|
|
t.Equals(syncMc(test.rdram, &cmd), 0, "sceMcUnformat should complete successfully");
|
|
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, 0u);
|
|
setRegU32(test.ctx, 5, 0u);
|
|
setRegU32(test.ctx, 6, typeAddr);
|
|
setRegU32(test.ctx, 7, freeAddr);
|
|
setRegU32(test.ctx, 29, GUEST_STACK_AREA_START);
|
|
writeStackArg(test.rdram, test.ctx, 0u, formatAddr);
|
|
ps2_stubs::sceMcGetInfo(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
t.Equals(syncMc(test.rdram, &cmd), -2, "unformatted cards should report sceMcResNoFormat through sceMcSync");
|
|
t.Equals(readGuestS32(test.rdram.data(), formatAddr), 0, "sceMcGetInfo should report an unformatted card after sceMcUnformat");
|
|
});
|
|
|
|
tc.Run("sceMcEnd resets libmc state so sync reports no active command", [](TestCase &t)
|
|
{
|
|
TestContext test;
|
|
|
|
constexpr uint32_t dirAddr = GUEST_STRING_AREA_START + 0xB00;
|
|
|
|
clearContext(test.ctx);
|
|
ps2_stubs::sceMcInit(test.rdram.data(), &test.ctx, nullptr);
|
|
t.Equals(getRegS32(&test.ctx, 2), 0, "sceMcInit should succeed");
|
|
|
|
writeGuestString(test.rdram.data(), dirAddr, "/SAVEDATA");
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, 0u);
|
|
setRegU32(test.ctx, 5, 0u);
|
|
setRegU32(test.ctx, 6, dirAddr);
|
|
ps2_stubs::sceMcMkdir(test.rdram.data(), &test.ctx, nullptr);
|
|
int32_t cmd = 0;
|
|
t.Equals(syncMc(test.rdram, &cmd), 0, "sceMcMkdir should complete before teardown");
|
|
t.Equals(cmd, 0x0B, "sceMcSync should report MKDIR before teardown");
|
|
|
|
clearContext(test.ctx);
|
|
ps2_stubs::sceMcEnd(test.rdram.data(), &test.ctx, nullptr);
|
|
t.Equals(getRegS32(&test.ctx, 2), 0, "sceMcEnd should succeed");
|
|
|
|
t.Equals(syncMc(test.rdram, &cmd), 0, "sceMcSync should report cleared result after sceMcEnd");
|
|
t.Equals(cmd, 0, "sceMcEnd should clear the last active libmc command");
|
|
});
|
|
|
|
tc.Run("sceIoctl cmd1 updates wait flag state", [](TestCase &t)
|
|
{
|
|
TestContext test;
|
|
|
|
constexpr uint32_t statusAddr = GUEST_BUFFER_AREA_START + 0x1800;
|
|
const uint32_t busy = 1u;
|
|
std::memcpy(test.rdram.data() + statusAddr, &busy, sizeof(busy));
|
|
|
|
setRegU32(test.ctx, 4, 3u); // fd
|
|
setRegU32(test.ctx, 5, 1u); // cmd
|
|
setRegU32(test.ctx, 6, statusAddr); // arg
|
|
|
|
ps2_stubs::sceIoctl(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
t.Equals(getRegS32(&test.ctx, 2), 0, "sceIoctl cmd1 should return success");
|
|
|
|
uint32_t state = 0xFFFFFFFFu;
|
|
std::memcpy(&state, test.rdram.data() + statusAddr, sizeof(state));
|
|
t.Equals(state, 0u, "sceIoctl cmd1 should clear wait state from busy to ready");
|
|
});
|
|
|
|
tc.Run("sceCdSearchFile resolves movie filenames with zero-padded host leaf", [](TestCase &t)
|
|
{
|
|
TestContext test;
|
|
|
|
std::filesystem::create_directories(test.paths.cdRoot / "movie");
|
|
{
|
|
std::ofstream out(test.paths.cdRoot / "movie" / "mv_016.pss", std::ios::binary);
|
|
const std::string payload = "pss";
|
|
out.write(payload.data(), static_cast<std::streamsize>(payload.size()));
|
|
}
|
|
|
|
constexpr uint32_t fileAddr = GUEST_BUFFER_AREA_START + 0x1A00;
|
|
constexpr uint32_t pathAddr = GUEST_STRING_AREA_START + 0xA00;
|
|
writeGuestString(test.rdram.data(), pathAddr, "\\MOVIE\\MV_16.PSS;1");
|
|
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, fileAddr);
|
|
setRegU32(test.ctx, 5, pathAddr);
|
|
ps2_stubs::sceCdSearchFile(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
t.Equals(getRegS32(&test.ctx, 2), 1, "sceCdSearchFile should resolve the extracted movie file");
|
|
t.Equals(readGuestU32(test.rdram.data(), fileAddr + 4), 3u,
|
|
"sceCdSearchFile should report the host file size");
|
|
t.IsTrue(readGuestU32(test.rdram.data(), fileAddr + 0) >= 0x00100000u,
|
|
"sceCdSearchFile should assign a pseudo LSN for the resolved host file");
|
|
});
|
|
|
|
tc.Run("sceCdRead reads from explicit cdImage path", [](TestCase &t)
|
|
{
|
|
TestContext test;
|
|
|
|
constexpr uint32_t kSectorSize = 2048u;
|
|
constexpr uint32_t bufAddr = GUEST_BUFFER_AREA_START + 0x1C80;
|
|
const std::filesystem::path imagePath = test.paths.base / "disc.iso";
|
|
{
|
|
std::vector<uint8_t> sector(kSectorSize, 0);
|
|
const char payload[] = "cd-image";
|
|
std::memcpy(sector.data(), payload, sizeof(payload) - 1);
|
|
|
|
std::ofstream out(imagePath, std::ios::binary);
|
|
out.write(reinterpret_cast<const char *>(sector.data()),
|
|
static_cast<std::streamsize>(sector.size()));
|
|
}
|
|
|
|
PS2Runtime::IoPaths ioPaths;
|
|
ioPaths.elfDirectory = test.paths.cdRoot;
|
|
ioPaths.hostRoot = test.paths.cdRoot;
|
|
ioPaths.cdRoot = test.paths.cdRoot;
|
|
ioPaths.mcRoot = test.paths.mcRoot;
|
|
ioPaths.cdImage = imagePath;
|
|
PS2Runtime::setIoPaths(ioPaths);
|
|
|
|
clearContext(test.ctx);
|
|
setRegU32(test.ctx, 4, 0u);
|
|
setRegU32(test.ctx, 5, 1u);
|
|
setRegU32(test.ctx, 6, bufAddr);
|
|
ps2_stubs::sceCdRead(test.rdram.data(), &test.ctx, nullptr);
|
|
|
|
t.Equals(getRegS32(&test.ctx, 2), 1, "sceCdRead should succeed when cdImage is configured");
|
|
t.Equals(std::memcmp(test.rdram.data() + bufAddr, "cd-image", 8), 0,
|
|
"sceCdRead should copy sector data from the configured image");
|
|
});
|
|
});
|
|
}
|