Files
PS2Recomp/ps2xIOP/tests/iop_compat_test_support.h
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

238 lines
8.2 KiB
C++

#pragma once
#include "ps2x/iop/iop_subsystem.h"
#include <algorithm>
#include <array>
#include <cstring>
#include <iostream>
#include <span>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
namespace iop_test
{
using namespace ps2x::iop;
inline void require(bool condition, const char *message)
{
if (!condition)
throw std::runtime_error(message);
}
class Host final : public IopHost
{
public:
explicit Host(size_t bytes = 0x20000u) : guest(bytes, 0xCCu) {}
bool readGuest(uint32_t address, void *destination, size_t size) const override
{
if ((size != 0u && !destination) || address > guest.size() || size > guest.size() - address)
return false;
if (size != 0u)
std::memcpy(destination, guest.data() + address, size);
++guestReads;
return true;
}
bool writeGuest(uint32_t address, const void *source, size_t size) override
{
if ((size != 0u && !source) || address > guest.size() || size > guest.size() - address)
return false;
if (size != 0u)
std::memcpy(guest.data() + address, source, size);
++guestWrites;
return true;
}
bool zeroGuest(uint32_t address, size_t size) override
{
if (address > guest.size() || size > guest.size() - address)
return false;
std::fill_n(guest.begin() + address, size, uint8_t{0});
++guestWrites;
return true;
}
bool normalizeGuestAddress(uint32_t address, uint32_t &normalized) const override
{
normalized = address;
return address < guest.size();
}
uint32_t allocateIopHandle(IopHandleKind) override { return nextHandle += 0x80u; }
uint32_t allocateGuest(uint32_t, uint32_t) override { return 0u; }
void freeGuest(uint32_t) override {}
void audioCommand(uint32_t, uint32_t, GuestBuffer, GuestBuffer) override { ++audioCalls; }
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 file.empty() ? 0u : 1u; }
bool hostFileSize(uint64_t handle, uint64_t &size) const override
{
size = file.size();
return handle == 1u && !file.empty();
}
bool readHostFile(uint64_t handle, uint64_t offset, void *destination, size_t size,
size_t &bytesRead) override
{
bytesRead = 0u;
if (handle != 1u || offset > file.size())
return false;
bytesRead = std::min(size, file.size() - static_cast<size_t>(offset));
if (bytesRead != 0u)
std::memcpy(destination, file.data() + offset, bytesRead);
return true;
}
void closeHostFile(uint64_t) override {}
int32_t memoryCard(const MemoryCardRequest &request) override
{
cardCalls.push_back(request);
return request.operation == MemoryCardOperation::Init ? initResult : 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 message) override { logs.emplace_back(message); }
uint32_t word(uint32_t address) const
{
uint32_t value = 0u;
require(readGuest(address, &value, sizeof(value)), "test read outside guest RAM");
return value;
}
void fill(uint32_t address, size_t size, uint8_t value = 0xCCu)
{
require(address <= guest.size() && size <= guest.size() - address, "test fill outside RAM");
std::fill_n(guest.begin() + address, size, value);
}
std::vector<uint8_t> guest;
std::vector<uint8_t> file;
std::vector<std::string> logs;
std::vector<MemoryCardRequest> cardCalls;
mutable size_t guestReads = 0u;
size_t guestWrites = 0u;
size_t audioCalls = 0u;
int32_t initResult = 0;
uint32_t nextHandle = 0x1000u;
};
inline RpcRequest request(uint32_t sid, uint32_t function, uint32_t size = 16u)
{
RpcRequest result{};
result.sid = sid;
result.function = function;
result.receive = {0x800u, size};
return result;
}
inline uint64_t metric(const IopSubsystem &iop, std::string_view service, std::string_view name)
{
for (const auto &row : iop.debugSnapshot().services)
if (row.name == service)
for (const auto &entry : row.metrics)
if (entry.name == name)
return entry.value;
throw std::runtime_error("missing debug metric");
}
class Irx
{
public:
explicit Irx(uint32_t base = 0x10000u, uint32_t imageBytes = 0x500u)
: bytes(0x100u + imageBytes, 0u)
{
put32(0u, 0x464C457Fu);
bytes[4] = bytes[5] = bytes[6] = 1u;
put16(16u, 2u);
put16(18u, 8u);
put32(20u, 1u);
put32(24u, base);
put32(28u, 52u);
put16(40u, 52u);
put16(42u, 32u);
put16(44u, 1u);
put32(52u, 1u);
put32(56u, 0x100u);
put32(60u, base);
put32(64u, base);
put32(68u, imageBytes);
put32(72u, imageBytes);
put32(76u, 7u);
put32(80u, 4u);
}
void words(uint32_t offset, std::initializer_list<uint32_t> values)
{
for (uint32_t value : values)
{
put32(0x100u + offset, value);
offset += 4u;
}
}
void install(Host &host, uint32_t address = 0x1000u) const
{
require(host.writeGuest(address, bytes.data(), bytes.size()), "synthetic IRX does not fit");
}
std::vector<uint8_t> bytes;
private:
void put16(uint32_t offset, uint16_t value)
{
require(offset + 2u <= bytes.size(), "IRX builder overflow");
bytes[offset] = static_cast<uint8_t>(value);
bytes[offset + 1u] = static_cast<uint8_t>(value >> 8u);
}
void put32(uint32_t offset, uint32_t value)
{
put16(offset, static_cast<uint16_t>(value));
put16(offset + 2u, static_cast<uint16_t>(value >> 16u));
}
};
inline Irx rpcServer(uint32_t sid, uint32_t reply)
{
Irx image;
image.words(0u, {
0x27BDFFE0u, 0xAFBF001Cu, // save ra
0x3C040001u, 0x34840200u,
0x3C050000u | (sid >> 16u), 0x34A50000u | (sid & 0xFFFFu),
0x3C060001u, 0x34C60300u,
0x3C070001u, 0x34E70400u,
0xAFA00010u, 0xAFA00014u, 0xAFA00018u,
0x0C00401Du, 0u, // jal 0x10074: sceSifRegisterRpc
0x8FBF001Cu, 0x00001021u, 0x27BD0020u, 0x03E00008u, 0u,
});
image.words(0x60u, {0x41E00000u, 0u, 0x0101u, 0x63666973u, 0x0000646Du,
0x03E00008u, 0x24000011u, 0u, 0u});
image.words(0x300u, {0x3C020001u, 0x34420400u, 0x03E00008u, 0u});
image.words(0x400u, {reply, reply, reply, reply});
return image;
}
struct Test
{
const char *name;
void (*function)();
};
inline int run(std::span<const Test> tests)
{
size_t failures = 0u;
for (const Test &test : tests)
{
try
{
test.function();
std::cout << "PASS " << test.name << '\n';
}
catch (const std::exception &error)
{
++failures;
std::cerr << "FAIL " << test.name << ": " << error.what() << '\n';
}
}
std::cout << tests.size() - failures << '/' << tests.size() << " cases passed\n";
return failures == 0u ? 0 : 1;
}
}