mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -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
1148 lines
52 KiB
C++
1148 lines
52 KiB
C++
#include "ps2x/iop/iop_subsystem.h"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
using namespace ps2x::iop;
|
|
|
|
class TestHost final : public IopHost
|
|
{
|
|
public:
|
|
explicit TestHost(size_t guestBytes = 4096u)
|
|
: guest(guestBytes, 0u)
|
|
{
|
|
}
|
|
|
|
bool readGuest(uint32_t address, void *destination, size_t size) const override
|
|
{
|
|
if ((!destination && size != 0u) || address > guest.size() || size > guest.size() - address)
|
|
return false;
|
|
if (size != 0u)
|
|
std::memcpy(destination, guest.data() + address, size);
|
|
return true;
|
|
}
|
|
|
|
bool writeGuest(uint32_t address, const void *source, size_t size) override
|
|
{
|
|
if ((!source && size != 0u) || address > guest.size() || size > guest.size() - address)
|
|
return false;
|
|
if (size != 0u)
|
|
std::memcpy(guest.data() + address, source, size);
|
|
return true;
|
|
}
|
|
|
|
bool zeroGuest(uint32_t address, size_t size) override
|
|
{
|
|
if (address > guest.size() || size > guest.size() - address)
|
|
return false;
|
|
if (size != 0u)
|
|
std::memset(guest.data() + address, 0, size);
|
|
return true;
|
|
}
|
|
|
|
bool normalizeGuestAddress(uint32_t address, uint32_t &normalized) const override
|
|
{
|
|
normalized = address;
|
|
return address <= guest.size();
|
|
}
|
|
|
|
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 kind) const override
|
|
{
|
|
return kind == HostPathKind::CdRoot ? cdRoot : std::string{};
|
|
}
|
|
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 message) override { logs.emplace_back(message); }
|
|
|
|
std::vector<uint8_t> guest;
|
|
std::vector<std::string> logs;
|
|
std::string cdRoot;
|
|
};
|
|
|
|
#pragma pack(push, 1)
|
|
struct ElfHeader
|
|
{
|
|
uint8_t ident[16];
|
|
uint16_t type;
|
|
uint16_t machine;
|
|
uint32_t version;
|
|
uint32_t entry;
|
|
uint32_t phoff;
|
|
uint32_t shoff;
|
|
uint32_t flags;
|
|
uint16_t ehsize;
|
|
uint16_t phentsize;
|
|
uint16_t phnum;
|
|
uint16_t shentsize;
|
|
uint16_t shnum;
|
|
uint16_t shstrndx;
|
|
};
|
|
|
|
struct ProgramHeader
|
|
{
|
|
uint32_t type;
|
|
uint32_t offset;
|
|
uint32_t vaddr;
|
|
uint32_t paddr;
|
|
uint32_t filesz;
|
|
uint32_t memsz;
|
|
uint32_t flags;
|
|
uint32_t align;
|
|
};
|
|
|
|
struct SectionHeader
|
|
{
|
|
uint32_t name;
|
|
uint32_t type;
|
|
uint32_t flags;
|
|
uint32_t address;
|
|
uint32_t offset;
|
|
uint32_t size;
|
|
uint32_t link;
|
|
uint32_t info;
|
|
uint32_t alignment;
|
|
uint32_t entrySize;
|
|
};
|
|
|
|
struct Relocation
|
|
{
|
|
uint32_t offset;
|
|
uint32_t info;
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
static_assert(sizeof(ElfHeader) == 52u);
|
|
static_assert(sizeof(ProgramHeader) == 32u);
|
|
static_assert(sizeof(SectionHeader) == 40u);
|
|
static_assert(sizeof(Relocation) == 8u);
|
|
|
|
void writeMinimalIrx(TestHost &host, uint32_t address)
|
|
{
|
|
constexpr uint32_t codeOffset = 0x100u;
|
|
constexpr uint32_t loadAddress = 0x00010000u;
|
|
|
|
ElfHeader header{};
|
|
header.ident[0] = 0x7Fu;
|
|
header.ident[1] = 'E';
|
|
header.ident[2] = 'L';
|
|
header.ident[3] = 'F';
|
|
header.ident[4] = 1u;
|
|
header.ident[5] = 1u;
|
|
header.ident[6] = 1u;
|
|
header.type = 2u;
|
|
header.machine = 8u;
|
|
header.version = 1u;
|
|
header.entry = loadAddress;
|
|
header.phoff = sizeof(ElfHeader);
|
|
header.ehsize = sizeof(ElfHeader);
|
|
header.phentsize = sizeof(ProgramHeader);
|
|
header.phnum = 1u;
|
|
|
|
// Branch is deliberately included so the test checks that its delay slot runs.
|
|
const uint32_t code[] = {
|
|
0x24080001u, // addiu t0, zero, 1
|
|
0x11080002u, // beq t0, t0, +2
|
|
0x24020007u, // addiu v0, zero, 7 (delay slot)
|
|
0x24020063u, // addiu v0, zero, 99 (must be skipped)
|
|
0x03E00008u, // jr ra
|
|
0x00000000u, // nop
|
|
};
|
|
|
|
ProgramHeader program{};
|
|
program.type = 1u;
|
|
program.offset = codeOffset;
|
|
program.vaddr = loadAddress;
|
|
program.paddr = loadAddress;
|
|
program.filesz = sizeof(code);
|
|
program.memsz = sizeof(code);
|
|
program.flags = 5u;
|
|
program.align = 4u;
|
|
|
|
std::memcpy(host.guest.data() + address, &header, sizeof(header));
|
|
std::memcpy(host.guest.data() + address + sizeof(header), &program, sizeof(program));
|
|
std::memcpy(host.guest.data() + address + codeOffset, code, sizeof(code));
|
|
}
|
|
|
|
void writeRpcServerIrx(TestHost &host,
|
|
uint32_t address,
|
|
uint32_t sid = 0xF00DCAFEu)
|
|
{
|
|
constexpr uint32_t codeOffset = 0x100u;
|
|
constexpr uint32_t loadAddress = 0x00010000u;
|
|
constexpr uint32_t importTableOffset = 0x60u;
|
|
constexpr uint32_t importStubAddress = loadAddress + importTableOffset + 20u;
|
|
|
|
ElfHeader header{};
|
|
header.ident[0] = 0x7Fu;
|
|
header.ident[1] = 'E';
|
|
header.ident[2] = 'L';
|
|
header.ident[3] = 'F';
|
|
header.ident[4] = 1u;
|
|
header.ident[5] = 1u;
|
|
header.ident[6] = 1u;
|
|
header.type = 2u;
|
|
header.machine = 8u;
|
|
header.version = 1u;
|
|
header.entry = loadAddress;
|
|
header.phoff = sizeof(ElfHeader);
|
|
header.ehsize = sizeof(ElfHeader);
|
|
header.phentsize = sizeof(ProgramHeader);
|
|
header.phnum = 1u;
|
|
|
|
const uint32_t code[] = {
|
|
0x27BDFFE0u, // addiu sp, sp, -0x20
|
|
0xAFBF001Cu, // sw ra, 0x1c(sp)
|
|
0x3C040001u, // lui a0, 1
|
|
0x34840200u, // ori a0, a0, 0x200 (server data)
|
|
0x3C050000u | ((sid >> 16u) & 0xFFFFu), // lui a1, SID upper
|
|
0x34A50000u | (sid & 0xFFFFu), // ori a1, a1, SID lower
|
|
0x3C060001u, // lui a2, 1
|
|
0x34C60300u, // ori a2, a2, 0x300 (server function)
|
|
0x3C070001u, // lui a3, 1
|
|
0x34E70400u, // ori a3, a3, 0x400 (server buffer)
|
|
0xAFA00010u, // sw zero, 0x10(sp)
|
|
0xAFA00014u, // sw zero, 0x14(sp)
|
|
0xAFA00018u, // sw zero, 0x18(sp)
|
|
0x0C000000u | ((importStubAddress >> 2u) & 0x03FFFFFFu), // jal sceSifRegisterRpc
|
|
0x00000000u, // nop
|
|
0x8FBF001Cu, // lw ra, 0x1c(sp)
|
|
0x00001021u, // addu v0, zero, zero
|
|
0x27BD0020u, // addiu sp, sp, 0x20
|
|
0x03E00008u, // jr ra
|
|
0x00000000u, // nop
|
|
};
|
|
const uint32_t importTable[] = {
|
|
0x41E00000u, // IRX import magic
|
|
0x00000000u,
|
|
0x00000101u,
|
|
0x63666973u, // "sifc"
|
|
0x0000646Du, // "md"
|
|
0x03E00008u, // jr ra
|
|
0x24000011u, // addiu zero, zero, 17 (sceSifRegisterRpc)
|
|
0x00000000u,
|
|
0x00000000u,
|
|
};
|
|
|
|
ProgramHeader program{};
|
|
program.type = 1u;
|
|
program.offset = codeOffset;
|
|
program.vaddr = loadAddress;
|
|
program.paddr = loadAddress;
|
|
program.filesz = 0xA0u;
|
|
program.memsz = 0x500u;
|
|
program.flags = 7u;
|
|
program.align = 4u;
|
|
|
|
std::fill(host.guest.begin() + address, host.guest.end(), 0u);
|
|
std::memcpy(host.guest.data() + address, &header, sizeof(header));
|
|
std::memcpy(host.guest.data() + address + sizeof(header), &program, sizeof(program));
|
|
std::memcpy(host.guest.data() + address + codeOffset, code, sizeof(code));
|
|
std::memcpy(host.guest.data() + address + codeOffset + importTableOffset,
|
|
importTable, sizeof(importTable));
|
|
}
|
|
|
|
void writeRelocatableRpcServerIrx(TestHost &host, uint32_t address)
|
|
{
|
|
constexpr uint32_t codeOffset = 0x100u;
|
|
constexpr uint32_t entryAddress = 0x20u;
|
|
constexpr uint32_t importTableAddress = 0x80u;
|
|
constexpr uint32_t importStubAddress = importTableAddress + 20u;
|
|
constexpr uint32_t handlerAddress = 0xC0u;
|
|
constexpr uint32_t gpAddress = 0xD0u;
|
|
constexpr uint32_t iopModFileOffset = 0xE0u;
|
|
constexpr uint32_t relocationFileOffset = 0x200u;
|
|
constexpr uint32_t sectionTableOffset = 0x240u;
|
|
constexpr uint32_t rpcSid = 0xA11CE001u;
|
|
|
|
ElfHeader header{};
|
|
header.ident[0] = 0x7Fu;
|
|
header.ident[1] = 'E';
|
|
header.ident[2] = 'L';
|
|
header.ident[3] = 'F';
|
|
header.ident[4] = 1u;
|
|
header.ident[5] = 1u;
|
|
header.ident[6] = 1u;
|
|
header.type = 0xFF80u; // ET_SCE_IOPRELEXEC
|
|
header.machine = 8u;
|
|
header.version = 1u;
|
|
header.entry = entryAddress;
|
|
header.phoff = sizeof(ElfHeader);
|
|
header.shoff = sectionTableOffset;
|
|
header.ehsize = sizeof(ElfHeader);
|
|
header.phentsize = sizeof(ProgramHeader);
|
|
header.phnum = 2u;
|
|
header.shentsize = sizeof(SectionHeader);
|
|
header.shnum = 3u;
|
|
|
|
const uint32_t code[] = {
|
|
0x27BDFFE0u, // addiu sp, sp, -0x20
|
|
0xAFBF001Cu, // sw ra, 0x1c(sp)
|
|
0x00002021u, // addu a0, zero, zero
|
|
0x3C05A11Cu, // lui a1, 0xa11c
|
|
0x34A5E001u, // ori a1, a1, 0xe001
|
|
0x3C060000u, // lui a2, 0 (HI16 handler address)
|
|
0x24C600C0u, // addiu a2, a2, 0xc0 (LO16 handler address)
|
|
0x00003821u, // addu a3, zero, zero
|
|
0xAFA00010u, // sw zero, 0x10(sp)
|
|
0xAFA00014u, // sw zero, 0x14(sp)
|
|
0xAFA00018u, // sw zero, 0x18(sp)
|
|
0x0C000000u | ((importStubAddress >> 2u) & 0x03FFFFFFu), // jal sceSifRegisterRpc
|
|
0x00000000u, // nop
|
|
0x8FBF001Cu, // lw ra, 0x1c(sp)
|
|
0x00001021u, // addu v0, zero, zero
|
|
0x27BD0020u, // addiu sp, sp, 0x20
|
|
0x03E00008u, // jr ra
|
|
0x00000000u, // nop
|
|
};
|
|
const uint32_t importTable[] = {
|
|
0x41E00000u,
|
|
0x00000000u,
|
|
0x00000101u,
|
|
0x63666973u, // "sifc"
|
|
0x0000646Du, // "md"
|
|
0x03E00008u,
|
|
0x24000011u, // sceSifRegisterRpc
|
|
0x00000000u,
|
|
};
|
|
const uint32_t handler[] = {
|
|
0x03E00008u, // jr ra
|
|
0x03801021u, // addu v0, gp, zero
|
|
};
|
|
constexpr uint32_t gpData = 0x47505250u; // "GPRP"
|
|
const uint32_t iopModuleHeader[] = {
|
|
0u,
|
|
entryAddress,
|
|
gpAddress,
|
|
};
|
|
|
|
ProgramHeader iopModule{};
|
|
iopModule.type = 0x70000080u; // PT_SCE_IOPMOD
|
|
iopModule.offset = iopModFileOffset;
|
|
iopModule.filesz = sizeof(iopModuleHeader);
|
|
iopModule.memsz = sizeof(iopModuleHeader);
|
|
iopModule.align = 4u;
|
|
|
|
ProgramHeader program{};
|
|
program.type = 1u;
|
|
program.offset = codeOffset;
|
|
program.vaddr = 0u;
|
|
program.paddr = 0u;
|
|
program.filesz = gpAddress + sizeof(gpData);
|
|
program.memsz = 0x500u;
|
|
program.flags = 7u;
|
|
program.align = 4u;
|
|
|
|
SectionHeader text{};
|
|
text.type = 1u; // SHT_PROGBITS
|
|
text.flags = 0x6u; // SHF_ALLOC | SHF_EXECINSTR
|
|
text.address = entryAddress;
|
|
text.offset = codeOffset + entryAddress;
|
|
text.size = 0xB0u;
|
|
text.alignment = 4u;
|
|
|
|
SectionHeader relocations{};
|
|
relocations.type = 9u; // SHT_REL
|
|
relocations.offset = relocationFileOffset;
|
|
relocations.size = 3u * sizeof(Relocation);
|
|
relocations.info = 1u; // target .text
|
|
relocations.alignment = 4u;
|
|
relocations.entrySize = sizeof(Relocation);
|
|
|
|
const Relocation relocationData[] = {
|
|
{entryAddress + 5u * sizeof(uint32_t), 5u}, // symbol 0, R_MIPS_HI16
|
|
{entryAddress + 6u * sizeof(uint32_t), 6u}, // symbol 0, R_MIPS_LO16
|
|
{entryAddress + 11u * sizeof(uint32_t), 4u}, // symbol 0, R_MIPS_26
|
|
};
|
|
|
|
std::fill(host.guest.begin() + address, host.guest.end(), 0u);
|
|
std::memcpy(host.guest.data() + address, &header, sizeof(header));
|
|
std::memcpy(host.guest.data() + address + sizeof(header), &iopModule, sizeof(iopModule));
|
|
std::memcpy(host.guest.data() + address + sizeof(header) + sizeof(iopModule), &program, sizeof(program));
|
|
std::memcpy(host.guest.data() + address + iopModFileOffset,
|
|
iopModuleHeader, sizeof(iopModuleHeader));
|
|
std::memcpy(host.guest.data() + address + codeOffset + entryAddress, code, sizeof(code));
|
|
std::memcpy(host.guest.data() + address + codeOffset + importTableAddress,
|
|
importTable, sizeof(importTable));
|
|
std::memcpy(host.guest.data() + address + codeOffset + handlerAddress,
|
|
handler, sizeof(handler));
|
|
std::memcpy(host.guest.data() + address + codeOffset + gpAddress,
|
|
&gpData, sizeof(gpData));
|
|
std::memcpy(host.guest.data() + address + relocationFileOffset,
|
|
relocationData, sizeof(relocationData));
|
|
std::memcpy(host.guest.data() + address + sectionTableOffset + sizeof(SectionHeader),
|
|
&text, sizeof(text));
|
|
std::memcpy(host.guest.data() + address + sectionTableOffset + 2u * sizeof(SectionHeader),
|
|
&relocations, sizeof(relocations));
|
|
}
|
|
|
|
void writeExportProviderIrx(TestHost &host, uint32_t address)
|
|
{
|
|
constexpr uint32_t codeOffset = 0x100u;
|
|
constexpr uint32_t loadAddress = 0x00010000u;
|
|
constexpr uint32_t targetAddress = loadAddress + 0x60u;
|
|
constexpr uint32_t exportTableAddress = loadAddress + 0x80u;
|
|
constexpr uint32_t importTableAddress = loadAddress + 0xC0u;
|
|
constexpr uint32_t importStubAddress = importTableAddress + 20u;
|
|
|
|
ElfHeader header{};
|
|
header.ident[0] = 0x7Fu; header.ident[1] = 'E'; header.ident[2] = 'L'; header.ident[3] = 'F';
|
|
header.ident[4] = 1u; header.ident[5] = 1u; header.ident[6] = 1u;
|
|
header.type = 2u; header.machine = 8u; header.version = 1u;
|
|
header.entry = loadAddress; header.phoff = sizeof(ElfHeader);
|
|
header.ehsize = sizeof(ElfHeader); header.phentsize = sizeof(ProgramHeader); header.phnum = 1u;
|
|
|
|
ProgramHeader program{};
|
|
program.type = 1u; program.offset = codeOffset; program.vaddr = loadAddress; program.paddr = loadAddress;
|
|
program.filesz = 0xF0u; program.memsz = 0xF0u; program.flags = 7u; program.align = 4u;
|
|
|
|
const uint32_t code[] = {
|
|
0x27BDFFE0u, 0xAFBF001Cu,
|
|
0x3C040001u, 0x34840080u,
|
|
0x0C000000u | ((importStubAddress >> 2u) & 0x03FFFFFFu), 0x00000000u,
|
|
0x8FBF001Cu, 0x00001021u,
|
|
0x27BD0020u, 0x03E00008u, 0x00000000u,
|
|
};
|
|
const uint32_t target[] = {0x03E00008u, 0x24020042u};
|
|
const uint32_t exportTable[] = {
|
|
0x41C00000u, 0u, 0x00000101u,
|
|
0x6C747374u, 0x00006269u, // "tstlib"
|
|
loadAddress, loadAddress, loadAddress, targetAddress, 0u,
|
|
};
|
|
const uint32_t importTable[] = {
|
|
0x41E00000u, 0u, 0x00000101u,
|
|
0x64616F6Cu, 0x65726F63u, // "loadcore"
|
|
0x03E00008u, 0x24000006u, 0u, 0u,
|
|
};
|
|
|
|
std::memset(host.guest.data() + address, 0, codeOffset + program.filesz);
|
|
std::memcpy(host.guest.data() + address, &header, sizeof(header));
|
|
std::memcpy(host.guest.data() + address + sizeof(header), &program, sizeof(program));
|
|
std::memcpy(host.guest.data() + address + codeOffset, code, sizeof(code));
|
|
std::memcpy(host.guest.data() + address + codeOffset + 0x60u, target, sizeof(target));
|
|
std::memcpy(host.guest.data() + address + codeOffset + 0x80u, exportTable, sizeof(exportTable));
|
|
std::memcpy(host.guest.data() + address + codeOffset + 0xC0u, importTable, sizeof(importTable));
|
|
}
|
|
|
|
void writeExportConsumerIrx(TestHost &host, uint32_t address)
|
|
{
|
|
constexpr uint32_t codeOffset = 0x100u;
|
|
constexpr uint32_t loadAddress = 0x00011000u;
|
|
constexpr uint32_t importTableAddress = loadAddress + 0x40u;
|
|
constexpr uint32_t importStubAddress = importTableAddress + 20u;
|
|
|
|
ElfHeader header{};
|
|
header.ident[0] = 0x7Fu; header.ident[1] = 'E'; header.ident[2] = 'L'; header.ident[3] = 'F';
|
|
header.ident[4] = 1u; header.ident[5] = 1u; header.ident[6] = 1u;
|
|
header.type = 2u; header.machine = 8u; header.version = 1u;
|
|
header.entry = loadAddress; header.phoff = sizeof(ElfHeader);
|
|
header.ehsize = sizeof(ElfHeader); header.phentsize = sizeof(ProgramHeader); header.phnum = 1u;
|
|
|
|
ProgramHeader program{};
|
|
program.type = 1u; program.offset = codeOffset; program.vaddr = loadAddress; program.paddr = loadAddress;
|
|
program.filesz = 0x80u; program.memsz = 0x80u; program.flags = 7u; program.align = 4u;
|
|
|
|
const uint32_t code[] = {
|
|
0x27BDFFF0u, 0xAFBF000Cu,
|
|
0x0C000000u | ((importStubAddress >> 2u) & 0x03FFFFFFu), 0x00000000u,
|
|
0x8FBF000Cu, 0x27BD0010u,
|
|
0x03E00008u, 0x00000000u,
|
|
};
|
|
const uint32_t importTable[] = {
|
|
0x41E00000u, 0u, 0x00000101u,
|
|
0x6C747374u, 0x00006269u, // "tstlib"
|
|
0x03E00008u, 0x24000003u, 0u, 0u,
|
|
};
|
|
|
|
std::memset(host.guest.data() + address, 0, codeOffset + program.filesz);
|
|
std::memcpy(host.guest.data() + address, &header, sizeof(header));
|
|
std::memcpy(host.guest.data() + address + sizeof(header), &program, sizeof(program));
|
|
std::memcpy(host.guest.data() + address + codeOffset, code, sizeof(code));
|
|
std::memcpy(host.guest.data() + address + codeOffset + 0x40u, importTable, sizeof(importTable));
|
|
}
|
|
|
|
void writeVblankSchedulingIrx(TestHost &host, uint32_t address)
|
|
{
|
|
constexpr uint32_t codeOffset = 0x100u;
|
|
constexpr uint32_t loadAddress = 0x00010000u;
|
|
constexpr uint32_t highThreadAddress = loadAddress + 0x100u;
|
|
constexpr uint32_t lowThreadAddress = loadAddress + 0x140u;
|
|
constexpr uint32_t thbaseTableAddress = loadAddress + 0x180u;
|
|
constexpr uint32_t createThreadStub = thbaseTableAddress + 20u;
|
|
constexpr uint32_t startThreadStub = createThreadStub + 8u;
|
|
constexpr uint32_t vblankTableAddress = loadAddress + 0x1C0u;
|
|
constexpr uint32_t waitVblankEndStub = vblankTableAddress + 20u;
|
|
constexpr uint32_t highThreadDescriptor = loadAddress + 0x300u;
|
|
constexpr uint32_t lowThreadDescriptor = loadAddress + 0x320u;
|
|
constexpr uint32_t lowThreadMarker = loadAddress + 0x400u;
|
|
|
|
ElfHeader header{};
|
|
header.ident[0] = 0x7Fu; header.ident[1] = 'E'; header.ident[2] = 'L'; header.ident[3] = 'F';
|
|
header.ident[4] = 1u; header.ident[5] = 1u; header.ident[6] = 1u;
|
|
header.type = 2u; header.machine = 8u; header.version = 1u;
|
|
header.entry = loadAddress; header.phoff = sizeof(ElfHeader);
|
|
header.ehsize = sizeof(ElfHeader); header.phentsize = sizeof(ProgramHeader); header.phnum = 1u;
|
|
|
|
ProgramHeader program{};
|
|
program.type = 1u; program.offset = codeOffset; program.vaddr = loadAddress; program.paddr = loadAddress;
|
|
program.filesz = 0x500u; program.memsz = 0x500u; program.flags = 7u; program.align = 4u;
|
|
|
|
const auto jal = [](uint32_t target) { return 0x0C000000u | ((target >> 2u) & 0x03FFFFFFu); };
|
|
const auto jump = [](uint32_t target) { return 0x08000000u | ((target >> 2u) & 0x03FFFFFFu); };
|
|
const uint32_t entry[] = {
|
|
0x27BDFFE0u, 0xAFBF001Cu,
|
|
0x3C040001u, 0x34840300u, jal(createThreadStub), 0x00000000u,
|
|
0x00408021u, // move s0, v0
|
|
0x3C040001u, 0x34840320u, jal(createThreadStub), 0x00000000u,
|
|
0x00408821u, // move s1, v0
|
|
0x02002021u, 0x00002821u, jal(startThreadStub), 0x00000000u,
|
|
0x02202021u, 0x00002821u, jal(startThreadStub), 0x00000000u,
|
|
0x8FBF001Cu, 0x00001021u, 0x27BD0020u, 0x03E00008u, 0x00000000u,
|
|
};
|
|
const uint32_t highThread[] = {
|
|
jal(waitVblankEndStub), 0x00000000u,
|
|
jump(highThreadAddress), 0x00000000u,
|
|
};
|
|
const uint32_t lowThread[] = {
|
|
0x3C080001u, 0x35080400u,
|
|
0x24090001u, 0xAD090000u,
|
|
0x03E00008u, 0x00000000u,
|
|
};
|
|
const uint32_t thbaseImports[] = {
|
|
0x41E00000u, 0u, 0x00000101u,
|
|
0x61626874u, 0x00006573u, // "thbase"
|
|
0x03E00008u, 0x24000004u, // CreateThread
|
|
0x03E00008u, 0x24000006u, // StartThread
|
|
0u, 0u,
|
|
};
|
|
const uint32_t vblankImports[] = {
|
|
0x41E00000u, 0u, 0x00000101u,
|
|
0x616C6276u, 0x00006B6Eu, // "vblank"
|
|
0x03E00008u, 0x24000005u, // WaitVblankEnd
|
|
0u, 0u,
|
|
};
|
|
const uint32_t highDescriptor[] = {0u, 0u, highThreadAddress, 0x400u, 10u};
|
|
const uint32_t lowDescriptor[] = {0u, 0u, lowThreadAddress, 0x400u, 20u};
|
|
|
|
std::vector<uint8_t> segment(program.filesz, 0u);
|
|
const auto put = [&](uint32_t offset, const void *data, size_t size)
|
|
{
|
|
std::memcpy(segment.data() + offset, data, size);
|
|
};
|
|
put(0u, entry, sizeof(entry));
|
|
put(0x100u, highThread, sizeof(highThread));
|
|
put(0x140u, lowThread, sizeof(lowThread));
|
|
put(0x180u, thbaseImports, sizeof(thbaseImports));
|
|
put(0x1C0u, vblankImports, sizeof(vblankImports));
|
|
put(0x300u, highDescriptor, sizeof(highDescriptor));
|
|
put(0x320u, lowDescriptor, sizeof(lowDescriptor));
|
|
|
|
std::memset(host.guest.data() + address, 0, codeOffset + program.filesz);
|
|
std::memcpy(host.guest.data() + address, &header, sizeof(header));
|
|
std::memcpy(host.guest.data() + address + sizeof(header), &program, sizeof(program));
|
|
std::memcpy(host.guest.data() + address + codeOffset, segment.data(), segment.size());
|
|
}
|
|
|
|
void writeSifDmaIrx(TestHost &host, uint32_t address)
|
|
{
|
|
constexpr uint32_t codeOffset = 0x100u;
|
|
constexpr uint32_t loadAddress = 0x00010000u;
|
|
constexpr uint32_t importTableAddress = loadAddress + 0x100u;
|
|
constexpr uint32_t setDmaStub = importTableAddress + 20u;
|
|
constexpr uint32_t dmaStatStub = setDmaStub + 8u;
|
|
constexpr uint32_t descriptorAddress = loadAddress + 0x180u;
|
|
constexpr uint32_t payloadAddress = loadAddress + 0x1A0u;
|
|
constexpr uint32_t eeDestination = 0xC00u;
|
|
|
|
ElfHeader header{};
|
|
header.ident[0] = 0x7Fu; header.ident[1] = 'E'; header.ident[2] = 'L'; header.ident[3] = 'F';
|
|
header.ident[4] = 1u; header.ident[5] = 1u; header.ident[6] = 1u;
|
|
header.type = 2u; header.machine = 8u; header.version = 1u;
|
|
header.entry = loadAddress; header.phoff = sizeof(ElfHeader);
|
|
header.ehsize = sizeof(ElfHeader); header.phentsize = sizeof(ProgramHeader); header.phnum = 1u;
|
|
|
|
ProgramHeader program{};
|
|
program.type = 1u; program.offset = codeOffset; program.vaddr = loadAddress; program.paddr = loadAddress;
|
|
program.filesz = 0x200u; program.memsz = 0x200u; program.flags = 7u; program.align = 4u;
|
|
|
|
const auto jal = [](uint32_t target) { return 0x0C000000u | ((target >> 2u) & 0x03FFFFFFu); };
|
|
const uint32_t entry[] = {
|
|
0x27BDFFF0u, 0xAFBF000Cu,
|
|
0x3C040001u, 0x34840180u, 0x24050001u,
|
|
jal(setDmaStub), 0x00000000u,
|
|
0x00402021u, // move a0, v0
|
|
jal(dmaStatStub), 0x00000000u,
|
|
0x8FBF000Cu, 0x27BD0010u, 0x03E00008u, 0x00000000u,
|
|
};
|
|
const uint32_t imports[] = {
|
|
0x41E00000u, 0u, 0x00000101u,
|
|
0x6D666973u, 0x00006E61u, // "sifman"
|
|
0x03E00008u, 0x24000007u, // sceSifSetDma
|
|
0x03E00008u, 0x24000008u, // sceSifDmaStat
|
|
0u, 0u,
|
|
};
|
|
const uint32_t descriptor[] = {
|
|
payloadAddress, eeDestination, sizeof(uint32_t), 0u,
|
|
};
|
|
constexpr uint32_t payload = 0x53494621u; // "SIF!"
|
|
|
|
std::vector<uint8_t> segment(program.filesz, 0u);
|
|
std::memcpy(segment.data(), entry, sizeof(entry));
|
|
std::memcpy(segment.data() + 0x100u, imports, sizeof(imports));
|
|
std::memcpy(segment.data() + 0x180u, descriptor, sizeof(descriptor));
|
|
std::memcpy(segment.data() + 0x1A0u, &payload, sizeof(payload));
|
|
std::memset(host.guest.data() + address, 0, codeOffset + program.filesz);
|
|
std::memcpy(host.guest.data() + address, &header, sizeof(header));
|
|
std::memcpy(host.guest.data() + address + sizeof(header), &program, sizeof(program));
|
|
std::memcpy(host.guest.data() + address + codeOffset, segment.data(), segment.size());
|
|
}
|
|
|
|
void writeMcmanRegistrationIrx(TestHost &host, uint32_t address)
|
|
{
|
|
constexpr uint32_t codeOffset = 0x100u;
|
|
constexpr uint32_t loadAddress = 0x00010000u;
|
|
constexpr uint32_t initAddress = loadAddress + 0x100u;
|
|
constexpr uint32_t deinitAddress = loadAddress + 0x120u;
|
|
constexpr uint32_t callbackAddress = loadAddress + 0x140u;
|
|
constexpr uint32_t deviceAddress = loadAddress + 0x200u;
|
|
constexpr uint32_t operationsAddress = loadAddress + 0x220u;
|
|
constexpr uint32_t nameAddress = loadAddress + 0x280u;
|
|
constexpr uint32_t markerAddress = loadAddress + 0x290u;
|
|
constexpr uint32_t secrmanTableAddress = loadAddress + 0x300u;
|
|
constexpr uint32_t secrCommandStub = secrmanTableAddress + 20u;
|
|
constexpr uint32_t secrDeviceIdStub = secrCommandStub + 8u;
|
|
constexpr uint32_t modloadTableAddress = loadAddress + 0x340u;
|
|
constexpr uint32_t setKelfCallbackStub = modloadTableAddress + 20u;
|
|
constexpr uint32_t iomanTableAddress = loadAddress + 0x380u;
|
|
constexpr uint32_t deleteMissingDriverStub = iomanTableAddress + 20u;
|
|
constexpr uint32_t addDriverStub = deleteMissingDriverStub + 8u;
|
|
constexpr uint32_t deleteDriverStub = addDriverStub + 8u;
|
|
|
|
ElfHeader header{};
|
|
header.ident[0] = 0x7Fu; header.ident[1] = 'E'; header.ident[2] = 'L'; header.ident[3] = 'F';
|
|
header.ident[4] = 1u; header.ident[5] = 1u; header.ident[6] = 1u;
|
|
header.type = 2u; header.machine = 8u; header.version = 1u;
|
|
header.entry = loadAddress; header.phoff = sizeof(ElfHeader);
|
|
header.ehsize = sizeof(ElfHeader); header.phentsize = sizeof(ProgramHeader); header.phnum = 1u;
|
|
|
|
ProgramHeader program{};
|
|
program.type = 1u; program.offset = codeOffset; program.vaddr = loadAddress; program.paddr = loadAddress;
|
|
program.filesz = 0x400u; program.memsz = 0x400u; program.flags = 7u; program.align = 4u;
|
|
|
|
const auto jal = [](uint32_t target) { return 0x0C000000u | ((target >> 2u) & 0x03FFFFFFu); };
|
|
const auto jump = [](uint32_t target) { return 0x08000000u | ((target >> 2u) & 0x03FFFFFFu); };
|
|
constexpr uint32_t epilogueAddress = loadAddress + 35u * sizeof(uint32_t);
|
|
const uint32_t entry[] = {
|
|
0x27BDFFE0u, 0xAFBF001Cu,
|
|
0x3C040001u, 0x34840140u, jal(secrCommandStub), 0x00000000u,
|
|
0x3C040001u, 0x34840140u, jal(secrDeviceIdStub), 0x00000000u,
|
|
0x3C040001u, 0x34840140u, jal(setKelfCallbackStub), 0x00000000u,
|
|
0x3C040001u, 0x34840280u, jal(deleteMissingDriverStub), 0x00000000u,
|
|
0x3C040001u, 0x34840200u, jal(addDriverStub), 0x00000000u,
|
|
0x1440000Bu, 0x00000000u, // bnez v0, failure
|
|
0x3C040001u, 0x34840280u, jal(deleteDriverStub), 0x00000000u,
|
|
0x14400005u, 0x00000000u, // bnez v0, failure
|
|
0x3C080001u, 0x8D020290u, jump(epilogueAddress), 0x00000000u,
|
|
0x2402FFFFu, // failure: return -1
|
|
0x8FBF001Cu, 0x27BD0020u, 0x03E00008u, 0x00000000u,
|
|
};
|
|
const uint32_t init[] = {
|
|
0x3C080001u, 0x35080290u, 0x24090001u, 0xAD090000u,
|
|
0x03E00008u, 0x00001021u,
|
|
};
|
|
const uint32_t deinit[] = {
|
|
0x3C080001u, 0x35080290u, 0x8D090000u, 0x00000000u,
|
|
0x25290001u, 0xAD090000u, 0x03E00008u, 0x00001021u,
|
|
};
|
|
const uint32_t callback[] = {0x03E00008u, 0x00001021u};
|
|
const uint32_t device[] = {nameAddress, 0u, 0u, 0u, operationsAddress};
|
|
uint32_t operations[17]{};
|
|
operations[0] = initAddress;
|
|
operations[1] = deinitAddress;
|
|
const char name[] = "mc";
|
|
const uint32_t secrmanImports[] = {
|
|
0x41E00000u, 0u, 0x00000104u,
|
|
0x72636573u, 0x006E616Du, // "secrman"
|
|
0x03E00008u, 0x24000004u,
|
|
0x03E00008u, 0x24000005u,
|
|
0u, 0u,
|
|
};
|
|
const uint32_t modloadImports[] = {
|
|
0x41E00000u, 0u, 0x00000101u,
|
|
0x6C646F6Du, 0x0064616Fu, // "modload"
|
|
0x03E00008u, 0x2400000Du,
|
|
0u, 0u,
|
|
};
|
|
const uint32_t iomanImports[] = {
|
|
0x41E00000u, 0u, 0x00000101u,
|
|
0x616D6F69u, 0x0000006Eu, // "ioman"
|
|
0x03E00008u, 0x24000015u, // DelDrv
|
|
0x03E00008u, 0x24000014u, // AddDrv
|
|
0x03E00008u, 0x24000015u, // DelDrv
|
|
0u, 0u,
|
|
};
|
|
|
|
std::vector<uint8_t> segment(program.filesz, 0u);
|
|
const auto put = [&](uint32_t offset, const void *data, size_t size)
|
|
{
|
|
std::memcpy(segment.data() + offset, data, size);
|
|
};
|
|
put(0x000u, entry, sizeof(entry));
|
|
put(0x100u, init, sizeof(init));
|
|
put(0x120u, deinit, sizeof(deinit));
|
|
put(0x140u, callback, sizeof(callback));
|
|
put(0x200u, device, sizeof(device));
|
|
put(0x220u, operations, sizeof(operations));
|
|
put(0x280u, name, sizeof(name));
|
|
put(0x300u, secrmanImports, sizeof(secrmanImports));
|
|
put(0x340u, modloadImports, sizeof(modloadImports));
|
|
put(0x380u, iomanImports, sizeof(iomanImports));
|
|
|
|
std::memset(host.guest.data() + address, 0, codeOffset + program.filesz);
|
|
std::memcpy(host.guest.data() + address, &header, sizeof(header));
|
|
std::memcpy(host.guest.data() + address + sizeof(header), &program, sizeof(program));
|
|
std::memcpy(host.guest.data() + address + codeOffset, segment.data(), segment.size());
|
|
}
|
|
|
|
void writeCdvdLifecycleIrx(TestHost &host, uint32_t address)
|
|
{
|
|
constexpr uint32_t codeOffset = 0x100u;
|
|
constexpr uint32_t loadAddress = 0x00010000u;
|
|
constexpr uint32_t importTableAddress = loadAddress + 0x100u;
|
|
constexpr uint32_t initStub = importTableAddress + 20u;
|
|
constexpr uint32_t callbackStub = initStub + 8u;
|
|
constexpr uint32_t epilogueAddress = loadAddress + 26u * sizeof(uint32_t);
|
|
|
|
ElfHeader header{};
|
|
header.ident[0] = 0x7Fu; header.ident[1] = 'E'; header.ident[2] = 'L'; header.ident[3] = 'F';
|
|
header.ident[4] = 1u; header.ident[5] = 1u; header.ident[6] = 1u;
|
|
header.type = 2u; header.machine = 8u; header.version = 1u;
|
|
header.entry = loadAddress; header.phoff = sizeof(ElfHeader);
|
|
header.ehsize = sizeof(ElfHeader); header.phentsize = sizeof(ProgramHeader); header.phnum = 1u;
|
|
|
|
ProgramHeader program{};
|
|
program.type = 1u; program.offset = codeOffset; program.vaddr = loadAddress; program.paddr = loadAddress;
|
|
program.filesz = 0x140u; program.memsz = 0x140u; program.flags = 7u; program.align = 4u;
|
|
|
|
const auto jal = [](uint32_t target) { return 0x0C000000u | ((target >> 2u) & 0x03FFFFFFu); };
|
|
const auto jump = [](uint32_t target) { return 0x08000000u | ((target >> 2u) & 0x03FFFFFFu); };
|
|
const uint32_t entry[] = {
|
|
0x27BDFFF0u, 0xAFBF000Cu,
|
|
0x00002021u, jal(initStub), 0x00000000u,
|
|
0x24080001u, 0x14480012u, 0x00000000u,
|
|
0x3C041234u, 0x34845678u, jal(callbackStub), 0x00000000u,
|
|
0x1440000Cu, 0x00000000u,
|
|
0x3C0489ABu, 0x3484CDEFu, jal(callbackStub), 0x00000000u,
|
|
0x3C081234u, 0x35085678u, 0x14480004u, 0x00000000u,
|
|
0x00001021u, jump(epilogueAddress), 0x00000000u,
|
|
0x2402FFFFu,
|
|
0x8FBF000Cu, 0x27BD0010u, 0x03E00008u, 0x00000000u,
|
|
};
|
|
const uint32_t imports[] = {
|
|
0x41E00000u, 0u, 0x00000101u,
|
|
0x64766463u, 0x006E616Du, // "cdvdman"
|
|
0x03E00008u, 0x24000004u, // sceCdInit
|
|
0x03E00008u, 0x24000025u, // sceCdCallback
|
|
0u, 0u,
|
|
};
|
|
|
|
std::vector<uint8_t> segment(program.filesz, 0u);
|
|
std::memcpy(segment.data(), entry, sizeof(entry));
|
|
std::memcpy(segment.data() + 0x100u, imports, sizeof(imports));
|
|
std::memset(host.guest.data() + address, 0, codeOffset + program.filesz);
|
|
std::memcpy(host.guest.data() + address, &header, sizeof(header));
|
|
std::memcpy(host.guest.data() + address + sizeof(header), &program, sizeof(program));
|
|
std::memcpy(host.guest.data() + address + codeOffset, segment.data(), segment.size());
|
|
}
|
|
|
|
void writeCdvdPvdReadIrx(TestHost &host, uint32_t address)
|
|
{
|
|
constexpr uint32_t codeOffset = 0x100u;
|
|
constexpr uint32_t loadAddress = 0x00010000u;
|
|
constexpr uint32_t readBuffer = loadAddress + 0x800u;
|
|
constexpr uint32_t importTableAddress = loadAddress + 0x100u;
|
|
constexpr uint32_t callbackStub = importTableAddress + 20u;
|
|
constexpr uint32_t readStub = callbackStub + 8u;
|
|
constexpr uint32_t callbackFunction = loadAddress + 0x180u;
|
|
constexpr uint32_t epilogueAddress = loadAddress + 26u * sizeof(uint32_t);
|
|
|
|
ElfHeader header{};
|
|
header.ident[0] = 0x7Fu; header.ident[1] = 'E'; header.ident[2] = 'L'; header.ident[3] = 'F';
|
|
header.ident[4] = 1u; header.ident[5] = 1u; header.ident[6] = 1u;
|
|
header.type = 2u; header.machine = 8u; header.version = 1u;
|
|
header.entry = loadAddress; header.phoff = sizeof(ElfHeader);
|
|
header.ehsize = sizeof(ElfHeader); header.phentsize = sizeof(ProgramHeader); header.phnum = 1u;
|
|
|
|
ProgramHeader program{};
|
|
program.type = 1u; program.offset = codeOffset; program.vaddr = loadAddress; program.paddr = loadAddress;
|
|
program.filesz = 0x200u; program.memsz = 0x1000u; program.flags = 7u; program.align = 4u;
|
|
|
|
const auto jal = [](uint32_t target) { return 0x0C000000u | ((target >> 2u) & 0x03FFFFFFu); };
|
|
const auto jump = [](uint32_t target) { return 0x08000000u | ((target >> 2u) & 0x03FFFFFFu); };
|
|
const uint32_t entry[] = {
|
|
0x27BDFFF0u, 0xAFBF000Cu,
|
|
0x3C040001u, 0x34840180u, jal(callbackStub), 0x00000000u,
|
|
0x24040010u, 0x24050001u,
|
|
0x3C060001u, 0x34C60800u, jal(readStub), 0x00000000u,
|
|
0x1040000Cu, 0x00000000u,
|
|
0x90C80000u, 0x24090001u, 0x15090008u, 0x00000000u,
|
|
0x90C80001u, 0x24090043u, 0x15090004u, 0x00000000u,
|
|
0x00001021u, jump(epilogueAddress), 0x00000000u,
|
|
0x2402FFFFu,
|
|
0x8FBF000Cu, 0x27BD0010u, 0x03E00008u, 0x00000000u,
|
|
};
|
|
const uint32_t imports[] = {
|
|
0x41E00000u, 0u, 0x00000101u,
|
|
0x64766463u, 0x006E616Du, // "cdvdman"
|
|
0x03E00008u, 0x24000025u, // sceCdCallback
|
|
0x03E00008u, 0x24000006u, // sceCdRead
|
|
0u, 0u,
|
|
};
|
|
const uint32_t callback[] = {
|
|
0x3C080001u, 0x350801C0u, 0xAD040000u,
|
|
0x03E00008u, 0x00000000u,
|
|
};
|
|
|
|
std::vector<uint8_t> segment(program.filesz, 0u);
|
|
std::memcpy(segment.data(), entry, sizeof(entry));
|
|
std::memcpy(segment.data() + 0x100u, imports, sizeof(imports));
|
|
std::memcpy(segment.data() + (callbackFunction - loadAddress), callback, sizeof(callback));
|
|
std::memset(host.guest.data() + address, 0, codeOffset + program.filesz);
|
|
std::memcpy(host.guest.data() + address, &header, sizeof(header));
|
|
std::memcpy(host.guest.data() + address + sizeof(header), &program, sizeof(program));
|
|
std::memcpy(host.guest.data() + address + codeOffset, segment.data(), segment.size());
|
|
}
|
|
|
|
void writeCdvdSeekIrx(TestHost &host, uint32_t address)
|
|
{
|
|
constexpr uint32_t codeOffset = 0x100u;
|
|
constexpr uint32_t loadAddress = 0x00010000u;
|
|
constexpr uint32_t importTableAddress = loadAddress + 0x100u;
|
|
constexpr uint32_t initStub = importTableAddress + 20u;
|
|
constexpr uint32_t callbackStub = initStub + 8u;
|
|
constexpr uint32_t seekStub = callbackStub + 8u;
|
|
constexpr uint32_t callbackFunction = loadAddress + 0x180u;
|
|
|
|
ElfHeader header{};
|
|
header.ident[0] = 0x7Fu; header.ident[1] = 'E'; header.ident[2] = 'L'; header.ident[3] = 'F';
|
|
header.ident[4] = 1u; header.ident[5] = 1u; header.ident[6] = 1u;
|
|
header.type = 2u; header.machine = 8u; header.version = 1u;
|
|
header.entry = loadAddress; header.phoff = sizeof(ElfHeader);
|
|
header.ehsize = sizeof(ElfHeader); header.phentsize = sizeof(ProgramHeader); header.phnum = 1u;
|
|
|
|
ProgramHeader program{};
|
|
program.type = 1u; program.offset = codeOffset; program.vaddr = loadAddress; program.paddr = loadAddress;
|
|
program.filesz = 0x200u; program.memsz = 0x200u; program.flags = 7u; program.align = 4u;
|
|
|
|
const auto jal = [](uint32_t target) { return 0x0C000000u | ((target >> 2u) & 0x03FFFFFFu); };
|
|
const uint32_t entry[] = {
|
|
0x27BDFFF0u, 0xAFBF000Cu,
|
|
0x00002021u, jal(initStub), 0x00000000u,
|
|
0x3C040001u, 0x34840180u, jal(callbackStub), 0x00000000u,
|
|
0x24041234u, jal(seekStub), 0x00000000u,
|
|
0x8FBF000Cu, 0x27BD0010u, 0x03E00008u, 0x00000000u,
|
|
};
|
|
const uint32_t imports[] = {
|
|
0x41E00000u, 0u, 0x00000101u,
|
|
0x64766463u, 0x006E616Du, // "cdvdman"
|
|
0x03E00008u, 0x24000004u, // sceCdInit
|
|
0x03E00008u, 0x24000025u, // sceCdCallback
|
|
0x03E00008u, 0x24000007u, // sceCdSeek
|
|
0u, 0u,
|
|
};
|
|
const uint32_t callback[] = {
|
|
0x3C080001u, 0x350801C0u, 0xAD040000u,
|
|
0x03E00008u, 0x00001021u,
|
|
};
|
|
|
|
std::vector<uint8_t> segment(program.filesz, 0u);
|
|
std::memcpy(segment.data(), entry, sizeof(entry));
|
|
std::memcpy(segment.data() + 0x100u, imports, sizeof(imports));
|
|
std::memcpy(segment.data() + (callbackFunction - loadAddress), callback, sizeof(callback));
|
|
std::memset(host.guest.data() + address, 0, codeOffset + program.filesz);
|
|
std::memcpy(host.guest.data() + address, &header, sizeof(header));
|
|
std::memcpy(host.guest.data() + address + sizeof(header), &program, sizeof(program));
|
|
std::memcpy(host.guest.data() + address + codeOffset, segment.data(), segment.size());
|
|
}
|
|
|
|
bool expect(bool value, const char *message)
|
|
{
|
|
if (!value)
|
|
std::cerr << "FAIL: " << message << '\n';
|
|
return value;
|
|
}
|
|
}
|
|
|
|
int main()
|
|
{
|
|
TestHost host(0x20000u);
|
|
IopSubsystem iop(host);
|
|
|
|
writeMinimalIrx(host, 0x100u);
|
|
const ModuleLoadResult result = iop.loadModuleBuffer(0x100u);
|
|
const DebugSnapshot snapshot = iop.debugSnapshot();
|
|
|
|
if (!expect(result.handled, "Emulator must claim IRX loads")) return 1;
|
|
if (!expect(result.moduleId == 1, "First emulated IRX must get module id 1")) return 1;
|
|
if (!expect(result.startResult == 7, "R3000A branch delay slot result mismatch")) return 1;
|
|
if (!expect(snapshot.emulatorLoadedModules == 1u, "Loaded-module debug count mismatch")) return 1;
|
|
if (!expect(snapshot.emulatorInstructions >= 5u, "Instruction counter did not advance")) return 1;
|
|
|
|
int32_t stopResult = -1;
|
|
if (!expect(iop.stopModule(result.moduleId, &stopResult), "Emulated module stop failed")) return 1;
|
|
if (!expect(stopResult == 0, "Emulated module stop result mismatch")) return 1;
|
|
if (!expect(iop.debugSnapshot().emulatorLoadedModules == 0u, "Module was not released")) return 1;
|
|
|
|
constexpr uint32_t rpcSid = 0xF00DCAFEu;
|
|
writeRpcServerIrx(host, 0x100u);
|
|
const ModuleLoadResult rpcModule = iop.loadModuleBuffer(0x100u);
|
|
if (!expect(rpcModule.handled && rpcModule.startResult == 0,
|
|
"Synthetic RPC server IRX did not start")) return 1;
|
|
if (!expect(iop.debugSnapshot().emulatorRpcServers == 1u,
|
|
"Registered RPC server count mismatch")) return 1;
|
|
if (!expect(iop.canBindRpc(rpcSid),
|
|
"Emulator did not expose the SID registered by the IRX")) return 1;
|
|
|
|
iop.reset();
|
|
if (!expect(!iop.canBindRpc(rpcSid),
|
|
"IOP reset did not remove the registered RPC server")) return 1;
|
|
|
|
constexpr uint32_t lotrSoundSid = 0x00012345u;
|
|
writeRpcServerIrx(host, 0x100u, lotrSoundSid);
|
|
const ModuleLoadResult physicalSoundModule = iop.loadModuleBuffer(0x100u);
|
|
if (!expect(physicalSoundModule.handled && physicalSoundModule.startResult == 0,
|
|
"Synthetic physical sound RPC server did not start")) return 1;
|
|
|
|
const uint32_t soundHandler[] = {
|
|
0x3C020001u, // lui v0, 1
|
|
0x34420400u, // ori v0, v0, 0x400 (registered server buffer)
|
|
0x03E00008u, // jr ra
|
|
0x00000000u, // nop
|
|
};
|
|
if (!expect(iop.writeMemory(0x00010300u, soundHandler, sizeof(soundHandler)),
|
|
"Could not install the physical sound RPC handler")) return 1;
|
|
constexpr uint32_t soundPayload = 0x1234ABCDu;
|
|
std::memcpy(host.guest.data() + 0x800u, &soundPayload, sizeof(soundPayload));
|
|
RpcRequest soundRequest{};
|
|
soundRequest.sid = lotrSoundSid;
|
|
soundRequest.send = {0x800u, sizeof(soundPayload)};
|
|
soundRequest.receive = {0x900u, sizeof(soundPayload)};
|
|
const uint64_t soundInstructionsBefore = iop.debugSnapshot().emulatorInstructions;
|
|
const RpcResult soundResult = iop.handleRpc(soundRequest);
|
|
uint32_t soundResponse = 0u;
|
|
std::memcpy(&soundResponse, host.guest.data() + 0x900u, sizeof(soundResponse));
|
|
if (!expect(soundResult.handled && soundResponse == soundPayload,
|
|
"Physical sound RPC did not return its own payload")) return 1;
|
|
if (!expect(iop.debugSnapshot().emulatorInstructions > soundInstructionsBefore,
|
|
"Sound RPC did not execute the physical IOP handler")) return 1;
|
|
|
|
constexpr uint32_t relocatableRpcSid = 0xA11CE001u;
|
|
writeRelocatableRpcServerIrx(host, 0x100u);
|
|
const ModuleLoadResult relocatableRpcModule = iop.loadModuleBuffer(0x100u);
|
|
if (!expect(relocatableRpcModule.handled && relocatableRpcModule.startResult == 0,
|
|
"Relocatable RPC server IRX did not start")) return 1;
|
|
if (!expect(iop.canBindRpc(relocatableRpcSid),
|
|
"R_MIPS_26 did not relocate a symbol-less IRX import call")) return 1;
|
|
|
|
RpcRequest relocatableRequest{};
|
|
relocatableRequest.sid = relocatableRpcSid;
|
|
relocatableRequest.receive = {0x800u, sizeof(uint32_t)};
|
|
const uint64_t rpcInstructionsBefore = iop.debugSnapshot().emulatorInstructions;
|
|
const RpcResult relocatableRpcResult = iop.handleRpc(relocatableRequest);
|
|
const uint64_t rpcInstructions = iop.debugSnapshot().emulatorInstructions - rpcInstructionsBefore;
|
|
if (!expect(relocatableRpcResult.handled,
|
|
"Relocatable IRX RPC handler was not dispatched")) return 1;
|
|
if (!expect(rpcInstructions < 100u,
|
|
"R_MIPS_HI16/LO16 did not relocate the IRX RPC handler")) return 1;
|
|
uint32_t relocatedGpData = 0u;
|
|
std::memcpy(&relocatedGpData, host.guest.data() + relocatableRequest.receive.address,
|
|
sizeof(relocatedGpData));
|
|
if (!expect(relocatedGpData == 0x47505250u,
|
|
"Physical RPC callback did not inherit the registering module's GP")) return 1;
|
|
|
|
iop.reset();
|
|
writeExportProviderIrx(host, 0x100u);
|
|
const ModuleLoadResult exportProvider = iop.loadModuleBuffer(0x100u);
|
|
if (!expect(exportProvider.handled && exportProvider.startResult == 0,
|
|
"Synthetic export provider did not register")) return 1;
|
|
writeExportConsumerIrx(host, 0x500u);
|
|
const ModuleLoadResult exportConsumer = iop.loadModuleBuffer(0x500u);
|
|
if (!expect(exportConsumer.handled && exportConsumer.startResult == 0x42,
|
|
"IRX export ordinal was shifted by implicit function slots")) return 1;
|
|
|
|
iop.reset();
|
|
constexpr uint32_t eeSource = 0x100u;
|
|
constexpr uint32_t iopBuffer = 0x500u;
|
|
constexpr uint32_t eeDestination = 0x900u;
|
|
const uint32_t transferPayload = 0x53494621u; // "SIF!"
|
|
std::memcpy(host.guest.data() + eeSource, &transferPayload, sizeof(transferPayload));
|
|
if (!expect(iop.writeMemory(iopBuffer, &transferPayload, sizeof(transferPayload)),
|
|
"Could not initialize physical IOP memory")) return 1;
|
|
iop.onSifTransfer({
|
|
SifTransferKind::SetDma,
|
|
SifTransferPhase::AfterCopy,
|
|
eeSource,
|
|
iopBuffer,
|
|
sizeof(transferPayload),
|
|
});
|
|
std::memset(host.guest.data() + iopBuffer, 0, sizeof(transferPayload));
|
|
iop.onSifTransfer({
|
|
SifTransferKind::GetOtherData,
|
|
SifTransferPhase::BeforeCopy,
|
|
iopBuffer,
|
|
eeDestination,
|
|
sizeof(transferPayload),
|
|
});
|
|
uint32_t stagedIopPayload = 0u;
|
|
std::memcpy(&stagedIopPayload, host.guest.data() + iopBuffer, sizeof(stagedIopPayload));
|
|
if (!expect(stagedIopPayload == 0u,
|
|
"SIF notification aliased physical IOP memory into EE RAM")) return 1;
|
|
uint32_t physicalIopPayload = 0u;
|
|
if (!expect(iop.readMemory(iopBuffer, &physicalIopPayload, sizeof(physicalIopPayload)) &&
|
|
physicalIopPayload == transferPayload,
|
|
"SIF notification corrupted physical IOP memory")) return 1;
|
|
|
|
iop.reset();
|
|
constexpr uint32_t sifDmaDestination = 0xC00u;
|
|
writeSifDmaIrx(host, 0x100u);
|
|
const ModuleLoadResult sifDmaModule = iop.loadModuleBuffer(0x100u);
|
|
uint32_t sifDmaPayload = 0u;
|
|
std::memcpy(&sifDmaPayload, host.guest.data() + sifDmaDestination,
|
|
sizeof(sifDmaPayload));
|
|
if (!expect(sifDmaModule.handled && sifDmaModule.startResult == -1,
|
|
"sceSifDmaStat did not report the synchronous transfer as complete")) return 1;
|
|
if (!expect(sifDmaPayload == 0x53494621u,
|
|
"IOP sceSifSetDma did not copy the payload into EE memory")) return 1;
|
|
|
|
iop.reset();
|
|
writeVblankSchedulingIrx(host, 0x100u);
|
|
const ModuleLoadResult vblankModule = iop.loadModuleBuffer(0x100u);
|
|
if (!expect(vblankModule.handled && vblankModule.startResult == 0,
|
|
"Synthetic VBlank scheduling IRX did not start")) return 1;
|
|
iop.runEeCycles(8u * 4096u);
|
|
iop.onSifTransfer({
|
|
SifTransferKind::GetOtherData,
|
|
SifTransferPhase::BeforeCopy,
|
|
0x00010400u,
|
|
0x1000u,
|
|
sizeof(uint32_t),
|
|
});
|
|
uint32_t lowPriorityMarker = 0u;
|
|
if (!expect(iop.readMemory(0x00010400u, &lowPriorityMarker, sizeof(lowPriorityMarker)),
|
|
"Could not read the IOP scheduling marker")) return 1;
|
|
if (!expect(lowPriorityMarker == 1u,
|
|
"WaitVblankEnd returned immediately and starved a lower-priority IOP thread")) return 1;
|
|
|
|
iop.reset();
|
|
host.logs.clear();
|
|
writeMcmanRegistrationIrx(host, 0x100u);
|
|
const ModuleLoadResult mcmanRegistration = iop.loadModuleBuffer(0x100u);
|
|
if (mcmanRegistration.startResult != 2)
|
|
{
|
|
std::cerr << "MCMAN synthetic start result: " << mcmanRegistration.startResult << '\n';
|
|
for (const auto &message : host.logs)
|
|
std::cerr << message << '\n';
|
|
}
|
|
if (!expect(mcmanRegistration.handled && mcmanRegistration.startResult == 2,
|
|
"MCMAN callback registration or IOMAN AddDrv/DelDrv lifecycle failed")) return 1;
|
|
const bool emittedUnhandledImport = std::any_of(
|
|
host.logs.begin(), host.logs.end(),
|
|
[](const std::string &message) { return message.find("unhandled import") != std::string::npos; });
|
|
if (!expect(!emittedUnhandledImport,
|
|
"MCMAN registration still emitted an unhandled IOP import")) return 1;
|
|
|
|
iop.reset();
|
|
host.logs.clear();
|
|
writeCdvdLifecycleIrx(host, 0x100u);
|
|
const ModuleLoadResult cdvdLifecycle = iop.loadModuleBuffer(0x100u);
|
|
if (!expect(cdvdLifecycle.handled && cdvdLifecycle.startResult == 0,
|
|
"CDVD init or callback replacement semantics failed")) return 1;
|
|
const bool emittedUnhandledCdvdImport = std::any_of(
|
|
host.logs.begin(), host.logs.end(),
|
|
[](const std::string &message) { return message.find("unhandled import cdvdman") != std::string::npos; });
|
|
if (!expect(!emittedUnhandledCdvdImport,
|
|
"CDVD lifecycle still emitted an unhandled IOP import")) return 1;
|
|
|
|
iop.reset();
|
|
writeCdvdLifecycleIrx(host, 0x100u);
|
|
const ModuleLoadResult cdvdAfterReset = iop.loadModuleBuffer(0x100u);
|
|
if (!expect(cdvdAfterReset.handled && cdvdAfterReset.startResult == 0,
|
|
"IOP reset did not clear the CDVD callback")) return 1;
|
|
|
|
iop.reset();
|
|
const auto uniqueSuffix = std::chrono::steady_clock::now().time_since_epoch().count();
|
|
const std::filesystem::path virtualCdRoot =
|
|
std::filesystem::temp_directory_path() /
|
|
("ps2x-iop-cdvd-" + std::to_string(uniqueSuffix));
|
|
std::error_code cdRootError;
|
|
std::filesystem::create_directory(virtualCdRoot, cdRootError);
|
|
if (!expect(!cdRootError, "Could not create the virtual-CD test directory")) return 1;
|
|
host.cdRoot = virtualCdRoot.string();
|
|
writeCdvdPvdReadIrx(host, 0x100u);
|
|
const ModuleLoadResult cdvdPvdRead = iop.loadModuleBuffer(0x100u);
|
|
iop.runEeCycles(4096u);
|
|
iop.onSifTransfer({
|
|
SifTransferKind::GetOtherData,
|
|
SifTransferPhase::BeforeCopy,
|
|
0x000101C0u,
|
|
0x1000u,
|
|
sizeof(uint32_t),
|
|
});
|
|
uint32_t cdvdCallbackReason = 0u;
|
|
if (!expect(iop.readMemory(0x000101C0u, &cdvdCallbackReason, sizeof(cdvdCallbackReason)),
|
|
"Could not read the physical CDVD callback result")) return 1;
|
|
host.cdRoot.clear();
|
|
std::filesystem::remove(virtualCdRoot, cdRootError);
|
|
if (!expect(cdvdPvdRead.handled && cdvdPvdRead.startResult == 0,
|
|
"CDVD did not expose the extracted CD root as an ISO9660 PVD")) return 1;
|
|
if (!expect(cdvdCallbackReason == 1u,
|
|
"CDVD read completion did not invoke the registered guest callback")) return 1;
|
|
|
|
iop.reset();
|
|
host.logs.clear();
|
|
writeCdvdSeekIrx(host, 0x100u);
|
|
const ModuleLoadResult cdvdSeek = iop.loadModuleBuffer(0x100u);
|
|
iop.runEeCycles(4096u);
|
|
iop.onSifTransfer({
|
|
SifTransferKind::GetOtherData,
|
|
SifTransferPhase::BeforeCopy,
|
|
0x000101C0u,
|
|
0x1100u,
|
|
sizeof(uint32_t),
|
|
});
|
|
uint32_t cdvdSeekCallbackReason = 0u;
|
|
if (!expect(iop.readMemory(0x000101C0u, &cdvdSeekCallbackReason, sizeof(cdvdSeekCallbackReason)),
|
|
"Could not read the physical CDVD callback result")) return 1;
|
|
if (!expect(cdvdSeek.handled && cdvdSeek.startResult == 1,
|
|
"sceCdSeek did not accept a valid LBN")) return 1;
|
|
if (!expect(cdvdSeekCallbackReason == 4u,
|
|
"sceCdSeek completion did not invoke the registered callback")) return 1;
|
|
const bool emittedUnhandledSeek = std::any_of(
|
|
host.logs.begin(), host.logs.end(),
|
|
[](const std::string &message)
|
|
{ return message.find("unhandled import cdvdman:7") != std::string::npos; });
|
|
if (!expect(!emittedUnhandledSeek,
|
|
"sceCdSeek still emitted an unhandled IOP import")) return 1;
|
|
|
|
std::cout << "ps2xIOP emulator smoke tests passed\n";
|
|
return 0;
|
|
}
|