Feature/mc0 IO + Win IO fixes (#57)

* mc0: map memory card paths to mcRoot and related io tests

* Fix mc0 test setup to initialize temp paths

* fix: implementing WIN32 I/O support in syscalls

* fix: avoid fioWrite deadlock

Do not lock the fd map while calling getHostFile; use the syscall file mutex around fwrite instead
This commit is contained in:
Antonio Guastella
2026-02-17 16:40:57 +01:00
committed by GitHub
parent c4555f6723
commit cf5f57180f
8 changed files with 356 additions and 47 deletions
+278
View File
@@ -0,0 +1,278 @@
#include "MiniTest.h"
#include "ps2_runtime.h"
#include "ps2_syscalls.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;
// 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;
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);
}
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");
});
});
}