Files
PS2Recomp/ps2xIOP/tests/iop_import_tests.cpp
Ranieri 75d729ce40 Feature/iop emulator (#244)
* 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
2026-09-19 21:31:44 -03:00

339 lines
13 KiB
C++

#include "emulator/core/iop_cpu.h"
#include "emulator/core/iop_kernel.h"
#include "emulator/core/iop_memory.h"
#include "emulator/imports/iop_cdvd.h"
#include "emulator/imports/iop_imports.h"
#include "emulator/imports/iop_loadcore.h"
#include "emulator/imports/iop_timrman.h"
#include "emulator/services/iop_rpc.h"
#include "ps2x/iop/iop_host.h"
#include <cstdint>
#include <chrono>
#include <cstring>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <string>
#include <string_view>
namespace
{
using namespace ps2x::iop;
using namespace ps2x::iop::detail;
constexpr uint32_t kExportMagic = 0x41C00000u;
constexpr int32_t kLibraryNotFound = -213;
constexpr int32_t kIllegalLibrary = -214;
class NullHost : public IopHost
{
public:
bool readGuest(uint32_t, void *, size_t) const override { return false; }
bool writeGuest(uint32_t, const void *, size_t) override { return false; }
bool zeroGuest(uint32_t, size_t) override { return false; }
bool normalizeGuestAddress(uint32_t, uint32_t &) const override { return false; }
uint32_t allocateIopHandle(IopHandleKind) override { return 1u; }
uint32_t allocateGuest(uint32_t, uint32_t) override { return 0u; }
void freeGuest(uint32_t) override {}
void audioCommand(uint32_t, uint32_t, GuestBuffer, GuestBuffer) override {}
std::string hostPath(HostPathKind) const override { return {}; }
std::string translateGuestPath(std::string_view path) const override { return std::string(path); }
uint64_t openHostFile(std::string_view) override { return 0u; }
bool hostFileSize(uint64_t, uint64_t &) const override { return false; }
bool readHostFile(uint64_t, uint64_t, void *, size_t, size_t &) override { return false; }
void closeHostFile(uint64_t) override {}
int32_t memoryCard(const MemoryCardRequest &) override { return 0; }
bool hasGuestFunction(uint32_t) const override { return false; }
bool invokeGuestFunction(uint64_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t *) override { return false; }
void log(LogLevel, std::string_view) override {}
};
class CdRootHost final : public NullHost
{
public:
explicit CdRootHost(std::filesystem::path rootPath)
: root(std::move(rootPath))
{
}
std::string hostPath(HostPathKind kind) const override
{
return kind == HostPathKind::CdRoot ? root.string() : std::string{};
}
private:
std::filesystem::path root;
};
class RecordingExecutor final : public IopGuestExecutor
{
public:
uint32_t executeGuestFunction(uint32_t address,
uint32_t a0,
uint32_t,
uint32_t,
uint32_t,
uint32_t gp) override
{
++calls;
lastAddress = address;
lastArgument = a0;
lastGp = gp;
return callbackResult;
}
uint32_t callbackResult = 0u;
uint32_t calls = 0u;
uint32_t lastAddress = 0u;
uint32_t lastArgument = 0u;
uint32_t lastGp = 0u;
};
bool expect(bool condition, std::string_view message)
{
if (condition)
return true;
std::cerr << "FAIL: " << message << '\n';
return false;
}
bool testLoadcoreRebootLibraryMode()
{
IopMemory memory;
IopImportRegistry imports(memory);
IopLoadcore loadcore(memory, imports);
IopCpuState cpu{};
cpu.gpr[4] = 0u;
cpu.gpr[5] = 2u;
if (!expect(loadcore.dispatchImport(27u, cpu), "loadcore:27 was not handled") ||
!expect(static_cast<int32_t>(cpu.gpr[2]) == kIllegalLibrary,
"loadcore:27 did not reject a null export table"))
return false;
constexpr uint32_t table = 0x1000u;
memory.write32(table, kExportMagic);
memory.write16(table + 8u, 0x0101u);
memory.write16(table + 10u, 0x1234u);
const char name[8] = {'t', 'e', 's', 't', 'l', 'i', 'b', '\0'};
(void)memory.writeRam(table + 12u, name, sizeof(name));
memory.write32(table + 20u, 0u);
cpu = {};
cpu.gpr[4] = table;
cpu.gpr[5] = 2u;
if (!expect(loadcore.dispatchImport(27u, cpu), "loadcore:27 rejected a valid export table") ||
!expect(cpu.gpr[2] == 0u, "loadcore:27 returned an error for a valid export table") ||
!expect(memory.read16(table + 10u) == 0x1232u,
"loadcore:27 did not replace only export mode bits 1 and 2"))
return false;
constexpr uint32_t invalidTable = 0x1100u;
memory.write32(invalidTable, 0xDEADBEEFu);
cpu = {};
cpu.gpr[4] = invalidTable;
cpu.gpr[5] = 6u;
if (!expect(loadcore.dispatchImport(27u, cpu), "loadcore:27 did not consume an invalid-table call") ||
!expect(static_cast<int32_t>(cpu.gpr[2]) == kLibraryNotFound,
"loadcore:27 returned the wrong invalid-table error"))
return false;
if (!expect(imports.registerExportTable(table), "test export table did not register"))
return false;
memory.write32(table, 0u);
cpu = {};
cpu.gpr[4] = table;
cpu.gpr[5] = 6u;
return expect(loadcore.dispatchImport(27u, cpu), "loadcore:27 rejected a registered table") &&
expect(cpu.gpr[2] == 0u, "loadcore:27 returned an error for a registered table") &&
expect(memory.read16(table + 10u) == 0x1236u,
"loadcore:27 did not update a registered table's mode");
}
bool pollEvent(IopKernel &kernel, int eventId, uint32_t bits, uint32_t resultAddress, int32_t expected)
{
IopCpuState cpu{};
cpu.gpr[4] = static_cast<uint32_t>(eventId);
cpu.gpr[5] = bits;
cpu.gpr[6] = 0u; // WEF_AND
cpu.gpr[7] = resultAddress;
return expect(kernel.dispatchEventImport(11u, cpu), "PollEventFlag was not handled") &&
expect(static_cast<int32_t>(cpu.gpr[2]) == expected, "PollEventFlag returned an unexpected result");
}
bool testCdvdSpecialControl()
{
NullHost host;
IopMemory memory;
IopKernel kernel(memory);
kernel.reset();
IopCdvd cdvd(host, memory, kernel);
cdvd.reset();
constexpr uint32_t param = 0x2000u;
constexpr uint32_t eventResult = 0x2010u;
IopCpuState cpu{};
cpu.gpr[4] = static_cast<uint32_t>(-11); // sceCdSC: return cdvdman interrupt event flag
cpu.gpr[5] = param;
if (!expect(cdvd.dispatchImport(50u, cpu), "cdvdman:50 was not handled") ||
!expect(static_cast<int32_t>(cpu.gpr[2]) > 0, "sceCdSC(-11) did not return a valid event flag"))
return false;
const int eventId = static_cast<int>(cpu.gpr[2]);
if (!pollEvent(kernel, eventId, 0x29u, eventResult, 0) ||
!expect(memory.read32(eventResult) == 0x29u, "cdvdman event flag did not start with bits 0x29"))
return false;
IopCpuState clear{};
clear.gpr[4] = static_cast<uint32_t>(eventId);
clear.gpr[5] = ~0x29u;
if (!expect(kernel.dispatchEventImport(8u, clear), "ClearEventFlag was not handled") ||
!pollEvent(kernel, eventId, 0x29u, eventResult, -418))
return false;
cpu = {};
cpu.gpr[4] = 0x12345u;
if (!expect(cdvd.dispatchImport(7u, cpu), "sceCdSeek was not handled") ||
!pollEvent(kernel, eventId, 0x29u, eventResult, 0))
return false;
memory.write8(param, 0x30u);
cpu = {};
cpu.gpr[4] = static_cast<uint32_t>(-2);
cpu.gpr[5] = param;
if (!expect(cdvd.dispatchImport(50u, cpu), "sceCdSC(-2) was not handled") ||
!expect(cpu.gpr[2] == 0x30u, "sceCdSC(-2) did not store the low-byte error"))
return false;
memory.write32(param, 0u);
cpu = {};
cpu.gpr[4] = static_cast<uint32_t>(-1);
cpu.gpr[5] = param;
if (!expect(cdvd.dispatchImport(50u, cpu), "sceCdSC(-1) was not handled") ||
!expect(cpu.gpr[2] == 0u, "sceCdSC(-1) returned the wrong initial stream state") ||
!expect(memory.read32(param) == 0x30u, "sceCdSC(-1) did not publish the last error"))
return false;
cpu = {};
cpu.gpr[4] = 2u;
cpu.gpr[5] = param;
if (!expect(cdvd.dispatchImport(50u, cpu), "sceCdSC(2) was not handled") ||
!expect(cpu.gpr[2] == 2u, "sceCdSC(2) did not update the stream state"))
return false;
cpu = {};
cpu.gpr[4] = static_cast<uint32_t>(-1);
cpu.gpr[5] = param;
return expect(cdvd.dispatchImport(50u, cpu), "second sceCdSC(-1) was not handled") &&
expect(cpu.gpr[2] == 2u, "sceCdSC(-1) did not preserve the stream state");
}
bool testCdvdSearchFile()
{
const auto suffix = std::to_string(
static_cast<unsigned long long>(std::chrono::steady_clock::now().time_since_epoch().count()));
const std::filesystem::path root =
std::filesystem::temp_directory_path() / ("ps2x-iop-cdvd-search-" + suffix);
const std::filesystem::path movieDirectory = root / "MOVIE";
const std::filesystem::path moviePath = movieDirectory / "OPENING.PSS";
std::error_code error;
std::filesystem::create_directories(movieDirectory, error);
if (!expect(!error, "could not create the temporary CD root"))
return false;
{
std::ofstream movie(moviePath, std::ios::binary);
movie.write("PSS!", 4);
}
CdRootHost host(root);
IopMemory memory;
IopKernel kernel(memory);
kernel.reset();
IopCdvd cdvd(host, memory, kernel);
cdvd.reset();
constexpr uint32_t resultAddress = 0x2400u;
constexpr uint32_t pathAddress = 0x2480u;
const char path[] = "cdrom0:\\movie\\opening.pss;1";
(void)memory.writeRam(pathAddress, path, sizeof(path));
IopCpuState cpu{};
cpu.gpr[4] = resultAddress;
cpu.gpr[5] = pathAddress;
const bool handled = cdvd.dispatchImport(10u, cpu);
const bool passed =
expect(handled, "cdvdman:10 was not handled") &&
expect(cpu.gpr[2] == 1u, "sceCdSearchFile did not find a case-insensitive ISO path") &&
expect(memory.read32(resultAddress) >= 20u, "sceCdSearchFile returned an invalid LSN") &&
expect(memory.read32(resultAddress + 4u) == 4u, "sceCdSearchFile returned the wrong size") &&
expect(memory.readString(resultAddress + 8u, 16u) == "OPENING.PSS",
"sceCdSearchFile returned the wrong file name");
std::filesystem::remove_all(root, error);
return passed;
}
bool testTimrmanPeriodicCallback()
{
IopTimrman timrman;
timrman.reset();
IopCpuState cpu{};
cpu.gpr[4] = 1u; // SYSCLK
cpu.gpr[5] = 32u;
cpu.gpr[6] = 1u;
if (!expect(timrman.dispatchImport(4u, cpu, 100u), "AllocHardTimer was not handled") ||
!expect(static_cast<int32_t>(cpu.gpr[2]) > 0, "AllocHardTimer did not allocate a 32-bit timer"))
return false;
const uint32_t timerId = cpu.gpr[2];
cpu = {};
cpu.gpr[4] = timerId;
cpu.gpr[5] = 100u;
cpu.gpr[6] = 0x12340u;
cpu.gpr[7] = 0x45670u;
cpu.gpr[28] = 0x89AB0u;
if (!expect(timrman.dispatchImport(20u, cpu, 100u), "SetTimerHandler was not handled") ||
!expect(cpu.gpr[2] == 0u, "SetTimerHandler failed"))
return false;
cpu = {};
cpu.gpr[4] = timerId;
cpu.gpr[5] = 1u;
cpu.gpr[6] = 0u;
cpu.gpr[7] = 1u;
if (!expect(timrman.dispatchImport(22u, cpu, 100u), "SetupHardTimer was not handled") ||
!expect(cpu.gpr[2] == 0u, "SetupHardTimer failed"))
return false;
cpu = {};
cpu.gpr[4] = timerId;
if (!expect(timrman.dispatchImport(23u, cpu, 100u), "StartHardTimer was not handled") ||
!expect(cpu.gpr[2] == 0u, "StartHardTimer failed") ||
!expect(timrman.nextEventCycle(1000u) == 200u, "timer compare was scheduled at the wrong cycle"))
return false;
RecordingExecutor executor;
executor.callbackResult = 100u;
timrman.serviceDue(199u, executor);
if (!expect(executor.calls == 0u, "timer callback ran too early"))
return false;
timrman.serviceDue(200u, executor);
return expect(executor.calls == 1u, "timer callback did not run") &&
expect(executor.lastAddress == 0x12340u, "timer called the wrong handler") &&
expect(executor.lastArgument == 0x45670u, "timer passed the wrong common argument") &&
expect(executor.lastGp == 0x89AB0u, "timer callback lost the registering module GP") &&
expect(timrman.nextEventCycle(1000u) == 300u, "timer callback return did not rearm compare");
}
}
int main()
{
if (!testLoadcoreRebootLibraryMode() || !testCdvdSpecialControl() || !testCdvdSearchFile() ||
!testTimrmanPeriodicCallback())
return 1;
std::cout << "ps2xIOP import tests passed\n";
return 0;
}