feat: split runtime code in small files to be easy to develop (#56)

* feat: split runtime code in small files to be easy to develop

* feat: split stubs in inl files

* feat: function auto link function treat functions with underscore as same as without underscore

* feat: remove underscore prefix from stubs
This commit is contained in:
Ranieri
2026-02-17 03:00:59 -03:00
committed by GitHub
parent aa76cdbbf7
commit c4555f6723
28 changed files with 11112 additions and 11228 deletions
+13 -11
View File
@@ -499,17 +499,19 @@ namespace ps2recomp
}
else
{
switch (resolveStubTarget(function.name))
const std::string_view resolvedSyscallName = ps2_runtime_calls::resolveSyscallName(function.name);
const std::string_view resolvedStubName = ps2_runtime_calls::resolveStubName(function.name);
if (!resolvedSyscallName.empty())
{
stub << "ps2_syscalls::" << resolvedSyscallName << "(rdram, ctx, runtime); ";
}
else if (!resolvedStubName.empty())
{
stub << "ps2_stubs::" << resolvedStubName << "(rdram, ctx, runtime); ";
}
else
{
case StubTarget::Syscall:
stub << "ps2_syscalls::" << function.name << "(rdram, ctx, runtime); ";
break;
case StubTarget::Stub:
stub << "ps2_stubs::" << function.name << "(rdram, ctx, runtime); ";
break;
default:
stub << "ps2_stubs::TODO_NAMED(\"" << escapeCStringLiteral(function.name) << "\", rdram, ctx, runtime); ";
break;
}
}
@@ -1039,11 +1041,11 @@ namespace ps2recomp
StubTarget PS2Recompiler::resolveStubTarget(const std::string &name)
{
if (ps2_runtime_calls::isSyscallName(name))
if (!ps2_runtime_calls::resolveSyscallName(name).empty())
{
return StubTarget::Syscall;
}
if (ps2_runtime_calls::isStubName(name))
if (!ps2_runtime_calls::resolveStubName(name).empty())
{
return StubTarget::Stub;
}
+13 -4
View File
@@ -29,11 +29,20 @@ file(GLOB RUNNER_SRC_FILES CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/src/runner/*.cpp"
)
set(RUNNER_MAIN_CPP "${CMAKE_CURRENT_SOURCE_DIR}/src/runner/main.cpp")
set(ROOT_MAIN_CPP "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp")
if(EXISTS "${RUNNER_MAIN_CPP}")
list(APPEND RUNNER_SRC_FILES "${RUNNER_MAIN_CPP}")
elseif(EXISTS "${ROOT_MAIN_CPP}")
list(APPEND RUNNER_SRC_FILES "${ROOT_MAIN_CPP}")
endif()
add_executable(ps2EntryRunner
${RUNNER_SRC_FILES}
)
if (MSVC)
if(MSVC)
target_compile_options(ps2EntryRunner PRIVATE /FS /Z7)
endif()
@@ -42,14 +51,14 @@ target_include_directories(ps2_runtime PUBLIC
)
target_link_libraries(ps2_runtime PRIVATE raylib)
target_link_libraries(ps2EntryRunner
PRIVATE
target_link_libraries(ps2EntryRunner
PRIVATE
ps2_runtime
raylib
)
# Work around WinAPI vs raylib symbol clash for CloseWindow on x64
if (MSVC)
if(MSVC)
target_link_options(ps2EntryRunner PRIVATE "/FORCE:MULTIPLE")
endif()
+29 -27
View File
@@ -71,7 +71,7 @@
X(SifRemoveRpc) \
X(sceSifCallRpc) \
X(sceSifSendCmd) \
X(_sceRpcGetPacket) \
X(sceRpcGetPacket) \
\
X(fioOpen) \
X(fioClose) \
@@ -93,7 +93,10 @@
X(SetOsdConfigParam) \
X(GetRomName) \
X(SifLoadElfPart) \
X(sceSifLoadElf) \
X(sceSifLoadElfPart) \
X(sceSifLoadModule) \
X(sceSifLoadModuleBuffer) \
\
X(SetupThread) \
X(QueryBootMode) \
@@ -103,13 +106,12 @@
// Stubs
#define PS2_STUB_LIST(X) \
/* Std/Libc */ \
X(_calloc_r) \
X(_free_r) \
X(_malloc_r) \
X(_malloc_trim_r) \
X(_mbtowc_r) \
X(_printf) \
X(_printf_r) \
X(calloc_r) \
X(free_r) \
X(malloc_r) \
X(malloc_trim_r) \
X(mbtowc_r) \
X(printf_r) \
X(abs) \
X(__ieee754_rem_pio2f) \
X(__kernel_cosf) \
@@ -176,25 +178,25 @@
X(DmaAddr) \
X(Pad_init) \
X(Pad_set) \
X(_builtin_set_imask) \
X(_sceCdRI) \
X(_sceCdRM) \
X(_sceFsDbChk) \
X(_sceFsIntrSigSema) \
X(_sceFsSemExit) \
X(_sceFsSemInit) \
X(_sceFsSigSema) \
X(_sceIDC) \
X(_sceMpegFlush) \
X(_sceRpcFreePacket) \
X(_sceRpcGetFPacket) \
X(_sceRpcGetFPacket2) \
X(_sceSDC) \
X(_sceSifCmdIntrHdlr) \
X(_sceSifLoadElfPart) \
X(_sceSifLoadModule) \
X(_sceSifSendCmd) \
X(_sceVu0ecossin) \
X(builtin_set_imask) \
X(sceCdRI) \
X(sceCdRM) \
X(sceFsDbChk) \
X(sceFsIntrSigSema) \
X(sceFsSemExit) \
X(sceFsSemInit) \
X(sceFsSigSema) \
X(sceIDC) \
X(sceMpegFlush) \
X(sceRpcFreePacket) \
X(sceRpcGetFPacket) \
X(sceRpcGetFPacket2) \
X(sceSDC) \
X(sceSifCmdIntrHdlr) \
X(sceSifLoadElfPart) \
X(sceSifLoadModule) \
X(sceSifSendCmd) \
X(sceVu0ecossin) \
X(iopGetArea) \
X(mcCallMessageTypeSe) \
X(mcCheckReadStartConfigFile) \
+333
View File
@@ -0,0 +1,333 @@
#ifndef PS2_MEMORY_H
#define PS2_MEMORY_H
#include <cstddef>
#include <cstdint>
#include <vector>
#include <unordered_map>
#include <atomic>
#if defined(_MSC_VER)
#include <intrin.h>
#elif defined(USE_SSE2NEON)
#include "sse2neon.h"
#else
#include <immintrin.h> // For SSE/AVX instructions
#include <smmintrin.h> // For SSE4.1 instructions
#endif
constexpr uint32_t PS2_RAM_SIZE = 32u * 1024u * 1024u; // 32MB
constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1u; // Mask for 32MB alignment
constexpr uint32_t PS2_RAM_BASE = 0x00000000; // Physical base of RDRAM
constexpr uint32_t PS2_SCRATCHPAD_BASE = 0x70000000;
constexpr uint32_t PS2_SCRATCHPAD_SIZE = 16u * 1024u; // 16KB
constexpr uint32_t PS2_IO_BASE = 0x10000000; // Base for many I/O regs (Timers, DMAC, INTC)
constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB
constexpr uint32_t PS2_BIOS_BASE = 0x1FC00000; // Or BFC00000 depending on KSEG
constexpr uint32_t PS2_BIOS_SIZE = 4u * 1024u * 1024u; // 4MB
constexpr uint32_t PS2_VU0_CODE_BASE = 0x11000000; // Base address as seen from EE
constexpr uint32_t PS2_VU0_DATA_BASE = 0x11004000;
constexpr uint32_t PS2_VU0_CODE_SIZE = 4u * 1024u; // 4KB Micro Memory
constexpr uint32_t PS2_VU0_DATA_SIZE = 4u * 1024u; // 4KB Data Memory (VU Mem)
constexpr uint32_t PS2_VU1_CODE_BASE = 0x11008000;
constexpr uint32_t PS2_VU1_DATA_BASE = 0x1100C000;
constexpr uint32_t PS2_VU1_MEM_BASE = PS2_VU1_CODE_BASE; // Alias used by older code paths
constexpr uint32_t PS2_VU1_CODE_SIZE = 16u * 1024u; // 16KB Micro Memory
constexpr uint32_t PS2_VU1_DATA_SIZE = 16u * 1024u; // 16KB Data Memory (VU Mem)
constexpr uint32_t PS2_GS_BASE = 0x12000000;
constexpr uint32_t PS2_GS_PRIV_REG_BASE = PS2_GS_BASE; // GS Privileged Registers
constexpr uint32_t PS2_GS_PRIV_REG_SIZE = 0x2000;
constexpr size_t PS2_GS_VRAM_SIZE = 4u * 1024u * 1024u; // 4MB GS VRAM
inline constexpr uint32_t PS2_FIO_O_RDONLY = 0x0001;
inline constexpr uint32_t PS2_FIO_O_WRONLY = 0x0002;
inline constexpr uint32_t PS2_FIO_O_RDWR = 0x0003;
inline constexpr uint32_t PS2_FIO_O_NBLOCK = 0x0010;
inline constexpr uint32_t PS2_FIO_O_APPEND = 0x0100;
inline constexpr uint32_t PS2_FIO_O_CREAT = 0x0200;
inline constexpr uint32_t PS2_FIO_O_TRUNC = 0x0400;
inline constexpr uint32_t PS2_FIO_O_EXCL = 0x0800;
inline constexpr uint32_t PS2_FIO_O_NOWAIT = 0x8000;
inline constexpr uint32_t PS2_FIO_SEEK_SET = 0;
inline constexpr uint32_t PS2_FIO_SEEK_CUR = 1;
inline constexpr uint32_t PS2_FIO_SEEK_END = 2;
inline constexpr uint32_t PS2_FIO_S_IFDIR = 0x1000;
inline constexpr uint32_t PS2_FIO_S_IFREG = 0x2000;
static_assert((PS2_RAM_SIZE & (PS2_RAM_SIZE - 1u)) == 0u, "PS2_RAM_SIZE must be a power of two");
static_assert(PS2_RAM_MASK == (PS2_RAM_SIZE - 1u), "PS2_RAM_MASK must match PS2_RAM_SIZE");
inline std::atomic<uint8_t *> &ps2ScratchpadHostPtrStorage()
{
static std::atomic<uint8_t *> ptr{nullptr};
return ptr;
}
inline void ps2SetScratchpadHostPtr(uint8_t *ptr)
{
ps2ScratchpadHostPtrStorage().store(ptr, std::memory_order_relaxed);
}
inline uint8_t *ps2GetScratchpadHostPtr()
{
return ps2ScratchpadHostPtrStorage().load(std::memory_order_relaxed);
}
inline bool ps2ResolveGuestPointer(uint32_t addr, uint32_t &offset, bool &scratch)
{
if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE))
{
scratch = true;
offset = addr - PS2_SCRATCHPAD_BASE;
return true;
}
uint32_t phys = 0;
if (addr < 0x20000000u)
{
phys = addr;
}
else if ((addr >= 0x20000000u && addr < 0x40000000u) ||
(addr >= 0x80000000u && addr < 0xC0000000u))
{
phys = addr & 0x1FFFFFFFu;
}
else
{
// Keep legacy runtime behavior for odd upper-bit aliases used by game code.
phys = addr & PS2_RAM_MASK;
}
if (phys >= PS2_RAM_SIZE)
{
phys &= PS2_RAM_MASK;
}
scratch = false;
offset = phys;
return true;
}
inline uint8_t *getMemPtr(uint8_t *rdram, uint32_t addr)
{
if (rdram == nullptr)
{
return nullptr;
}
uint32_t offset = 0;
bool scratch = false;
if (!ps2ResolveGuestPointer(addr, offset, scratch))
{
return nullptr;
}
if (scratch)
{
uint8_t *scratchpad = ps2GetScratchpadHostPtr();
return scratchpad ? (scratchpad + offset) : nullptr;
}
return rdram + offset;
}
inline const uint8_t *getConstMemPtr(const uint8_t *rdram, uint32_t addr)
{
if (rdram == nullptr)
{
return nullptr;
}
uint32_t offset = 0;
bool scratch = false;
if (!ps2ResolveGuestPointer(addr, offset, scratch))
{
return nullptr;
}
if (scratch)
{
const uint8_t *scratchpad = ps2GetScratchpadHostPtr();
return scratchpad ? (scratchpad + offset) : nullptr;
}
return rdram + offset;
}
// PS2 GS (Graphics Synthesizer) registers
struct GSRegisters
{
uint64_t pmode; // Pixel mode
uint64_t smode1; // Sync mode 1
uint64_t smode2; // Sync mode 2
uint64_t srfsh; // Refresh control
uint64_t synch1; // Synchronization control 1
uint64_t synch2; // Synchronization control 2
uint64_t syncv; // Synchronization control V
uint64_t dispfb1; // Display buffer 1
uint64_t display1; // Display area 1
uint64_t dispfb2; // Display buffer 2
uint64_t display2; // Display area 2
uint64_t extbuf; // External buffer
uint64_t extdata; // External data
uint64_t extwrite; // External write
uint64_t bgcolor; // Background color
uint64_t csr; // Status
uint64_t imr; // Interrupt mask
uint64_t busdir; // Bus direction
uint64_t siglblid; // Signal label ID
};
static_assert(sizeof(GSRegisters) == (19u * sizeof(uint64_t)), "GSRegisters layout changed unexpectedly");
static_assert(alignof(GSRegisters) == alignof(uint64_t), "GSRegisters alignment must remain 64-bit");
// PS2 VIF (VPU Interface) registers
struct VIFRegisters
{
uint32_t stat; // Status
uint32_t fbrst; // VIF Force Break
uint32_t err; // Error status
uint32_t mark; // Interrupt control
uint32_t cycle; // Transfer mode
uint32_t mode; // Mode control
uint32_t num; // Data amount counter
uint32_t mask; // Data mask
uint32_t code; // VIFcode
uint32_t itops; // ITOP save
uint32_t base; // Base address
uint32_t ofst; // Offset
uint32_t tops; // TOPS
uint32_t itop; // ITOP
uint32_t top; // TOP
uint32_t row[4]; // Transfer row data
uint32_t col[4]; // Transfer column data
};
static_assert(sizeof(VIFRegisters) == (23u * sizeof(uint32_t)), "VIFRegisters layout changed unexpectedly");
// PS2 DMA registers
struct DMARegisters
{
uint32_t chcr; // Channel control
uint32_t madr; // Memory address
uint32_t qwc; // Quadword count
uint32_t tadr; // Tag address
uint32_t asr0; // Address stack 0
uint32_t asr1; // Address stack 1
uint32_t sadr; // Source address
};
static_assert(sizeof(DMARegisters) == (7u * sizeof(uint32_t)), "DMARegisters layout changed unexpectedly");
struct JumpTable
{
uint32_t address = 0; // Base address of the jump table
uint32_t baseRegister = 0; // Register used for index
std::vector<uint32_t> targets; // Jump targets
};
class PS2Memory
{
public:
PS2Memory();
~PS2Memory();
PS2Memory(const PS2Memory &) = delete;
PS2Memory &operator=(const PS2Memory &) = delete;
PS2Memory(PS2Memory &&) = delete;
PS2Memory &operator=(PS2Memory &&) = delete;
// Initialize memory
bool initialize(size_t ramSize = PS2_RAM_SIZE);
// Memory access methods
uint8_t *getRDRAM() { return m_rdram; }
uint8_t *getScratchpad() { return m_scratchpad; }
uint8_t *getIOPRAM() { return iop_ram; }
uint64_t dmaStartCount() const { return m_dmaStartCount.load(std::memory_order_relaxed); }
uint64_t gifCopyCount() const { return m_gifCopyCount.load(std::memory_order_relaxed); }
uint64_t gsWriteCount() const { return m_gsWriteCount.load(std::memory_order_relaxed); }
uint64_t vifWriteCount() const { return m_vifWriteCount.load(std::memory_order_relaxed); }
// Read/write memory
uint8_t read8(uint32_t address);
uint16_t read16(uint32_t address);
uint32_t read32(uint32_t address);
uint64_t read64(uint32_t address);
__m128i read128(uint32_t address);
void write8(uint32_t address, uint8_t value);
void write16(uint32_t address, uint16_t value);
void write32(uint32_t address, uint32_t value);
void write64(uint32_t address, uint64_t value);
void write128(uint32_t address, __m128i value);
// TLB handling
uint32_t translateAddress(uint32_t virtualAddress);
bool tlbRead(uint32_t index, uint32_t &vpn, uint32_t &pfn, uint32_t &mask, bool &valid) const;
bool tlbWrite(uint32_t index, uint32_t vpn, uint32_t pfn, uint32_t mask, bool valid);
int32_t tlbProbe(uint32_t vpn) const;
size_t tlbEntryCount() const { return m_tlbEntries.size(); }
// Hardware register interface
bool writeIORegister(uint32_t address, uint32_t value);
uint32_t readIORegister(uint32_t address);
// Track code modifications for self-modifying code
void registerCodeRegion(uint32_t start, uint32_t end);
bool isCodeModified(uint32_t address, uint32_t size);
void clearModifiedFlag(uint32_t address, uint32_t size);
// GS register accessors
GSRegisters &gs() { return gs_regs; }
const GSRegisters &gs() const { return gs_regs; }
uint8_t *getGSVRAM() { return m_gsVRAM; }
const uint8_t *getGSVRAM() const { return m_gsVRAM; }
bool hasSeenGifCopy() const { return m_seenGifCopy; }
// Main RAM (32MB)
uint8_t *m_rdram;
// Scratchpad memory (16KB)
uint8_t *m_scratchpad;
// IOP RAM (2MB)
uint8_t *iop_ram;
bool m_seenGifCopy;
std::atomic<uint64_t> m_dmaStartCount{0};
std::atomic<uint64_t> m_gifCopyCount{0};
std::atomic<uint64_t> m_gsWriteCount{0};
std::atomic<uint64_t> m_vifWriteCount{0};
// I/O registers
std::unordered_map<uint32_t, uint32_t> m_ioRegisters;
// Registers
GSRegisters gs_regs;
uint8_t *m_gsVRAM;
VIFRegisters vif0_regs;
VIFRegisters vif1_regs;
DMARegisters dma_regs[10]; // 10 DMA channels
// TLB entries
struct TLBEntry
{
uint32_t vpn;
uint32_t pfn;
uint32_t mask;
bool valid;
};
std::vector<TLBEntry> m_tlbEntries;
struct CodeRegion
{
uint32_t start;
uint32_t end;
std::vector<bool> modified; // Bitmap of modified 4-byte blocks
};
std::vector<CodeRegion> m_codeRegions;
bool isAddressInRegion(uint32_t address, const CodeRegion &region);
void markModified(uint32_t address, uint32_t size);
bool isScratchpad(uint32_t address) const;
};
#endif // PS2_MEMORY_H
+2 -314
View File
@@ -21,51 +21,7 @@
#include <iostream>
#include <iomanip>
constexpr uint32_t PS2_RAM_SIZE = 32u * 1024u * 1024u; // 32MB
constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1u; // Mask for 32MB alignment
constexpr uint32_t PS2_RAM_BASE = 0x00000000; // Physical base of RDRAM
constexpr uint32_t PS2_SCRATCHPAD_BASE = 0x70000000;
constexpr uint32_t PS2_SCRATCHPAD_SIZE = 16u * 1024u; // 16KB
constexpr uint32_t PS2_IO_BASE = 0x10000000; // Base for many I/O regs (Timers, DMAC, INTC)
constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB
constexpr uint32_t PS2_BIOS_BASE = 0x1FC00000; // Or BFC00000 depending on KSEG
constexpr uint32_t PS2_BIOS_SIZE = 4u * 1024u * 1024u; // 4MB
constexpr uint32_t PS2_VU0_CODE_BASE = 0x11000000; // Base address as seen from EE
constexpr uint32_t PS2_VU0_DATA_BASE = 0x11004000;
constexpr uint32_t PS2_VU0_CODE_SIZE = 4u * 1024u; // 4KB Micro Memory
constexpr uint32_t PS2_VU0_DATA_SIZE = 4u * 1024u; // 4KB Data Memory (VU Mem)
constexpr uint32_t PS2_VU1_CODE_BASE = 0x11008000;
constexpr uint32_t PS2_VU1_DATA_BASE = 0x1100C000;
constexpr uint32_t PS2_VU1_MEM_BASE = PS2_VU1_CODE_BASE; // Alias used by older code paths
constexpr uint32_t PS2_VU1_CODE_SIZE = 16u * 1024u; // 16KB Micro Memory
constexpr uint32_t PS2_VU1_DATA_SIZE = 16u * 1024u; // 16KB Data Memory (VU Mem)
constexpr uint32_t PS2_GS_BASE = 0x12000000;
constexpr uint32_t PS2_GS_PRIV_REG_BASE = PS2_GS_BASE; // GS Privileged Registers
constexpr uint32_t PS2_GS_PRIV_REG_SIZE = 0x2000;
constexpr size_t PS2_GS_VRAM_SIZE = 4u * 1024u * 1024u; // 4MB GS VRAM
inline constexpr uint32_t PS2_FIO_O_RDONLY = 0x0001;
inline constexpr uint32_t PS2_FIO_O_WRONLY = 0x0002;
inline constexpr uint32_t PS2_FIO_O_RDWR = 0x0003;
inline constexpr uint32_t PS2_FIO_O_NBLOCK = 0x0010;
inline constexpr uint32_t PS2_FIO_O_APPEND = 0x0100;
inline constexpr uint32_t PS2_FIO_O_CREAT = 0x0200;
inline constexpr uint32_t PS2_FIO_O_TRUNC = 0x0400;
inline constexpr uint32_t PS2_FIO_O_EXCL = 0x0800;
inline constexpr uint32_t PS2_FIO_O_NOWAIT = 0x8000;
inline constexpr uint32_t PS2_FIO_SEEK_SET = 0;
inline constexpr uint32_t PS2_FIO_SEEK_CUR = 1;
inline constexpr uint32_t PS2_FIO_SEEK_END = 2;
inline constexpr uint32_t PS2_FIO_S_IFDIR = 0x1000;
inline constexpr uint32_t PS2_FIO_S_IFREG = 0x2000;
static_assert((PS2_RAM_SIZE & (PS2_RAM_SIZE - 1u)) == 0u, "PS2_RAM_SIZE must be a power of two");
static_assert(PS2_RAM_MASK == (PS2_RAM_SIZE - 1u), "PS2_RAM_MASK must match PS2_RAM_SIZE");
#include "ps2_memory.h"
enum PS2Exception
{
@@ -386,275 +342,6 @@ inline void ps2TraceGuestRangeWrite(uint8_t *rdram,
std::cout << std::endl;
}
inline std::atomic<uint8_t *> &ps2ScratchpadHostPtrStorage()
{
static std::atomic<uint8_t *> ptr{nullptr};
return ptr;
}
inline void ps2SetScratchpadHostPtr(uint8_t *ptr)
{
ps2ScratchpadHostPtrStorage().store(ptr, std::memory_order_relaxed);
}
inline uint8_t *ps2GetScratchpadHostPtr()
{
return ps2ScratchpadHostPtrStorage().load(std::memory_order_relaxed);
}
inline bool ps2ResolveGuestPointer(uint32_t addr, uint32_t &offset, bool &scratch)
{
if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE))
{
scratch = true;
offset = addr - PS2_SCRATCHPAD_BASE;
return true;
}
uint32_t phys = 0;
if (addr < 0x20000000u)
{
phys = addr;
}
else if ((addr >= 0x20000000u && addr < 0x40000000u) ||
(addr >= 0x80000000u && addr < 0xC0000000u))
{
phys = addr & 0x1FFFFFFFu;
}
else
{
// Keep legacy runtime behavior for odd upper-bit aliases used by game code.
phys = addr & PS2_RAM_MASK;
}
if (phys >= PS2_RAM_SIZE)
{
phys &= PS2_RAM_MASK;
}
scratch = false;
offset = phys;
return true;
}
inline uint8_t *getMemPtr(uint8_t *rdram, uint32_t addr)
{
if (rdram == nullptr)
{
return nullptr;
}
uint32_t offset = 0;
bool scratch = false;
if (!ps2ResolveGuestPointer(addr, offset, scratch))
{
return nullptr;
}
if (scratch)
{
uint8_t *scratchpad = ps2GetScratchpadHostPtr();
return scratchpad ? (scratchpad + offset) : nullptr;
}
return rdram + offset;
}
inline const uint8_t *getConstMemPtr(const uint8_t *rdram, uint32_t addr)
{
if (rdram == nullptr)
{
return nullptr;
}
uint32_t offset = 0;
bool scratch = false;
if (!ps2ResolveGuestPointer(addr, offset, scratch))
{
return nullptr;
}
if (scratch)
{
const uint8_t *scratchpad = ps2GetScratchpadHostPtr();
return scratchpad ? (scratchpad + offset) : nullptr;
}
return rdram + offset;
}
// PS2 GS (Graphics Synthesizer) registers
struct GSRegisters
{
uint64_t pmode; // Pixel mode
uint64_t smode1; // Sync mode 1
uint64_t smode2; // Sync mode 2
uint64_t srfsh; // Refresh control
uint64_t synch1; // Synchronization control 1
uint64_t synch2; // Synchronization control 2
uint64_t syncv; // Synchronization control V
uint64_t dispfb1; // Display buffer 1
uint64_t display1; // Display area 1
uint64_t dispfb2; // Display buffer 2
uint64_t display2; // Display area 2
uint64_t extbuf; // External buffer
uint64_t extdata; // External data
uint64_t extwrite; // External write
uint64_t bgcolor; // Background color
uint64_t csr; // Status
uint64_t imr; // Interrupt mask
uint64_t busdir; // Bus direction
uint64_t siglblid; // Signal label ID
};
static_assert(sizeof(GSRegisters) == (19u * sizeof(uint64_t)), "GSRegisters layout changed unexpectedly");
static_assert(alignof(GSRegisters) == alignof(uint64_t), "GSRegisters alignment must remain 64-bit");
// PS2 VIF (VPU Interface) registers
struct VIFRegisters
{
uint32_t stat; // Status
uint32_t fbrst; // VIF Force Break
uint32_t err; // Error status
uint32_t mark; // Interrupt control
uint32_t cycle; // Transfer mode
uint32_t mode; // Mode control
uint32_t num; // Data amount counter
uint32_t mask; // Data mask
uint32_t code; // VIFcode
uint32_t itops; // ITOP save
uint32_t base; // Base address
uint32_t ofst; // Offset
uint32_t tops; // TOPS
uint32_t itop; // ITOP
uint32_t top; // TOP
uint32_t row[4]; // Transfer row data
uint32_t col[4]; // Transfer column data
};
static_assert(sizeof(VIFRegisters) == (23u * sizeof(uint32_t)), "VIFRegisters layout changed unexpectedly");
// PS2 DMA registers
struct DMARegisters
{
uint32_t chcr; // Channel control
uint32_t madr; // Memory address
uint32_t qwc; // Quadword count
uint32_t tadr; // Tag address
uint32_t asr0; // Address stack 0
uint32_t asr1; // Address stack 1
uint32_t sadr; // Source address
};
static_assert(sizeof(DMARegisters) == (7u * sizeof(uint32_t)), "DMARegisters layout changed unexpectedly");
struct JumpTable
{
uint32_t address = 0; // Base address of the jump table
uint32_t baseRegister = 0; // Register used for index
std::vector<uint32_t> targets; // Jump targets
};
class PS2Memory
{
public:
PS2Memory();
~PS2Memory();
PS2Memory(const PS2Memory &) = delete;
PS2Memory &operator=(const PS2Memory &) = delete;
PS2Memory(PS2Memory &&) = delete;
PS2Memory &operator=(PS2Memory &&) = delete;
// Initialize memory
bool initialize(size_t ramSize = PS2_RAM_SIZE);
// Memory access methods
uint8_t *getRDRAM() { return m_rdram; }
uint8_t *getScratchpad() { return m_scratchpad; }
uint8_t *getIOPRAM() { return iop_ram; }
uint64_t dmaStartCount() const { return m_dmaStartCount.load(std::memory_order_relaxed); }
uint64_t gifCopyCount() const { return m_gifCopyCount.load(std::memory_order_relaxed); }
uint64_t gsWriteCount() const { return m_gsWriteCount.load(std::memory_order_relaxed); }
uint64_t vifWriteCount() const { return m_vifWriteCount.load(std::memory_order_relaxed); }
// Read/write memory
uint8_t read8(uint32_t address);
uint16_t read16(uint32_t address);
uint32_t read32(uint32_t address);
uint64_t read64(uint32_t address);
__m128i read128(uint32_t address);
void write8(uint32_t address, uint8_t value);
void write16(uint32_t address, uint16_t value);
void write32(uint32_t address, uint32_t value);
void write64(uint32_t address, uint64_t value);
void write128(uint32_t address, __m128i value);
// TLB handling
uint32_t translateAddress(uint32_t virtualAddress);
bool tlbRead(uint32_t index, uint32_t &vpn, uint32_t &pfn, uint32_t &mask, bool &valid) const;
bool tlbWrite(uint32_t index, uint32_t vpn, uint32_t pfn, uint32_t mask, bool valid);
int32_t tlbProbe(uint32_t vpn) const;
size_t tlbEntryCount() const { return m_tlbEntries.size(); }
// Hardware register interface
bool writeIORegister(uint32_t address, uint32_t value);
uint32_t readIORegister(uint32_t address);
// Track code modifications for self-modifying code
void registerCodeRegion(uint32_t start, uint32_t end);
bool isCodeModified(uint32_t address, uint32_t size);
void clearModifiedFlag(uint32_t address, uint32_t size);
// GS register accessors
GSRegisters &gs() { return gs_regs; }
const GSRegisters &gs() const { return gs_regs; }
uint8_t *getGSVRAM() { return m_gsVRAM; }
const uint8_t *getGSVRAM() const { return m_gsVRAM; }
bool hasSeenGifCopy() const { return m_seenGifCopy; }
// Main RAM (32MB)
uint8_t *m_rdram;
// Scratchpad memory (16KB)
uint8_t *m_scratchpad;
// IOP RAM (2MB)
uint8_t *iop_ram;
bool m_seenGifCopy;
std::atomic<uint64_t> m_dmaStartCount{0};
std::atomic<uint64_t> m_gifCopyCount{0};
std::atomic<uint64_t> m_gsWriteCount{0};
std::atomic<uint64_t> m_vifWriteCount{0};
// I/O registers
std::unordered_map<uint32_t, uint32_t> m_ioRegisters;
// Registers
GSRegisters gs_regs;
uint8_t *m_gsVRAM;
VIFRegisters vif0_regs;
VIFRegisters vif1_regs;
DMARegisters dma_regs[10]; // 10 DMA channels
// TLB entries
struct TLBEntry
{
uint32_t vpn;
uint32_t pfn;
uint32_t mask;
bool valid;
};
std::vector<TLBEntry> m_tlbEntries;
struct CodeRegion
{
uint32_t start;
uint32_t end;
std::vector<bool> modified; // Bitmap of modified 4-byte blocks
};
std::vector<CodeRegion> m_codeRegions;
bool isAddressInRegion(uint32_t address, const CodeRegion &region);
void markModified(uint32_t address, uint32_t size);
bool isScratchpad(uint32_t address) const;
};
class PS2Runtime
{
public:
@@ -816,3 +503,4 @@ private:
};
#endif // PS2_RUNTIME_H
+59 -16
View File
@@ -1,5 +1,7 @@
#pragma once
#include <algorithm>
#include <cstddef>
#include <string_view>
#include "ps2_call_list.h"
@@ -17,27 +19,68 @@ namespace ps2_runtime_calls
#undef PS2_STUB_NAME
};
namespace detail
{
template <std::size_t N>
inline std::string_view findExact(
std::string_view name,
const std::string_view (&entries)[N])
{
const auto it = std::ranges::find(entries, name);
return (it == std::end(entries)) ? std::string_view{} : *it;
}
template <std::size_t N>
inline std::string_view resolveNameWithOptionalLeadingUnderscoreAlias(
std::string_view name,
const std::string_view (&entries)[N])
{
if (name.empty())
{
return {};
}
if (const std::string_view exact = findExact(name, entries); !exact.empty())
{
return exact;
}
if (name.starts_with('_'))
{
return findExact(name.substr(1), entries);
}
for (auto entry : entries)
{
if (entry.size() == name.size() + 1 &&
entry.starts_with('_') &&
entry.substr(1) == name)
{
return entry;
}
}
return {};
}
}
inline std::string_view resolveSyscallName(std::string_view name)
{
return detail::resolveNameWithOptionalLeadingUnderscoreAlias(name, kSyscallNames);
}
inline std::string_view resolveStubName(std::string_view name)
{
return detail::resolveNameWithOptionalLeadingUnderscoreAlias(name, kStubNames);
}
inline bool isSyscallName(std::string_view name)
{
for (auto entry : kSyscallNames)
{
if (entry == name)
{
return true;
}
}
return false;
return !resolveSyscallName(name).empty();
}
inline bool isStubName(std::string_view name)
{
for (auto entry : kStubNames)
{
if (entry == name)
{
return true;
}
}
return false;
return !resolveStubName(name).empty();
}
}
-3
View File
@@ -17,9 +17,6 @@ namespace ps2_syscalls
PS2_SYSCALL_LIST(PS2_DECLARE_SYSCALL)
#undef PS2_DECLARE_SYSCALL
void sceSifLoadElf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void sceSifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId);
}
+2 -1
View File
@@ -1,8 +1,9 @@
#include "ps2_runtime.h"
#include "ps2_memory.h"
#include <iostream>
#include <cstring>
#include <stdexcept>
#include <algorithm>
#include <string>
namespace
{
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+317
View File
@@ -0,0 +1,317 @@
void sceGsExecLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t imgAddr = getRegU32(ctx, 4);
uint32_t srcAddr = getRegU32(ctx, 5);
GsImageMem img{};
if (!runtime || !readGsImage(rdram, imgAddr, img))
{
setReturnS32(ctx, -1);
return;
}
const uint32_t rowBytes = bytesForPixels(img.psm, static_cast<uint32_t>(img.width));
if (rowBytes == 0)
{
setReturnS32(ctx, -1);
return;
}
uint32_t fbw = img.vram_width ? img.vram_width : std::max<uint32_t>(1, (img.width + 63) / 64);
uint32_t base = static_cast<uint32_t>(img.vram_addr) * 2048u;
uint32_t stride = bytesForPixels(img.psm, fbw * 64u);
if (stride == 0)
{
setReturnS32(ctx, -1);
return;
}
uint8_t *gsvram = runtime->memory().getGSVRAM();
uint8_t *src = getMemPtr(rdram, srcAddr);
if (!gsvram || !src)
{
setReturnS32(ctx, -1);
return;
}
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub sceGsExecLoadImage: x=" << img.x
<< " y=" << img.y
<< " w=" << img.width
<< " h=" << img.height
<< " vram=0x" << std::hex << img.vram_addr
<< " fbw=" << std::dec << static_cast<int>(fbw)
<< " psm=" << static_cast<int>(img.psm)
<< " src=0x" << std::hex << srcAddr << std::dec << std::endl;
++logCount;
}
for (uint32_t row = 0; row < img.height; ++row)
{
uint32_t dstOff = base + (static_cast<uint32_t>(img.y) + row) * stride + bytesForPixels(img.psm, static_cast<uint32_t>(img.x));
uint32_t srcOff = row * rowBytes;
if (dstOff >= PS2_GS_VRAM_SIZE)
break;
uint32_t copyBytes = rowBytes;
if (dstOff + copyBytes > PS2_GS_VRAM_SIZE)
copyBytes = PS2_GS_VRAM_SIZE - dstOff;
std::memcpy(gsvram + dstOff, src + srcOff, copyBytes);
}
if (img.width >= 320 && img.height >= 200)
{
auto &gs = runtime->memory().gs();
gs.dispfb1 = makeDispFb(img.vram_addr, fbw, img.psm, 0, 0);
gs.display1 = makeDisplay(0, 0, 0, 0, img.width - 1, img.height - 1);
}
setReturnS32(ctx, 0);
}
void sceGsExecStoreImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t imgAddr = getRegU32(ctx, 4);
uint32_t dstAddr = getRegU32(ctx, 5);
GsImageMem img{};
if (!runtime || !readGsImage(rdram, imgAddr, img))
{
setReturnS32(ctx, -1);
return;
}
const uint32_t rowBytes = bytesForPixels(img.psm, static_cast<uint32_t>(img.width));
if (rowBytes == 0)
{
setReturnS32(ctx, -1);
return;
}
uint32_t fbw = img.vram_width ? img.vram_width : std::max<uint32_t>(1, (img.width + 63) / 64);
uint32_t base = static_cast<uint32_t>(img.vram_addr) * 2048u;
uint32_t stride = bytesForPixels(img.psm, fbw * 64u);
if (stride == 0)
{
setReturnS32(ctx, -1);
return;
}
uint8_t *gsvram = runtime->memory().getGSVRAM();
uint8_t *dst = getMemPtr(rdram, dstAddr);
if (!gsvram || !dst)
{
setReturnS32(ctx, -1);
return;
}
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub sceGsExecStoreImage: x=" << img.x
<< " y=" << img.y
<< " w=" << img.width
<< " h=" << img.height
<< " vram=0x" << std::hex << img.vram_addr
<< " fbw=" << std::dec << static_cast<int>(fbw)
<< " psm=" << static_cast<int>(img.psm)
<< " dst=0x" << std::hex << dstAddr << std::dec << std::endl;
++logCount;
}
for (uint32_t row = 0; row < img.height; ++row)
{
uint32_t srcOff = base + (static_cast<uint32_t>(img.y) + row) * stride + bytesForPixels(img.psm, static_cast<uint32_t>(img.x));
uint32_t dstOff = row * rowBytes;
if (srcOff >= PS2_GS_VRAM_SIZE)
break;
uint32_t copyBytes = rowBytes;
if (srcOff + copyBytes > PS2_GS_VRAM_SIZE)
copyBytes = PS2_GS_VRAM_SIZE - srcOff;
std::memcpy(dst + dstOff, gsvram + srcOff, copyBytes);
}
setReturnS32(ctx, 0);
}
void sceGsGetGParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t addr = writeGsGParamToScratch(runtime);
setReturnU32(ctx, addr);
}
void sceGsPutDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t envAddr = getRegU32(ctx, 4);
GsDispEnvMem env{};
if (readGsDispEnv(rdram, envAddr, env))
{
auto &gs = runtime->memory().gs();
gs.display1 = env.display;
gs.dispfb1 = env.dispfb;
}
setReturnS32(ctx, 0);
}
void sceGsPutDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t envAddr = getRegU32(ctx, 4);
uint32_t psm = getRegU32(ctx, 5);
uint32_t w = getRegU32(ctx, 6);
uint32_t h = getRegU32(ctx, 7);
if (w == 0)
w = 640;
if (h == 0)
h = 448;
GsDrawEnvMem env{};
env.offset_x = static_cast<uint16_t>(2048 - (w / 2));
env.offset_y = static_cast<uint16_t>(2048 - (h / 2));
env.clip_x = 0;
env.clip_y = 0;
env.clip_w = static_cast<uint16_t>(w);
env.clip_h = static_cast<uint16_t>(h);
env.vram_addr = 0;
env.fbw = static_cast<uint8_t>((w + 63) / 64);
env.psm = static_cast<uint8_t>(psm);
env.vram_x = 0;
env.vram_y = 0;
env.draw_mask = 0;
env.auto_clear = 1;
env.bg_r = 1;
env.bg_g = 1;
env.bg_b = 1;
env.bg_a = 0x80;
env.bg_q = 0.0f;
uint8_t *ptr = getMemPtr(rdram, envAddr);
if (ptr)
{
std::memcpy(ptr, &env, sizeof(env));
}
setReturnS32(ctx, 0);
}
void sceGsResetGraph(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t mode = getRegU32(ctx, 4);
uint32_t interlace = getRegU32(ctx, 5);
uint32_t omode = getRegU32(ctx, 6);
uint32_t ffmode = getRegU32(ctx, 7);
if (mode == 0)
{
g_gparam.interlace = static_cast<uint8_t>(interlace & 0x1);
g_gparam.omode = static_cast<uint8_t>(omode & 0xFF);
g_gparam.ffmode = static_cast<uint8_t>(ffmode & 0x1);
writeGsGParamToScratch(runtime);
auto &gs = runtime->memory().gs();
gs.pmode = makePmode(1, 0, 0, 0, 0, 0x80);
gs.smode2 = (interlace & 0x1) | ((ffmode & 0x1) << 1);
gs.dispfb1 = makeDispFb(0, 10, 0, 0, 0);
gs.display1 = makeDisplay(0, 0, 0, 0, 639, 447);
}
setReturnS32(ctx, 0);
}
void sceGsResetPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0);
}
void sceGsSetDefClear(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("sceGsSetDefClear", rdram, ctx, runtime);
}
void sceGsSetDefDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0);
}
void sceGsSetDefDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t envAddr = getRegU32(ctx, 4);
uint32_t psm = getRegU32(ctx, 5);
uint32_t w = getRegU32(ctx, 6);
uint32_t h = getRegU32(ctx, 7);
uint32_t dx = readStackU32(rdram, ctx, 16);
uint32_t dy = readStackU32(rdram, ctx, 20);
if (w == 0)
w = 640;
if (h == 0)
h = 448;
uint32_t fbw = (w + 63) / 64;
uint64_t dispfb = makeDispFb(0, fbw, psm, 0, 0);
uint64_t display = makeDisplay(dx, dy, 0, 0, w - 1, h - 1);
writeGsDispEnv(rdram, envAddr, display, dispfb);
setReturnS32(ctx, 0);
}
void sceGsSetDefDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("sceGsSetDefDrawEnv", rdram, ctx, runtime);
}
void sceGsSetDefDrawEnv2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("sceGsSetDefDrawEnv2", rdram, ctx, runtime);
}
void sceGsSetDefLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t imgAddr = getRegU32(ctx, 4);
const GsSetDefImageArgs args = decodeGsSetDefImageArgs(rdram, ctx);
GsImageMem img{};
img.x = static_cast<uint16_t>(args.x);
img.y = static_cast<uint16_t>(args.y);
img.width = static_cast<uint16_t>(args.width);
img.height = static_cast<uint16_t>(args.height);
img.vram_addr = static_cast<uint16_t>(args.vramAddr);
img.vram_width = static_cast<uint8_t>(args.vramWidth);
img.psm = static_cast<uint8_t>(args.psm);
writeGsImage(rdram, imgAddr, img);
setReturnS32(ctx, 0);
}
void sceGsSetDefStoreImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
sceGsSetDefLoadImage(rdram, ctx, runtime);
}
void sceGsSwapDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
// can we get away with that ? kkkk
static int cur = 0;
cur ^= 1;
setReturnS32(ctx, cur);
}
void sceGsSyncPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0);
}
void sceGsSyncV(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0);
}
void sceGsSyncVCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0);
}
void sceGszbufaddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("sceGszbufaddr", rdram, ctx, runtime);
}
@@ -0,0 +1,889 @@
void malloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t size = getRegU32(ctx, 4); // $a0
const uint32_t guestAddr = runtime ? runtime->guestMalloc(size) : 0u;
setReturnU32(ctx, guestAddr);
}
void free(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t guestAddr = getRegU32(ctx, 4); // $a0
if (runtime && guestAddr != 0u)
{
runtime->guestFree(guestAddr);
}
}
void calloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t count = getRegU32(ctx, 4); // $a0
const uint32_t size = getRegU32(ctx, 5); // $a1
const uint32_t guestAddr = runtime ? runtime->guestCalloc(count, size) : 0u;
setReturnU32(ctx, guestAddr);
}
void realloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t oldGuestAddr = getRegU32(ctx, 4); // $a0
const uint32_t newSize = getRegU32(ctx, 5); // $a1
const uint32_t newGuestAddr = runtime ? runtime->guestRealloc(oldGuestAddr, newSize) : 0u;
setReturnU32(ctx, newGuestAddr);
}
void memcpy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t destAddr = getRegU32(ctx, 4); // $a0
uint32_t srcAddr = getRegU32(ctx, 5); // $a1
size_t size = getRegU32(ctx, 6); // $a2
uint8_t *hostDest = getMemPtr(rdram, destAddr);
const uint8_t *hostSrc = getConstMemPtr(rdram, srcAddr);
if (hostDest && hostSrc)
{
::memcpy(hostDest, hostSrc, size);
ps2TraceGuestRangeWrite(rdram, destAddr, static_cast<uint32_t>(size), "memcpy", ctx);
}
else
{
std::cerr << "memcpy error: Attempted copy involving non-RDRAM address (or invalid RDRAM address)."
<< " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")"
<< ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec
<< ", Size: " << size << std::endl;
}
// returns dest pointer ($v0 = $a0)
ctx->r[2] = ctx->r[4];
}
void memset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t destAddr = getRegU32(ctx, 4); // $a0
int value = (int)(getRegU32(ctx, 5) & 0xFF); // $a1 (char value)
uint32_t size = getRegU32(ctx, 6); // $a2
uint8_t *hostDest = getMemPtr(rdram, destAddr);
if (hostDest)
{
::memset(hostDest, value, size);
ps2TraceGuestRangeWrite(rdram, destAddr, size, "memset", ctx);
}
else
{
std::cerr << "memset error: Invalid address provided." << std::endl;
}
// returns dest pointer ($v0 = $a0)
ctx->r[2] = ctx->r[4];
}
void memmove(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t destAddr = getRegU32(ctx, 4); // $a0
uint32_t srcAddr = getRegU32(ctx, 5); // $a1
size_t size = getRegU32(ctx, 6); // $a2
uint8_t *hostDest = getMemPtr(rdram, destAddr);
const uint8_t *hostSrc = getConstMemPtr(rdram, srcAddr);
if (hostDest && hostSrc)
{
::memmove(hostDest, hostSrc, size);
ps2TraceGuestRangeWrite(rdram, destAddr, static_cast<uint32_t>(size), "memmove", ctx);
}
else
{
std::cerr << "memmove error: Attempted move involving potentially invalid RDRAM address."
<< " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")"
<< ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec
<< ", Size: " << size << std::endl;
}
// returns dest pointer ($v0 = $a0)
ctx->r[2] = ctx->r[4];
}
void memcmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t ptr1Addr = getRegU32(ctx, 4); // $a0
uint32_t ptr2Addr = getRegU32(ctx, 5); // $a1
uint32_t size = getRegU32(ctx, 6); // $a2
const uint8_t *hostPtr1 = getConstMemPtr(rdram, ptr1Addr);
const uint8_t *hostPtr2 = getConstMemPtr(rdram, ptr2Addr);
int result = 0;
if (hostPtr1 && hostPtr2)
{
result = ::memcmp(hostPtr1, hostPtr2, size);
}
else
{
std::cerr << "memcmp error: Invalid address provided."
<< " Ptr1: 0x" << std::hex << ptr1Addr << " (host ptr valid: " << (hostPtr1 != nullptr) << ")"
<< ", Ptr2: 0x" << ptr2Addr << " (host ptr valid: " << (hostPtr2 != nullptr) << ")" << std::dec
<< std::endl;
result = (hostPtr1 == nullptr) - (hostPtr2 == nullptr);
if (result == 0)
result = 1; // If both null, still different? Or 0?
}
setReturnS32(ctx, result);
}
void strcpy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t destAddr = getRegU32(ctx, 4); // $a0
uint32_t srcAddr = getRegU32(ctx, 5); // $a1
char *hostDest = reinterpret_cast<char *>(getMemPtr(rdram, destAddr));
const char *hostSrc = reinterpret_cast<const char *>(getConstMemPtr(rdram, srcAddr));
if (hostDest && hostSrc)
{
::strcpy(hostDest, hostSrc);
ps2TraceGuestRangeWrite(rdram, destAddr, static_cast<uint32_t>(::strlen(hostSrc) + 1u), "strcpy", ctx);
}
else
{
std::cerr << "strcpy error: Invalid address provided."
<< " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")"
<< ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec
<< std::endl;
}
// returns dest pointer ($v0 = $a0)
ctx->r[2] = ctx->r[4];
}
void strncpy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t destAddr = getRegU32(ctx, 4); // $a0
uint32_t srcAddr = getRegU32(ctx, 5); // $a1
uint32_t size = getRegU32(ctx, 6); // $a2
char *hostDest = reinterpret_cast<char *>(getMemPtr(rdram, destAddr));
const char *hostSrc = reinterpret_cast<const char *>(getConstMemPtr(rdram, srcAddr));
if (hostDest && hostSrc)
{
::strncpy(hostDest, hostSrc, size);
ps2TraceGuestRangeWrite(rdram, destAddr, size, "strncpy", ctx);
}
else
{
std::cerr << "strncpy error: Invalid address provided."
<< " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")"
<< ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec
<< std::endl;
}
// returns dest pointer ($v0 = $a0)
ctx->r[2] = ctx->r[4];
}
void strlen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t strAddr = getRegU32(ctx, 4); // $a0
const char *hostStr = reinterpret_cast<const char *>(getConstMemPtr(rdram, strAddr));
size_t len = 0;
if (hostStr)
{
len = ::strlen(hostStr);
}
else
{
std::cerr << "strlen error: Invalid address provided: 0x" << std::hex << strAddr << std::dec << std::endl;
}
setReturnU32(ctx, (uint32_t)len);
}
void strcmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t str1Addr = getRegU32(ctx, 4); // $a0
uint32_t str2Addr = getRegU32(ctx, 5); // $a1
const char *hostStr1 = reinterpret_cast<const char *>(getConstMemPtr(rdram, str1Addr));
const char *hostStr2 = reinterpret_cast<const char *>(getConstMemPtr(rdram, str2Addr));
int result = 0;
if (hostStr1 && hostStr2)
{
result = ::strcmp(hostStr1, hostStr2);
}
else
{
std::cerr << "strcmp error: Invalid address provided."
<< " Str1: 0x" << std::hex << str1Addr << " (host ptr valid: " << (hostStr1 != nullptr) << ")"
<< ", Str2: 0x" << str2Addr << " (host ptr valid: " << (hostStr2 != nullptr) << ")" << std::dec
<< std::endl;
// Return non-zero on error, consistent with memcmp error handling
result = (hostStr1 == nullptr) - (hostStr2 == nullptr);
if (result == 0 && hostStr1 == nullptr)
result = 1; // Both null -> treat as different? Or 0? Let's say different.
}
setReturnS32(ctx, result);
}
void strncmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t str1Addr = getRegU32(ctx, 4); // $a0
uint32_t str2Addr = getRegU32(ctx, 5); // $a1
uint32_t size = getRegU32(ctx, 6); // $a2
const char *hostStr1 = reinterpret_cast<const char *>(getConstMemPtr(rdram, str1Addr));
const char *hostStr2 = reinterpret_cast<const char *>(getConstMemPtr(rdram, str2Addr));
int result = 0;
if (hostStr1 && hostStr2)
{
result = ::strncmp(hostStr1, hostStr2, size);
}
else
{
std::cerr << "strncmp error: Invalid address provided."
<< " Str1: 0x" << std::hex << str1Addr << " (host ptr valid: " << (hostStr1 != nullptr) << ")"
<< ", Str2: 0x" << str2Addr << " (host ptr valid: " << (hostStr2 != nullptr) << ")" << std::dec
<< std::endl;
result = (hostStr1 == nullptr) - (hostStr2 == nullptr);
if (result == 0 && hostStr1 == nullptr)
result = 1; // Both null -> different
}
setReturnS32(ctx, result);
}
void strcat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t destAddr = getRegU32(ctx, 4); // $a0
uint32_t srcAddr = getRegU32(ctx, 5); // $a1
char *hostDest = reinterpret_cast<char *>(getMemPtr(rdram, destAddr));
const char *hostSrc = reinterpret_cast<const char *>(getConstMemPtr(rdram, srcAddr));
if (hostDest && hostSrc)
{
::strcat(hostDest, hostSrc);
}
else
{
std::cerr << "strcat error: Invalid address provided."
<< " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")"
<< ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec
<< std::endl;
}
// returns dest pointer ($v0 = $a0)
ctx->r[2] = ctx->r[4];
}
void strncat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t destAddr = getRegU32(ctx, 4); // $a0
uint32_t srcAddr = getRegU32(ctx, 5); // $a1
uint32_t size = getRegU32(ctx, 6); // $a2
char *hostDest = reinterpret_cast<char *>(getMemPtr(rdram, destAddr));
const char *hostSrc = reinterpret_cast<const char *>(getConstMemPtr(rdram, srcAddr));
if (hostDest && hostSrc)
{
::strncat(hostDest, hostSrc, size);
}
else
{
std::cerr << "strncat error: Invalid address provided."
<< " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")"
<< ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec
<< std::endl;
}
// returns dest pointer ($v0 = $a0)
ctx->r[2] = ctx->r[4];
}
void strchr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t strAddr = getRegU32(ctx, 4); // $a0
int char_code = (int)(getRegU32(ctx, 5) & 0xFF); // $a1 (char value)
const char *hostStr = reinterpret_cast<const char *>(getConstMemPtr(rdram, strAddr));
char *foundPtr = nullptr;
uint32_t resultAddr = 0;
if (hostStr)
{
foundPtr = ::strchr(const_cast<char *>(hostStr), char_code);
if (foundPtr)
{
resultAddr = hostPtrToPs2Addr(rdram, foundPtr);
}
}
else
{
std::cerr << "strchr error: Invalid address provided: 0x" << std::hex << strAddr << std::dec << std::endl;
}
// returns PS2 address or 0 (NULL)
setReturnU32(ctx, resultAddr);
}
void strrchr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t strAddr = getRegU32(ctx, 4); // $a0
int char_code = (int)(getRegU32(ctx, 5) & 0xFF); // $a1 (char value)
const char *hostStr = reinterpret_cast<const char *>(getConstMemPtr(rdram, strAddr));
char *foundPtr = nullptr;
uint32_t resultAddr = 0;
if (hostStr)
{
foundPtr = ::strrchr(const_cast<char *>(hostStr), char_code); // Use const_cast carefully
if (foundPtr)
{
resultAddr = hostPtrToPs2Addr(rdram, foundPtr);
}
}
else
{
std::cerr << "strrchr error: Invalid address provided: 0x" << std::hex << strAddr << std::dec << std::endl;
}
// returns PS2 address or 0 (NULL)
setReturnU32(ctx, resultAddr);
}
void strstr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t haystackAddr = getRegU32(ctx, 4); // $a0
uint32_t needleAddr = getRegU32(ctx, 5); // $a1
const char *hostHaystack = reinterpret_cast<const char *>(getConstMemPtr(rdram, haystackAddr));
const char *hostNeedle = reinterpret_cast<const char *>(getConstMemPtr(rdram, needleAddr));
char *foundPtr = nullptr;
uint32_t resultAddr = 0;
if (hostHaystack && hostNeedle)
{
foundPtr = ::strstr(const_cast<char *>(hostHaystack), hostNeedle);
if (foundPtr)
{
resultAddr = hostPtrToPs2Addr(rdram, foundPtr);
}
}
else
{
std::cerr << "strstr error: Invalid address provided."
<< " Haystack: 0x" << std::hex << haystackAddr << " (host ptr valid: " << (hostHaystack != nullptr) << ")"
<< ", Needle: 0x" << needleAddr << " (host ptr valid: " << (hostNeedle != nullptr) << ")" << std::dec
<< std::endl;
}
// returns PS2 address or 0 (NULL)
setReturnU32(ctx, resultAddr);
}
void printf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t format_addr = getRegU32(ctx, 4); // $a0
const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024);
int ret = -1;
if (format_addr != 0)
{
std::string rendered = formatPs2StringWithArgs(rdram, ctx, runtime, formatOwned.c_str(), 1);
if (rendered.size() > 2048)
{
rendered.resize(2048);
}
const std::string logLine = sanitizeForLog(rendered);
uint32_t count = 0;
{
std::lock_guard<std::mutex> lock(g_printfLogMutex);
count = ++g_printfLogCount;
}
if (count <= kMaxPrintfLogs)
{
std::cout << "PS2 printf: " << logLine;
std::cout << std::flush;
}
else if (count == kMaxPrintfLogs + 1)
{
std::cerr << "PS2 printf logging suppressed after " << kMaxPrintfLogs << " lines" << std::endl;
}
ret = static_cast<int>(rendered.size());
}
else
{
std::cerr << "printf error: Invalid format string address provided: 0x" << std::hex << format_addr << std::dec << std::endl;
}
// returns the number of characters written, or negative on error.
setReturnS32(ctx, ret);
}
void sprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t str_addr = getRegU32(ctx, 4); // $a0
uint32_t format_addr = getRegU32(ctx, 5); // $a1
const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024);
int ret = -1;
if (format_addr != 0)
{
const uint32_t watchBase = ps2PathWatchPhysAddr();
const uint32_t watchEnd = watchBase + PS2_PATH_WATCH_BYTES;
const uint32_t dest = str_addr & PS2_RAM_MASK;
const bool touchesWatch = dest < watchEnd && dest >= watchBase;
static uint32_t watchSprintfLogCount = 0;
if (touchesWatch && watchSprintfLogCount < 64u)
{
const uint32_t arg0 = getRegU32(ctx, 6);
const uint32_t arg1 = getRegU32(ctx, 7);
std::cout << "[watch:sprintf] dest=0x" << std::hex << str_addr
<< " fmt@0x" << format_addr
<< " arg0=0x" << arg0
<< " arg1=0x" << arg1
<< " fmt=\"" << sanitizeForLog(readPs2CStringBounded(rdram, runtime, format_addr, 64)) << "\""
<< " s0=\"" << sanitizeForLog(readPs2CStringBounded(rdram, runtime, arg0, 64)) << "\""
<< " s1=\"" << sanitizeForLog(readPs2CStringBounded(rdram, runtime, arg1, 64)) << "\""
<< std::dec << std::endl;
++watchSprintfLogCount;
}
std::string rendered = formatPs2StringWithArgs(rdram, ctx, runtime, formatOwned.c_str(), 2);
if (rendered.size() >= kMaxFormattedOutputBytes)
{
rendered.resize(kMaxFormattedOutputBytes - 1);
}
const size_t writeLen = rendered.size() + 1u;
if (writeGuestBytes(rdram, runtime, str_addr, reinterpret_cast<const uint8_t *>(rendered.c_str()), writeLen))
{
ps2TraceGuestRangeWrite(rdram, str_addr, static_cast<uint32_t>(writeLen), "sprintf", ctx);
ret = static_cast<int>(rendered.size());
}
else
{
std::cerr << "sprintf error: Failed to write destination buffer at 0x"
<< std::hex << str_addr << std::dec << std::endl;
}
}
else
{
std::cerr << "sprintf error: Invalid format address provided."
<< " Dest: 0x" << std::hex << str_addr
<< ", Format: 0x" << format_addr << std::dec
<< std::endl;
}
// returns the number of characters written (excluding null), or negative on error.
setReturnS32(ctx, ret);
}
void snprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t str_addr = getRegU32(ctx, 4); // $a0
size_t size = getRegU32(ctx, 5); // $a1
uint32_t format_addr = getRegU32(ctx, 6); // $a2
const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024);
int ret = -1;
if (format_addr != 0)
{
std::string rendered = formatPs2StringWithArgs(rdram, ctx, runtime, formatOwned.c_str(), 3);
ret = static_cast<int>(rendered.size());
if (size > 0)
{
const size_t copyLen = std::min<size_t>(size - 1, rendered.size());
std::vector<uint8_t> output(copyLen + 1u, 0u);
if (copyLen > 0u)
{
std::memcpy(output.data(), rendered.data(), copyLen);
}
if (writeGuestBytes(rdram, runtime, str_addr, output.data(), output.size()))
{
ps2TraceGuestRangeWrite(rdram, str_addr, static_cast<uint32_t>(output.size()), "snprintf", ctx);
}
else
{
std::cerr << "snprintf error: Failed to write destination buffer at 0x"
<< std::hex << str_addr << std::dec << std::endl;
ret = -1;
}
}
}
else
{
std::cerr << "snprintf error: Invalid address provided or size is zero."
<< " Dest: 0x" << std::hex << str_addr
<< ", Format: 0x" << format_addr << std::dec
<< ", Size: " << size << std::endl;
}
// returns the number of characters that *would* have been written
// if size was large enough (excluding null), or negative on error.
setReturnS32(ctx, ret);
}
void puts(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t strAddr = getRegU32(ctx, 4); // $a0
const char *hostStr = reinterpret_cast<const char *>(getConstMemPtr(rdram, strAddr));
int result = EOF;
if (hostStr)
{
result = std::puts(hostStr); // std::puts adds a newline
std::fflush(stdout); // Ensure output appears
}
else
{
std::cerr << "puts error: Invalid address provided: 0x" << std::hex << strAddr << std::dec << std::endl;
}
// returns non-negative on success, EOF on error.
setReturnS32(ctx, result >= 0 ? 0 : -1); // PS2 might expect 0/-1 rather than EOF
}
void fopen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t pathAddr = getRegU32(ctx, 4); // $a0
uint32_t modeAddr = getRegU32(ctx, 5); // $a1
const char *hostPath = reinterpret_cast<const char *>(getConstMemPtr(rdram, pathAddr));
const char *hostMode = reinterpret_cast<const char *>(getConstMemPtr(rdram, modeAddr));
uint32_t file_handle = 0;
if (hostPath && hostMode)
{
// TODO: Add translation for PS2 paths like mc0:, host:, cdrom:, etc.
// treating as direct host path
std::cout << "ps2_stub fopen: path='" << hostPath << "', mode='" << hostMode << "'" << std::endl;
FILE *fp = ::fopen(hostPath, hostMode);
if (fp)
{
std::lock_guard<std::mutex> lock(g_file_mutex);
file_handle = generate_file_handle();
g_file_map[file_handle] = fp;
std::cout << " -> handle=0x" << std::hex << file_handle << std::dec << std::endl;
}
else
{
std::cerr << "ps2_stub fopen error: Failed to open '" << hostPath << "' with mode '" << hostMode << "'. Error: " << strerror(errno) << std::endl;
}
}
else
{
std::cerr << "fopen error: Invalid address provided for path or mode."
<< " Path: 0x" << std::hex << pathAddr << " (host ptr valid: " << (hostPath != nullptr) << ")"
<< ", Mode: 0x" << modeAddr << " (host ptr valid: " << (hostMode != nullptr) << ")" << std::dec
<< std::endl;
}
// returns a file handle (non-zero) on success, or NULL (0) on error.
setReturnU32(ctx, file_handle);
}
void fclose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t file_handle = getRegU32(ctx, 4); // $a0
int ret = EOF; // Default to error
if (file_handle != 0)
{
std::lock_guard<std::mutex> lock(g_file_mutex);
auto it = g_file_map.find(file_handle);
if (it != g_file_map.end())
{
FILE *fp = it->second;
ret = ::fclose(fp);
g_file_map.erase(it);
}
else
{
std::cerr << "ps2_stub fclose error: Invalid file handle 0x" << std::hex << file_handle << std::dec << std::endl;
}
}
else
{
// Closing NULL handle in Standard C defines this as no-op
ret = 0;
}
// returns 0 on success, EOF on error.
setReturnS32(ctx, ret);
}
void fread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t ptrAddr = getRegU32(ctx, 4); // $a0 (buffer)
uint32_t size = getRegU32(ctx, 5); // $a1 (element size)
uint32_t count = getRegU32(ctx, 6); // $a2 (number of elements)
uint32_t file_handle = getRegU32(ctx, 7); // $a3 (file handle)
size_t items_read = 0;
uint8_t *hostPtr = getMemPtr(rdram, ptrAddr);
FILE *fp = get_file_ptr(file_handle);
if (hostPtr && fp && size > 0 && count > 0)
{
items_read = ::fread(hostPtr, size, count, fp);
}
else
{
std::cerr << "fread error: Invalid arguments."
<< " Ptr: 0x" << std::hex << ptrAddr << " (host ptr valid: " << (hostPtr != nullptr) << ")"
<< ", Handle: 0x" << file_handle << " (file valid: " << (fp != nullptr) << ")" << std::dec
<< ", Size: " << size << ", Count: " << count << std::endl;
}
// returns the number of items successfully read.
setReturnU32(ctx, (uint32_t)items_read);
}
void fwrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t ptrAddr = getRegU32(ctx, 4); // $a0 (buffer)
uint32_t size = getRegU32(ctx, 5); // $a1 (element size)
uint32_t count = getRegU32(ctx, 6); // $a2 (number of elements)
uint32_t file_handle = getRegU32(ctx, 7); // $a3 (file handle)
size_t items_written = 0;
const uint8_t *hostPtr = getConstMemPtr(rdram, ptrAddr);
FILE *fp = get_file_ptr(file_handle);
if (hostPtr && fp && size > 0 && count > 0)
{
items_written = ::fwrite(hostPtr, size, count, fp);
}
else
{
std::cerr << "fwrite error: Invalid arguments."
<< " Ptr: 0x" << std::hex << ptrAddr << " (host ptr valid: " << (hostPtr != nullptr) << ")"
<< ", Handle: 0x" << file_handle << " (file valid: " << (fp != nullptr) << ")" << std::dec
<< ", Size: " << size << ", Count: " << count << std::endl;
}
// returns the number of items successfully written.
setReturnU32(ctx, (uint32_t)items_written);
}
void fprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t file_handle = getRegU32(ctx, 4); // $a0
uint32_t format_addr = getRegU32(ctx, 5); // $a1
FILE *fp = get_file_ptr(file_handle);
const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024);
int ret = -1;
if (fp && format_addr != 0)
{
std::string rendered = formatPs2StringWithArgs(rdram, ctx, runtime, formatOwned.c_str(), 2);
ret = std::fprintf(fp, "%s", rendered.c_str());
}
else
{
std::cerr << "fprintf error: Invalid file handle or format address."
<< " Handle: 0x" << std::hex << file_handle << " (file valid: " << (fp != nullptr) << ")"
<< ", Format: 0x" << format_addr << std::dec
<< std::endl;
}
// returns the number of characters written, or negative on error.
setReturnS32(ctx, ret);
}
void fseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t file_handle = getRegU32(ctx, 4); // $a0
long offset = (long)getRegU32(ctx, 5); // $a1 (Note: might need 64-bit for large files?)
int whence = (int)getRegU32(ctx, 6); // $a2 (SEEK_SET, SEEK_CUR, SEEK_END)
int ret = -1; // Default error
FILE *fp = get_file_ptr(file_handle);
if (fp)
{
// Ensure whence is valid (0, 1, 2)
if (whence >= 0 && whence <= 2)
{
ret = ::fseek(fp, offset, whence);
}
else
{
std::cerr << "fseek error: Invalid whence value: " << whence << std::endl;
}
}
else
{
std::cerr << "fseek error: Invalid file handle 0x" << std::hex << file_handle << std::dec << std::endl;
}
// returns 0 on success, non-zero on error.
setReturnS32(ctx, ret);
}
void ftell(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t file_handle = getRegU32(ctx, 4); // $a0
long ret = -1L;
FILE *fp = get_file_ptr(file_handle);
if (fp)
{
ret = ::ftell(fp);
}
else
{
std::cerr << "ftell error: Invalid file handle 0x" << std::hex << file_handle << std::dec << std::endl;
}
// returns the current position, or -1L on error.
if (ret > 0xFFFFFFFFL || ret < 0)
{
setReturnS32(ctx, -1);
}
else
{
setReturnU32(ctx, (uint32_t)ret);
}
}
void fflush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t file_handle = getRegU32(ctx, 4); // $a0
int ret = EOF; // Default error
// If handle is 0 fflush flushes *all* output streams.
if (file_handle == 0)
{
ret = ::fflush(NULL);
}
else
{
FILE *fp = get_file_ptr(file_handle);
if (fp)
{
ret = ::fflush(fp);
}
else
{
std::cerr << "fflush error: Invalid file handle 0x" << std::hex << file_handle << std::dec << std::endl;
}
}
// returns 0 on success, EOF on error.
setReturnS32(ctx, ret);
}
void sqrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
float arg = ctx->f[12];
ctx->f[0] = ::sqrtf(arg);
}
void sin(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
float arg = ctx->f[12];
ctx->f[0] = ::sinf(arg);
}
void __kernel_sinf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const float x = ctx->f[12];
const float y = ctx->f[13];
const int32_t iy = static_cast<int32_t>(getRegU32(ctx, 4));
ctx->f[0] = ::sinf(x + (iy != 0 ? y : 0.0f));
}
void cos(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
float arg = ctx->f[12];
ctx->f[0] = ::cosf(arg);
}
void __kernel_cosf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const float x = ctx->f[12];
const float y = ctx->f[13];
ctx->f[0] = ::cosf(x + y);
}
void __ieee754_rem_pio2f(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const float x = ctx->f[12];
constexpr float kPi = 3.14159265358979323846f;
constexpr float kHalfPi = kPi * 0.5f;
constexpr float kInvHalfPi = 2.0f / kPi;
const int32_t n = static_cast<int32_t>(std::nearbyintf(x * kInvHalfPi));
const float y0 = x - (static_cast<float>(n) * kHalfPi);
const float y1 = 0.0f;
const uint32_t yOutAddr = getRegU32(ctx, 4);
if (float *yOut0 = reinterpret_cast<float *>(getMemPtr(rdram, yOutAddr)); yOut0)
{
*yOut0 = y0;
}
if (float *yOut1 = reinterpret_cast<float *>(getMemPtr(rdram, yOutAddr + 4)); yOut1)
{
*yOut1 = y1;
}
setReturnS32(ctx, n);
}
void tan(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
float arg = ctx->f[12];
ctx->f[0] = ::tanf(arg);
}
void atan2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
float y = ctx->f[12];
float x = ctx->f[14];
ctx->f[0] = ::atan2f(y, x);
}
void pow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
float base = ctx->f[12];
float exp = ctx->f[14];
ctx->f[0] = ::powf(base, exp);
}
void exp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
float arg = ctx->f[12];
ctx->f[0] = ::expf(arg);
}
void log(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
float arg = ctx->f[12];
ctx->f[0] = ::logf(arg);
}
void log10(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
float arg = ctx->f[12];
ctx->f[0] = ::log10f(arg);
}
void ceil(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
float arg = ctx->f[12];
ctx->f[0] = ::ceilf(arg);
}
void floor(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
float arg = ctx->f[12];
ctx->f[0] = ::floorf(arg);
}
void fabs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
float arg = ctx->f[12];
ctx->f[0] = ::fabsf(arg);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,60 @@
void sceCdRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t lbn = getRegU32(ctx, 4); // $a0 - logical block number
uint32_t sectors = getRegU32(ctx, 5); // $a1 - sector count
uint32_t buf = getRegU32(ctx, 6); // $a2 - destination buffer in RDRAM
uint32_t offset = buf & PS2_RAM_MASK;
size_t bytes = static_cast<size_t>(sectors) * kCdSectorSize;
if (bytes > 0)
{
const size_t maxBytes = PS2_RAM_SIZE - offset;
if (bytes > maxBytes)
{
bytes = maxBytes;
}
}
uint8_t *dst = rdram + offset;
bool ok = true;
if (bytes > 0)
{
ok = readCdSectors(lbn, sectors, dst, bytes);
if (!ok)
{
std::memset(dst, 0, bytes);
}
}
if (ok)
{
g_cdStreamingLbn = lbn + sectors;
setReturnS32(ctx, 1); // command accepted/success
}
else
{
setReturnS32(ctx, 0);
}
}
void sceCdSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0); // 0 = completed/not busy
}
void sceCdGetError(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, g_lastCdError);
}
void builtin_set_imask(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub builtin_set_imask" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
@@ -0,0 +1,545 @@
void syRtcInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub syRtcInit" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void syFree(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub syFree" << std::endl;
++logCount;
}
const uint32_t guestAddr = getRegU32(ctx, 4); // $a0
if (runtime && guestAddr != 0u)
{
runtime->guestFree(guestAddr);
}
setReturnS32(ctx, 0);
}
void syMalloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t requestedSize = getRegU32(ctx, 4); // $a0
uint32_t resultAddr = 0u;
if (runtime && requestedSize != 0u)
{
// Match game expectation for allocator alignment while keeping pointers in EE RAM.
resultAddr = runtime->guestMalloc(requestedSize, 64u);
}
static int logCount = 0;
if (logCount < 16)
{
std::cout << "ps2_stub syMalloc"
<< " size=0x" << std::hex << requestedSize
<< " -> 0x" << resultAddr
<< std::dec << std::endl;
++logCount;
}
setReturnU32(ctx, resultAddr);
}
void InitSdcParameter(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub InitSdcParameter" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void Ps2_pad_actuater(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub Ps2_pad_actuater" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void syMallocInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (runtime)
{
const uint32_t heapBase = getRegU32(ctx, 4); // $a0
const uint32_t heapSize = getRegU32(ctx, 5); // $a1 (optional size)
constexpr uint32_t kHeapBaseFloor = 0x00100000u;
uint32_t normalizedBase = heapBase;
if (normalizedBase >= 0x80000000u && normalizedBase < 0xC0000000u)
{
normalizedBase &= 0x1FFFFFFFu;
}
else if (normalizedBase >= PS2_RAM_SIZE)
{
normalizedBase &= PS2_RAM_MASK;
}
const bool suspiciousKsegBase = (heapBase & 0xE0000000u) == 0x80000000u && normalizedBase < kHeapBaseFloor;
if (normalizedBase == 0u || suspiciousKsegBase)
{
// Keep the ELF-driven suggestion instead of collapsing heap to low memory.
normalizedBase = runtime->guestHeapBase();
}
// Treat absurd "size" values as unspecified limit.
uint32_t heapLimit = 0u;
if (heapSize != 0u && heapSize <= PS2_RAM_SIZE && normalizedBase < PS2_RAM_SIZE)
{
const uint64_t candidateLimit = static_cast<uint64_t>(normalizedBase) + static_cast<uint64_t>(heapSize);
heapLimit = static_cast<uint32_t>(std::min<uint64_t>(candidateLimit, PS2_RAM_SIZE));
}
runtime->configureGuestHeap(normalizedBase, heapLimit);
if (logCount < 8)
{
std::cout << "ps2_stub syMallocInit"
<< " reqBase=0x" << std::hex << heapBase
<< " reqSize=0x" << heapSize
<< " normBase=0x" << normalizedBase
<< " reqLimit=0x" << heapLimit
<< " finalBase=0x" << runtime->guestHeapBase()
<< " finalEnd=0x" << runtime->guestHeapEnd()
<< std::dec << std::endl;
++logCount;
}
}
else if (logCount < 8)
{
std::cout << "ps2_stub syMallocInit" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void syHwInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub syHwInit" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void syHwInit2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub syHwInit2" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void InitGdSystemEx(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub InitGdSystemEx" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void pdInitPeripheral(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub pdInitPeripheral" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void pdGetPeripheral(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub pdGetPeripheral" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void Ps2SwapDBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub Ps2SwapDBuff" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void InitReadKeyEx(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub InitReadKeyEx" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void SetRepeatKeyTimer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub SetRepeatKeyTimer" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void StopFxProgram(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub StopFxProgram" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void sndr_trans_func(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub sndr_trans_func (noop)" << std::endl;
++logCount;
}
// For now just clear the snd busy flag used by sdMultiUnitDownload/SysServer loops.
constexpr uint32_t kSndBusyAddr = 0x01E0E170;
if (rdram)
{
uint32_t offset = kSndBusyAddr & PS2_RAM_MASK;
if (offset + sizeof(uint32_t) <= PS2_RAM_SIZE)
{
*reinterpret_cast<uint32_t *>(rdram + offset) = 0;
}
}
setReturnS32(ctx, 0);
}
void sdDrvInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub sdDrvInit (noop)" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void ADXF_LoadPartitionNw(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub ADXF_LoadPartitionNw (noop)" << std::endl;
++logCount;
}
// Return success to keep the ADX partition setup moving.
setReturnS32(ctx, 0);
}
void sdSndStopAll(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub sdSndStopAll" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void sdSysFinish(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub sdSysFinish" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void ADXT_Init(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub ADXT_Init" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void ADXT_SetNumRetry(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub ADXT_SetNumRetry" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void cvFsSetDefDev(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
if (logCount < 8)
{
std::cout << "ps2_stub cvFsSetDefDev" << std::endl;
++logCount;
}
setReturnS32(ctx, 0);
}
void mcCallMessageTypeSe(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCallMessageTypeSe", rdram, ctx, runtime);
}
void mcCheckReadStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCheckReadStartConfigFile", rdram, ctx, runtime);
}
void mcCheckReadStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCheckReadStartSaveFile", rdram, ctx, runtime);
}
void mcCheckWriteStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCheckWriteStartConfigFile", rdram, ctx, runtime);
}
void mcCheckWriteStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCheckWriteStartSaveFile", rdram, ctx, runtime);
}
void mcCreateConfigInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCreateConfigInit", rdram, ctx, runtime);
}
void mcCreateFileSelectWindow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCreateFileSelectWindow", rdram, ctx, runtime);
}
void mcCreateIconInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCreateIconInit", rdram, ctx, runtime);
}
void mcCreateSaveFileInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCreateSaveFileInit", rdram, ctx, runtime);
}
void mcDispFileName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDispFileName", rdram, ctx, runtime);
}
void mcDispFileNumber(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDispFileNumber", rdram, ctx, runtime);
}
void mcDisplayFileSelectWindow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDisplayFileSelectWindow", rdram, ctx, runtime);
}
void mcDisplaySelectFileInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDisplaySelectFileInfo", rdram, ctx, runtime);
}
void mcDisplaySelectFileInfoMesCount(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDisplaySelectFileInfoMesCount", rdram, ctx, runtime);
}
void mcDispWindowCurSol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDispWindowCurSol", rdram, ctx, runtime);
}
void mcDispWindowFoundtion(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDispWindowFoundtion", rdram, ctx, runtime);
}
void mceGetInfoApdx(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mceGetInfoApdx", rdram, ctx, runtime);
}
void mceIntrReadFixAlign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mceIntrReadFixAlign", rdram, ctx, runtime);
}
void mceStorePwd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mceStorePwd", rdram, ctx, runtime);
}
void mcGetConfigCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetConfigCapacitySize", rdram, ctx, runtime);
}
void mcGetFileSelectWindowCursol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetFileSelectWindowCursol", rdram, ctx, runtime);
}
void mcGetFreeCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetFreeCapacitySize", rdram, ctx, runtime);
}
void mcGetIconCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetIconCapacitySize", rdram, ctx, runtime);
}
void mcGetIconFileCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetIconFileCapacitySize", rdram, ctx, runtime);
}
void mcGetPortSelectDirInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetPortSelectDirInfo", rdram, ctx, runtime);
}
void mcGetSaveFileCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetSaveFileCapacitySize", rdram, ctx, runtime);
}
void mcGetStringEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetStringEnd", rdram, ctx, runtime);
}
void mcMoveFileSelectWindowCursor(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcMoveFileSelectWindowCursor", rdram, ctx, runtime);
}
void mcNewCreateConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcNewCreateConfigFile", rdram, ctx, runtime);
}
void mcNewCreateIcon(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcNewCreateIcon", rdram, ctx, runtime);
}
void mcNewCreateSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcNewCreateSaveFile", rdram, ctx, runtime);
}
void mcReadIconData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcReadIconData", rdram, ctx, runtime);
}
void mcReadStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcReadStartConfigFile", rdram, ctx, runtime);
}
void mcReadStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcReadStartSaveFile", rdram, ctx, runtime);
}
void mcSelectFileInfoInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcSelectFileInfoInit", rdram, ctx, runtime);
}
void mcSelectSaveFileCheck(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcSelectSaveFileCheck", rdram, ctx, runtime);
}
void mcSetFileSelectWindowCursol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcSetFileSelectWindowCursol", rdram, ctx, runtime);
}
void mcSetFileSelectWindowCursolInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcSetFileSelectWindowCursolInit", rdram, ctx, runtime);
}
void mcSetStringSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcSetStringSaveFile", rdram, ctx, runtime);
}
void mcSetTyepWriteMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcSetTyepWriteMode", rdram, ctx, runtime);
}
void mcWriteIconData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcWriteIconData", rdram, ctx, runtime);
}
void mcWriteStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcWriteStartConfigFile", rdram, ctx, runtime);
}
void mcWriteStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcWriteStartSaveFile", rdram, ctx, runtime);
}
@@ -0,0 +1,422 @@
namespace
{
std::string readGuestCStringBounded(const uint8_t *rdram, uint32_t guestAddr, size_t maxBytes)
{
std::string out;
if (!rdram || guestAddr == 0 || maxBytes == 0)
{
return out;
}
out.reserve(maxBytes);
for (size_t i = 0; i < maxBytes; ++i)
{
const char ch = static_cast<char>(rdram[(guestAddr + static_cast<uint32_t>(i)) & PS2_RAM_MASK]);
if (ch == '\0')
{
break;
}
out.push_back(ch);
}
return out;
}
std::string normalizeSifModulePathKey(const std::string &path)
{
return toLowerAscii(normalizePs2PathSuffix(path));
}
uint64_t hashGuestBytesFnv1a64(const uint8_t *rdram, uint32_t guestAddr, size_t byteCount)
{
constexpr uint64_t kOffset = 1469598103934665603ull;
constexpr uint64_t kPrime = 1099511628211ull;
if (!rdram || guestAddr == 0 || byteCount == 0)
{
return 0ull;
}
uint64_t hash = kOffset;
for (size_t i = 0; i < byteCount; ++i)
{
const uint8_t b = rdram[(guestAddr + static_cast<uint32_t>(i)) & PS2_RAM_MASK];
hash ^= static_cast<uint64_t>(b);
hash *= kPrime;
}
return hash;
}
std::string makeSifModuleBufferTag(const uint8_t *rdram, uint32_t bufferAddr)
{
char key[96] = {};
const uint64_t hash = hashGuestBytesFnv1a64(rdram, bufferAddr, kSifModuleBufferProbeBytes);
std::snprintf(key, sizeof(key), "iopbuf:fnv64:%016llx", static_cast<unsigned long long>(hash));
return std::string(key);
}
void logSifModuleAction(const char *op, int32_t moduleId, const std::string &path, uint32_t refCount)
{
if (!op)
{
return;
}
std::lock_guard<std::mutex> lock(g_sif_module_mutex);
if (g_sif_module_log_count >= kMaxSifModuleLogs)
{
return;
}
std::cout << "[SIF module] " << op
<< " id=" << moduleId
<< " ref=" << refCount
<< " path=\"" << path << "\""
<< std::endl;
++g_sif_module_log_count;
}
int32_t trackSifModuleLoad(const std::string &path)
{
if (path.empty())
{
return -1;
}
const std::string pathKey = normalizeSifModulePathKey(path);
if (pathKey.empty())
{
return -1;
}
std::lock_guard<std::mutex> lock(g_sif_module_mutex);
auto byPathIt = g_sif_module_id_by_path.find(pathKey);
if (byPathIt != g_sif_module_id_by_path.end())
{
auto byIdIt = g_sif_modules_by_id.find(byPathIt->second);
if (byIdIt != g_sif_modules_by_id.end())
{
SifModuleRecord &record = byIdIt->second;
record.loaded = true;
++record.refCount;
return record.id;
}
}
if (g_next_sif_module_id <= 0)
{
g_next_sif_module_id = 1;
}
const int32_t moduleId = g_next_sif_module_id++;
SifModuleRecord record;
record.id = moduleId;
record.path = path;
record.pathKey = pathKey;
record.refCount = 1;
record.loaded = true;
g_sif_module_id_by_path[pathKey] = moduleId;
g_sif_modules_by_id[moduleId] = record;
return moduleId;
}
bool trackSifModuleStop(int32_t moduleId, uint32_t *remainingRefs = nullptr)
{
if (moduleId <= 0)
{
if (remainingRefs)
{
*remainingRefs = 0;
}
return false;
}
std::lock_guard<std::mutex> lock(g_sif_module_mutex);
auto it = g_sif_modules_by_id.find(moduleId);
if (it == g_sif_modules_by_id.end())
{
if (remainingRefs)
{
*remainingRefs = 0;
}
return false;
}
SifModuleRecord &record = it->second;
if (record.refCount > 0)
{
--record.refCount;
}
record.loaded = (record.refCount != 0);
if (remainingRefs)
{
*remainingRefs = record.refCount;
}
return true;
}
bool readFileBlockAt(std::ifstream &file, uint64_t offset, void *dst, size_t byteCount)
{
if (!dst || byteCount == 0)
{
return false;
}
file.seekg(static_cast<std::streamoff>(offset), std::ios::beg);
if (!file)
{
return false;
}
file.read(reinterpret_cast<char *>(dst), static_cast<std::streamsize>(byteCount));
return file.gcount() == static_cast<std::streamsize>(byteCount);
}
bool tryExtractElfGpValue(std::ifstream &file, const Elf32Header &header, uint32_t &gpOut)
{
uint8_t regInfo[24] = {};
for (uint32_t i = 0; i < header.phnum; ++i)
{
Elf32ProgramHeader ph{};
const uint64_t phOffset = static_cast<uint64_t>(header.phoff) + static_cast<uint64_t>(i) * header.phentsize;
if (!readFileBlockAt(file, phOffset, &ph, sizeof(ph)))
{
return false;
}
if (ph.type == kElfPtMipsRegInfo && ph.filesz >= sizeof(regInfo))
{
if (!readFileBlockAt(file, ph.offset, regInfo, sizeof(regInfo)))
{
return false;
}
std::memcpy(&gpOut, regInfo + 20u, sizeof(gpOut));
return true;
}
}
for (uint32_t i = 0; i < header.shnum; ++i)
{
Elf32SectionHeader sh{};
const uint64_t shOffset = static_cast<uint64_t>(header.shoff) + static_cast<uint64_t>(i) * header.shentsize;
if (!readFileBlockAt(file, shOffset, &sh, sizeof(sh)))
{
return false;
}
if (sh.type == kElfShtMipsRegInfo && sh.size >= sizeof(regInfo))
{
if (!readFileBlockAt(file, sh.offset, regInfo, sizeof(regInfo)))
{
return false;
}
std::memcpy(&gpOut, regInfo + 20u, sizeof(gpOut));
return true;
}
}
return false;
}
bool loadElfIntoGuestMemory(const std::string &hostPath,
uint8_t *rdram,
PS2Runtime *runtime,
const std::string &sectionName,
GuestExecData &execDataOut,
std::string &errorOut)
{
if (!rdram || hostPath.empty())
{
errorOut = "invalid path or RDRAM pointer";
return false;
}
std::ifstream file(hostPath, std::ios::binary);
if (!file)
{
errorOut = "failed to open ELF";
return false;
}
Elf32Header header{};
if (!readFileBlockAt(file, 0, &header, sizeof(header)))
{
errorOut = "failed to read ELF header";
return false;
}
if (header.magic != kElfMagic || header.machine != kElfMachineMips || header.type != kElfTypeExec)
{
errorOut = "not a MIPS executable ELF";
return false;
}
bool loadedAny = false;
const bool loadAll = sectionName.empty() || toLowerAscii(sectionName) == "all";
static uint32_t secFilterLogCount = 0;
if (!loadAll && secFilterLogCount < 8u)
{
std::cout << "[SifLoadElfPart] section filter \"" << sectionName
<< "\" requested; loading PT_LOAD segments only." << std::endl;
++secFilterLogCount;
}
for (uint32_t i = 0; i < header.phnum; ++i)
{
Elf32ProgramHeader ph{};
const uint64_t phOffset = static_cast<uint64_t>(header.phoff) + static_cast<uint64_t>(i) * header.phentsize;
if (!readFileBlockAt(file, phOffset, &ph, sizeof(ph)))
{
errorOut = "failed to read ELF program headers";
return false;
}
if (ph.type != kElfPtLoad || ph.memsz == 0u)
{
continue;
}
if (ph.filesz > ph.memsz)
{
errorOut = "ELF segment filesz > memsz";
return false;
}
const uint64_t memSize64 = static_cast<uint64_t>(ph.memsz);
if (runtime && ph.vaddr >= PS2_SCRATCHPAD_BASE && ph.vaddr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE))
{
const uint32_t scratchOffset = runtime->memory().translateAddress(ph.vaddr);
if (static_cast<uint64_t>(scratchOffset) + memSize64 > PS2_SCRATCHPAD_SIZE)
{
errorOut = "ELF scratchpad segment out of range";
return false;
}
uint8_t *dest = runtime->memory().getScratchpad() + scratchOffset;
if (ph.filesz > 0u)
{
if (!readFileBlockAt(file, ph.offset, dest, ph.filesz))
{
errorOut = "failed to read ELF segment payload";
return false;
}
}
if (ph.memsz > ph.filesz)
{
std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz);
}
}
else
{
const uint32_t physAddr = runtime ? runtime->memory().translateAddress(ph.vaddr) : (ph.vaddr & PS2_RAM_MASK);
if (static_cast<uint64_t>(physAddr) + memSize64 > PS2_RAM_SIZE)
{
errorOut = "ELF RDRAM segment out of range";
return false;
}
uint8_t *dest = rdram + physAddr;
if (ph.filesz > 0u)
{
if (!readFileBlockAt(file, ph.offset, dest, ph.filesz))
{
errorOut = "failed to read ELF segment payload";
return false;
}
}
if (ph.memsz > ph.filesz)
{
std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz);
}
}
loadedAny = true;
}
if (!loadedAny)
{
errorOut = "ELF has no loadable segments";
return false;
}
execDataOut.epc = header.entry;
execDataOut.gp = 0u;
execDataOut.sp = 0u;
execDataOut.dummy = 0u;
uint32_t gpValue = 0u;
if (tryExtractElfGpValue(file, header, gpValue))
{
execDataOut.gp = gpValue;
}
return true;
}
int32_t runSifLoadElfPart(uint8_t *rdram,
R5900Context *ctx,
PS2Runtime *runtime,
uint32_t pathAddr,
const std::string &sectionName,
uint32_t execDataAddr)
{
if (!rdram || !ctx)
{
return -1;
}
const std::string ps2Path = readGuestCStringBounded(rdram, pathAddr, kLoadfilePathMaxBytes);
if (ps2Path.empty())
{
return -1;
}
const std::string hostPath = translatePs2Path(ps2Path.c_str());
if (hostPath.empty())
{
return -1;
}
GuestExecData execData{};
std::string loadError;
if (!loadElfIntoGuestMemory(hostPath, rdram, runtime, sectionName, execData, loadError))
{
static uint32_t logCount = 0;
if (logCount < 16u)
{
std::cerr << "[SifLoadElfPart] failed path=\"" << ps2Path << "\" host=\"" << hostPath
<< "\" reason=" << loadError << std::endl;
++logCount;
}
return -1;
}
if (execData.gp == 0u)
{
execData.gp = getRegU32(ctx, 28);
}
execData.sp = getRegU32(ctx, 29);
if (execDataAddr != 0u)
{
GuestExecData *guestExec = reinterpret_cast<GuestExecData *>(getMemPtr(rdram, execDataAddr));
if (!guestExec)
{
return -1;
}
std::memcpy(guestExec, &execData, sizeof(execData));
}
static uint32_t successLogs = 0;
if (successLogs < 16u)
{
std::cout << "[SifLoadElfPart] loaded \"" << ps2Path << "\" epc=0x"
<< std::hex << execData.epc << " gp=0x" << execData.gp << std::dec << std::endl;
++successLogs;
}
return 0;
}
}
@@ -0,0 +1,80 @@
namespace
{
std::string toLowerAscii(std::string value)
{
std::transform(value.begin(), value.end(), value.begin(),
[](unsigned char c)
{ return static_cast<char>(std::tolower(c)); });
return value;
}
std::string stripIsoVersionSuffix(std::string value)
{
const std::size_t semicolon = value.find(';');
if (semicolon == std::string::npos)
{
return value;
}
bool numericSuffix = semicolon + 1 < value.size();
for (std::size_t i = semicolon + 1; i < value.size(); ++i)
{
if (!std::isdigit(static_cast<unsigned char>(value[i])))
{
numericSuffix = false;
break;
}
}
if (numericSuffix)
{
value.erase(semicolon);
}
return value;
}
std::string normalizePs2PathSuffix(std::string suffix)
{
std::replace(suffix.begin(), suffix.end(), '\\', '/');
suffix = stripIsoVersionSuffix(std::move(suffix));
while (!suffix.empty() && (suffix.front() == '/' || suffix.front() == '\\'))
{
suffix.erase(suffix.begin());
}
return suffix;
}
std::filesystem::path getConfiguredHostRoot()
{
const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths();
if (!paths.hostRoot.empty())
{
return paths.hostRoot;
}
if (!paths.elfDirectory.empty())
{
return paths.elfDirectory;
}
std::error_code ec;
const std::filesystem::path cwd = std::filesystem::current_path(ec);
return ec ? std::filesystem::path(".") : cwd.lexically_normal();
}
std::filesystem::path getConfiguredCdRoot()
{
const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths();
if (!paths.cdRoot.empty())
{
return paths.cdRoot;
}
if (!paths.elfDirectory.empty())
{
return paths.elfDirectory;
}
std::error_code ec;
const std::filesystem::path cwd = std::filesystem::current_path(ec);
return ec ? std::filesystem::path(".") : cwd.lexically_normal();
}
}
@@ -0,0 +1,577 @@
namespace
{
struct ThreadExitException final : public std::exception
{
const char *what() const noexcept override
{
return "PS2 Thread Exit";
}
};
}
static void throwIfTerminated(const std::shared_ptr<ThreadInfo> &info)
{
if (info && info->terminated.load())
{
throw ThreadExitException();
}
}
static void waitWhileSuspended(const std::shared_ptr<ThreadInfo> &info)
{
if (!info)
return;
std::unique_lock<std::mutex> lock(info->m);
if (info->suspendCount > 0)
{
info->status = THS_SUSPEND;
info->waitType = TSW_NONE;
info->waitId = 0;
info->cv.wait(lock, [&]()
{ return info->suspendCount == 0 || info->terminated.load(); });
if (info->terminated.load())
{
throw ThreadExitException();
}
info->status = THS_RUN;
}
}
static std::shared_ptr<ThreadInfo> lookupThreadInfo(int tid)
{
std::lock_guard<std::mutex> lock(g_thread_map_mutex);
auto it = g_threads.find(tid);
if (it != g_threads.end())
{
return it->second;
}
return nullptr;
}
static std::shared_ptr<ThreadInfo> ensureCurrentThreadInfo(R5900Context *ctx)
{
const int tid = g_currentThreadId;
std::lock_guard<std::mutex> lock(g_thread_map_mutex);
auto it = g_threads.find(tid);
if (it != g_threads.end())
{
return it->second;
}
auto info = std::make_shared<ThreadInfo>();
info->started = true;
info->status = THS_RUN;
info->currentPriority = info->priority;
info->suspendCount = 0;
if (ctx)
{
info->entry = ctx->pc;
info->stack = getRegU32(ctx, 29);
info->gp = getRegU32(ctx, 28);
}
info->waitType = TSW_NONE;
info->waitId = 0;
g_threads.emplace(tid, info);
return info;
}
static std::shared_ptr<SemaInfo> lookupSemaInfo(int sid)
{
std::lock_guard<std::mutex> lock(g_sema_map_mutex);
auto it = g_semas.find(sid);
if (it != g_semas.end())
{
return it->second;
}
return nullptr;
}
static std::shared_ptr<EventFlagInfo> lookupEventFlagInfo(int eid)
{
std::lock_guard<std::mutex> lock(g_event_flag_map_mutex);
auto it = g_eventFlags.find(eid);
if (it != g_eventFlags.end())
{
return it->second;
}
return nullptr;
}
static void setRegU32(R5900Context *ctx, int reg, uint32_t value)
{
if (reg < 0 || reg > 31)
return;
ctx->r[reg] = _mm_set_epi32(0, 0, 0, value);
}
static std::chrono::microseconds alarmTicksToDuration(uint16_t ticks)
{
constexpr uint64_t kAlarmTickUsec = 64u; // Approximate EE H-SYNC tick period.
const uint64_t clampedTicks = (ticks == 0u) ? 1u : static_cast<uint64_t>(ticks);
return std::chrono::microseconds(clampedTicks * kAlarmTickUsec);
}
static void ensureAlarmWorkerRunning()
{
std::call_once(g_alarm_worker_once, []()
{ std::thread([]()
{
for (;;)
{
std::shared_ptr<AlarmInfo> readyAlarm;
{
std::unique_lock<std::mutex> lock(g_alarm_mutex);
while (!readyAlarm)
{
if (g_alarms.empty())
{
g_alarm_cv.wait(lock);
continue;
}
auto nextIt = std::min_element(g_alarms.begin(), g_alarms.end(),
[](const auto &a, const auto &b)
{
return a.second->dueAt < b.second->dueAt;
});
if (nextIt == g_alarms.end())
{
g_alarm_cv.wait(lock);
continue;
}
const auto now = std::chrono::steady_clock::now();
if (nextIt->second->dueAt > now)
{
g_alarm_cv.wait_until(lock, nextIt->second->dueAt);
continue;
}
readyAlarm = nextIt->second;
g_alarms.erase(nextIt);
}
}
if (!readyAlarm || !readyAlarm->runtime || !readyAlarm->rdram || !readyAlarm->handler)
{
continue;
}
if (!readyAlarm->runtime->hasFunction(readyAlarm->handler))
{
continue;
}
try
{
R5900Context callbackCtx{};
setRegU32(&callbackCtx, 28, readyAlarm->gp);
setRegU32(&callbackCtx, 29, readyAlarm->sp);
setRegU32(&callbackCtx, 31, 0);
setRegU32(&callbackCtx, 4, static_cast<uint32_t>(readyAlarm->id));
setRegU32(&callbackCtx, 5, static_cast<uint32_t>(readyAlarm->ticks));
setRegU32(&callbackCtx, 6, readyAlarm->commonArg);
setRegU32(&callbackCtx, 7, 0);
callbackCtx.pc = readyAlarm->handler;
PS2Runtime::RecompiledFunction func = readyAlarm->runtime->lookupFunction(readyAlarm->handler);
func(readyAlarm->rdram, &callbackCtx, readyAlarm->runtime);
}
catch (const ThreadExitException &)
{
}
catch (const std::exception &e)
{
static int alarmExceptionLogs = 0;
if (alarmExceptionLogs < 8)
{
std::cerr << "[SetAlarm] callback exception: " << e.what() << std::endl;
++alarmExceptionLogs;
}
}
} })
.detach(); });
}
static void rpcCopyToRdram(uint8_t *rdram, uint32_t dst, uint32_t src, size_t size)
{
if (!rdram || size == 0)
return;
constexpr size_t kMaxRpcTransferBytes = 1u * 1024u * 1024u;
const size_t clampedSize = std::min(size, kMaxRpcTransferBytes);
if (clampedSize != size)
{
static uint32_t warnCount = 0;
if (warnCount < 8)
{
std::cerr << "[SifCallRpc] clamping copy size from " << size
<< " to " << clampedSize
<< " bytes (dst=0x" << std::hex << dst
<< " src=0x" << src << std::dec << ")" << std::endl;
++warnCount;
}
}
for (size_t i = 0; i < clampedSize; ++i)
{
const uint32_t dstAddr = dst + static_cast<uint32_t>(i);
const uint32_t srcAddr = src + static_cast<uint32_t>(i);
uint8_t *dstPtr = getMemPtr(rdram, dstAddr);
const uint8_t *srcPtr = getConstMemPtr(rdram, srcAddr);
if (!dstPtr || !srcPtr)
{
break;
}
*dstPtr = *srcPtr;
}
}
static void rpcZeroRdram(uint8_t *rdram, uint32_t dst, size_t size)
{
if (!rdram || size == 0)
return;
constexpr size_t kMaxRpcTransferBytes = 1u * 1024u * 1024u;
const size_t clampedSize = std::min(size, kMaxRpcTransferBytes);
if (clampedSize != size)
{
static uint32_t warnCount = 0;
if (warnCount < 8)
{
std::cerr << "[SifCallRpc] clamping zero size from " << size
<< " to " << clampedSize
<< " bytes (dst=0x" << std::hex << dst << std::dec << ")" << std::endl;
++warnCount;
}
}
for (size_t i = 0; i < clampedSize; ++i)
{
const uint32_t dstAddr = dst + static_cast<uint32_t>(i);
uint8_t *dstPtr = getMemPtr(rdram, dstAddr);
if (!dstPtr)
{
break;
}
*dstPtr = 0;
}
}
static bool readStackU32(uint8_t *rdram, uint32_t sp, uint32_t offset, uint32_t &out)
{
uint8_t *ptr = getMemPtr(rdram, sp + offset);
if (!ptr)
return false;
out = *reinterpret_cast<uint32_t *>(ptr);
return true;
}
static bool rpcInvokeFunction(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime,
uint32_t funcAddr, uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t *outV0)
{
if (!runtime || !funcAddr || !runtime->hasFunction(funcAddr))
return false;
R5900Context tmp = *ctx;
setRegU32(&tmp, 4, a0);
setRegU32(&tmp, 5, a1);
setRegU32(&tmp, 6, a2);
setRegU32(&tmp, 7, a3);
tmp.pc = funcAddr;
PS2Runtime::RecompiledFunction func = runtime->lookupFunction(funcAddr);
func(rdram, &tmp, runtime);
if (outV0)
{
*outV0 = getRegU32(&tmp, 2);
}
return true;
}
static uint32_t rpcAllocPacketAddr(uint8_t *rdram)
{
if (kRpcPacketPoolCount == 0)
return 0;
uint32_t slot = g_rpc_packet_index++ % kRpcPacketPoolCount;
uint32_t addr = kRpcPacketPoolBase + (slot * kRpcPacketSize);
rpcZeroRdram(rdram, addr, kRpcPacketSize);
return addr;
}
static uint32_t rpcAllocServerAddr(uint8_t *rdram)
{
if (kRpcServerPoolCount == 0)
return 0;
uint32_t slot = g_rpc_server_index++ % kRpcServerPoolCount;
uint32_t addr = kRpcServerPoolBase + (slot * kRpcServerStride);
rpcZeroRdram(rdram, addr, kRpcServerStride);
return addr;
}
struct IrqHandlerInfo
{
uint32_t cause = 0;
uint32_t handler = 0;
uint32_t arg = 0;
bool enabled = true;
};
static std::unordered_map<int, IrqHandlerInfo> g_intcHandlers;
static std::unordered_map<int, IrqHandlerInfo> g_dmacHandlers;
static int g_nextIntcHandlerId = 1;
static int g_nextDmacHandlerId = 1;
std::string translatePs2Path(const char *ps2Path)
{
if (!ps2Path || !*ps2Path)
{
return {};
}
std::string pathStr(ps2Path);
std::string lower = toLowerAscii(pathStr);
auto resolveWithBase = [&](const std::filesystem::path &base, const std::string &suffix) -> std::string
{
const std::string normalizedSuffix = normalizePs2PathSuffix(suffix);
std::filesystem::path resolved = base;
if (!normalizedSuffix.empty())
{
resolved /= std::filesystem::path(normalizedSuffix);
}
return resolved.lexically_normal().string();
};
if (lower.rfind("host0:", 0) == 0 || lower.rfind("host:", 0) == 0)
{
const std::size_t prefixLength = (lower.rfind("host0:", 0) == 0) ? 6 : 5;
return resolveWithBase(getConfiguredHostRoot(), pathStr.substr(prefixLength));
}
if (lower.rfind("cdrom0:", 0) == 0 || lower.rfind("cdrom:", 0) == 0)
{
const std::size_t prefixLength = (lower.rfind("cdrom0:", 0) == 0) ? 7 : 6;
return resolveWithBase(getConfiguredCdRoot(), pathStr.substr(prefixLength));
}
if (!pathStr.empty() && (pathStr.front() == '/' || pathStr.front() == '\\'))
{
return resolveWithBase(getConfiguredCdRoot(), pathStr);
}
if (pathStr.size() > 1 && pathStr[1] == ':')
{
return pathStr;
}
return resolveWithBase(getConfiguredCdRoot(), pathStr);
}
static bool localtimeSafe(const std::time_t *t, std::tm *out)
{
#ifdef _WIN32
return localtime_s(out, t) == 0;
#else
return localtime_r(t, out) != nullptr;
#endif
}
static void encodePs2Time(std::time_t t, uint8_t out[8])
{
std::tm tm{};
if (!localtimeSafe(&t, &tm))
{
std::memset(out, 0, 8);
return;
}
uint16_t year = static_cast<uint16_t>(tm.tm_year + 1900);
out[0] = 0;
out[1] = static_cast<uint8_t>(tm.tm_sec);
out[2] = static_cast<uint8_t>(tm.tm_min);
out[3] = static_cast<uint8_t>(tm.tm_hour);
out[4] = static_cast<uint8_t>(tm.tm_mday);
out[5] = static_cast<uint8_t>(tm.tm_mon + 1);
out[6] = static_cast<uint8_t>(year & 0xFF);
out[7] = static_cast<uint8_t>((year >> 8) & 0xFF);
}
static std::time_t fileTimeToTimeT(std::filesystem::file_time_type ft)
{
auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
ft - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now());
return std::chrono::system_clock::to_time_t(sctp);
}
static bool gmtimeSafe(const std::time_t *t, std::tm *out)
{
#ifdef _WIN32
return gmtime_s(out, t) == 0;
#else
return gmtime_r(t, out) != nullptr;
#endif
}
static int getTimezoneOffsetMinutes()
{
std::time_t now = std::time(nullptr);
std::tm local{};
std::tm gmt{};
if (!localtimeSafe(&now, &local) || !gmtimeSafe(&now, &gmt))
return 0;
std::time_t localTime = std::mktime(&local);
std::time_t gmtTime = std::mktime(&gmt);
if (localTime == static_cast<std::time_t>(-1) || gmtTime == static_cast<std::time_t>(-1))
return 0;
double diff = std::difftime(localTime, gmtTime);
return static_cast<int>(diff / 60.0);
}
static uint32_t packOsdConfig(uint32_t spdifMode, uint32_t screenType, uint32_t videoOutput,
uint32_t japLanguage, uint32_t ps1drvConfig, uint32_t version,
uint32_t language, int timezoneOffset)
{
uint32_t raw = 0;
raw |= (spdifMode & 0x1) << 0;
raw |= (screenType & 0x3) << 1;
raw |= (videoOutput & 0x1) << 3;
raw |= (japLanguage & 0x1) << 4;
raw |= (ps1drvConfig & 0xFF) << 5;
raw |= (version & 0x7) << 13;
raw |= (language & 0x1F) << 16;
raw |= (static_cast<uint32_t>(timezoneOffset) & 0x7FF) << 21;
return raw;
}
static int decodeTimezoneOffset(uint32_t raw)
{
int tz = static_cast<int>((raw >> 21) & 0x7FF);
if (tz & 0x400)
tz |= ~0x7FF;
return tz;
}
static int clampTimezoneOffset(int tz)
{
if (tz < -1024)
return -1024;
if (tz > 1023)
return 1023;
return tz;
}
static uint32_t sanitizeOsdConfigRaw(uint32_t raw)
{
uint32_t spdifMode = raw & 0x1;
uint32_t screenType = (raw >> 1) & 0x3;
if (screenType > 2)
screenType = 0;
uint32_t videoOutput = (raw >> 3) & 0x1;
uint32_t japLanguage = (raw >> 4) & 0x1;
uint32_t ps1drvConfig = (raw >> 5) & 0xFF;
uint32_t version = (raw >> 13) & 0x7;
if (version > 2)
version = 1;
uint32_t language = (raw >> 16) & 0x1F;
int tz = clampTimezoneOffset(decodeTimezoneOffset(raw));
return packOsdConfig(spdifMode, screenType, videoOutput, japLanguage, ps1drvConfig, version, language, tz);
}
static void ensureOsdConfigInitialized()
{
std::lock_guard<std::mutex> lock(g_osd_mutex);
if (g_osd_config_initialized)
return;
int tz = clampTimezoneOffset(getTimezoneOffsetMinutes());
uint32_t spdifMode = 1; // disabled
uint32_t screenType = 0; // 4:3
uint32_t videoOutput = 0; // RGB
uint32_t japLanguage = 1; // non-japanese
uint32_t ps1drvConfig = 0;
uint32_t version = 1; // OSD2
uint32_t language = 1; // English
g_osd_config_raw = packOsdConfig(spdifMode, screenType, videoOutput, japLanguage, ps1drvConfig, version, language, tz);
g_osd_config_initialized = true;
}
static uint32_t allocTlsAddr(uint8_t *rdram)
{
if (!rdram || kTlsPoolCount == 0)
return 0;
std::lock_guard<std::mutex> lock(g_tls_mutex);
uint32_t slot = g_tls_index++ % kTlsPoolCount;
uint32_t addr = kTlsPoolBase + (slot * kTlsBlockSize);
rpcZeroRdram(rdram, addr, kTlsBlockSize);
return addr;
}
static uint32_t allocBootModeAddr(uint8_t *rdram, size_t bytes)
{
if (!rdram)
return 0;
size_t aligned = (bytes + 15u) & ~15u;
if (g_bootmode_pool_offset + aligned > kBootModePoolBytes)
return 0;
uint32_t addr = kBootModePoolBase + g_bootmode_pool_offset;
g_bootmode_pool_offset += static_cast<uint32_t>(aligned);
rpcZeroRdram(rdram, addr, aligned);
return addr;
}
static uint32_t createBootModeEntry(uint8_t *rdram, uint8_t id, uint16_t value, uint8_t lenField, const uint32_t *data, uint8_t dataCount)
{
uint8_t allocCount = (dataCount == 0) ? 1 : dataCount;
size_t bytes = static_cast<size_t>(1 + allocCount) * sizeof(uint32_t);
uint32_t addr = allocBootModeAddr(rdram, bytes);
if (!addr)
return 0;
uint32_t header = (static_cast<uint32_t>(lenField) << 24) |
(static_cast<uint32_t>(id) << 16) |
(static_cast<uint32_t>(value) & 0xFFFFu);
uint32_t *dst = reinterpret_cast<uint32_t *>(getMemPtr(rdram, addr));
if (!dst)
return 0;
dst[0] = header;
for (uint8_t i = 0; i < allocCount; ++i)
{
dst[1 + i] = (data && i < dataCount) ? data[i] : 0;
}
return addr;
}
static void ensureBootModeTable(uint8_t *rdram)
{
std::lock_guard<std::mutex> lock(g_bootmode_mutex);
if (g_bootmode_initialized)
return;
g_bootmode_pool_offset = 0;
g_bootmode_addresses.clear();
const uint32_t boot3Data[1] = {0};
const uint32_t boot5Data[1] = {0};
g_bootmode_addresses[1] = createBootModeEntry(rdram, 1, 0, 0, nullptr, 0);
g_bootmode_addresses[3] = createBootModeEntry(rdram, 3, 0, 1, boot3Data, 1);
g_bootmode_addresses[4] = createBootModeEntry(rdram, 4, 0, 0, nullptr, 0);
g_bootmode_addresses[5] = createBootModeEntry(rdram, 5, 0, 1, boot5Data, 1);
g_bootmode_addresses[6] = createBootModeEntry(rdram, 6, 0, 0, nullptr, 0);
g_bootmode_addresses[7] = createBootModeEntry(rdram, 7, 0, 0, nullptr, 0);
g_bootmode_initialized = true;
}
@@ -0,0 +1,449 @@
std::unordered_map<int, FILE *> g_fileDescriptors;
int g_nextFd = 3; // Start after stdin, stdout, stderr
struct ThreadInfo
{
uint32_t entry = 0;
uint32_t stack = 0;
uint32_t stackSize = 0;
uint32_t gp = 0;
uint32_t priority = 0;
uint32_t attr = 0;
uint32_t option = 0;
uint32_t arg = 0;
bool started = false;
uint32_t tlsBase = 0;
// Thread Status
int status = 0x10; // THS_DORMANT
int waitType = 0; // TSW_NONE
int waitId = 0;
int wakeupCount = 0;
int currentPriority = 0;
int suspendCount = 0;
std::mutex m;
std::condition_variable cv;
std::atomic<bool> forceRelease{false};
std::atomic<bool> terminated{false};
};
// Thread status
#define THS_RUN 0x01
#define THS_READY 0x02
#define THS_WAIT 0x04
#define THS_SUSPEND 0x08
#define THS_WAITSUSPEND 0x0c
#define THS_DORMANT 0x10
// Thread WAIT Status
#define TSW_NONE 0
#define TSW_SLEEP 1
#define TSW_SEMA 2
#define TSW_EVENT 3
// Common kernel-like error codes used by thread/event/alarm syscalls.
constexpr int KE_OK = 0;
constexpr int KE_ERROR = -1;
constexpr int KE_ILLEGAL_MODE = -405;
constexpr int KE_ILLEGAL_THID = -406;
constexpr int KE_UNKNOWN_THID = -407;
constexpr int KE_UNKNOWN_SEMID = -408;
constexpr int KE_UNKNOWN_EVFID = -409;
constexpr int KE_DORMANT = -413;
constexpr int KE_NOT_WAIT = -416;
constexpr int KE_RELEASE_WAIT = -418;
constexpr int KE_SEMA_ZERO = -419;
constexpr int KE_EVF_COND = -421;
constexpr int KE_EVF_MULTI = -422;
constexpr int KE_EVF_ILPAT = -423;
constexpr int KE_WAIT_DELETE = -425;
// SIF RPC Structures
struct t_SifRpcHeader
{
uint32_t pkt_addr; // void*
uint32_t rpc_id;
int sema_id;
uint32_t mode;
};
struct t_SifRpcClientData
{
t_SifRpcHeader hdr;
uint32_t command;
uint32_t buf; // void*
uint32_t cbuf; // void*
uint32_t end_function; // func ptr
uint32_t end_param; // void*
uint32_t server; // t_SifRpcServerData*
};
struct t_SifRpcServerData
{
int sid;
uint32_t func; // func ptr
uint32_t buf; // void*
int size;
uint32_t cfunc; // func ptr
uint32_t cbuf; // void*
int size2;
uint32_t client; // t_SifRpcClientData*
uint32_t pkt_addr; // void*
int rpc_number;
uint32_t recvbuf; // void*
int rsize;
int rmode;
int rid;
uint32_t link; // t_SifRpcServerData*
uint32_t next; // t_SifRpcServerData*
uint32_t base; // t_SifRpcDataQueue*
};
struct t_SifRpcDataQueue
{
int thread_id;
int active;
uint32_t link; // t_SifRpcServerData*
uint32_t start; // t_SifRpcServerData*
uint32_t end; // t_SifRpcServerData*
uint32_t next; // t_SifRpcDataQueue*
};
struct ee_thread_status_t
{
int status; // 0x00
uint32_t func; // 0x04
uint32_t stack; // 0x08
int stack_size; // 0x0C
uint32_t gp_reg; // 0x10
int initial_priority; // 0x14
int current_priority; // 0x18
uint32_t attr; // 0x1C
uint32_t option; // 0x20
uint32_t waitType; // 0x24
uint32_t waitId; // 0x28
uint32_t wakeupCount; // 0x2C
};
struct ee_sema_t
{
int count;
int max_count;
int init_count;
int wait_threads;
uint32_t attr;
uint32_t option;
};
struct SemaInfo
{
int count = 0;
int maxCount = 0;
int initCount = 0;
uint32_t attr = 0;
uint32_t option = 0;
int waiters = 0;
bool deleted = false;
std::mutex m;
std::condition_variable cv;
};
struct EventFlagInfo
{
uint32_t attr = 0;
uint32_t option = 0;
uint32_t initBits = 0;
uint32_t bits = 0;
int waiters = 0;
bool deleted = false;
std::mutex m;
std::condition_variable cv;
};
struct AlarmInfo
{
int id = 0;
uint16_t ticks = 0;
uint32_t handler = 0;
uint32_t commonArg = 0;
uint32_t gp = 0;
uint32_t sp = 0;
uint8_t *rdram = nullptr;
PS2Runtime *runtime = nullptr;
std::chrono::steady_clock::time_point dueAt;
};
struct io_stat_t
{
uint32_t mode;
uint32_t attr;
uint32_t size;
uint8_t ctime[8];
uint8_t atime[8];
uint8_t mtime[8];
uint32_t hisize;
};
static constexpr uint32_t kFioSoIfLnk = 0x0008;
static constexpr uint32_t kFioSoIfReg = 0x0010;
static constexpr uint32_t kFioSoIfDir = 0x0020;
static constexpr uint32_t kFioSoIROth = 0x0004;
static constexpr uint32_t kFioSoIWOth = 0x0002;
static constexpr uint32_t kFioSoIXOth = 0x0001;
static std::unordered_map<int, std::shared_ptr<ThreadInfo>> g_threads;
static int g_nextThreadId = 2; // Reserve 1 for the main thread
static thread_local int g_currentThreadId = 1;
static std::mutex g_thread_map_mutex;
static std::unordered_map<int, std::shared_ptr<SemaInfo>> g_semas;
static int g_nextSemaId = 1;
static std::mutex g_sema_map_mutex;
static std::unordered_map<int, std::shared_ptr<EventFlagInfo>> g_eventFlags;
static int g_nextEventFlagId = 1;
static std::mutex g_event_flag_map_mutex;
static std::unordered_map<int, std::shared_ptr<AlarmInfo>> g_alarms;
static int g_nextAlarmId = 1;
static std::mutex g_alarm_mutex;
static std::condition_variable g_alarm_cv;
static std::once_flag g_alarm_worker_once;
std::atomic<int> g_activeThreads{0};
static std::mutex g_fd_mutex;
struct RpcServerState
{
uint32_t sid = 0;
uint32_t sd_ptr = 0; // PS2 address
};
struct RpcClientState
{
bool busy = false;
uint32_t last_rpc = 0;
uint32_t sid = 0;
};
static std::unordered_map<uint32_t, RpcServerState> g_rpc_servers;
static std::unordered_map<uint32_t, RpcClientState> g_rpc_clients;
static std::mutex g_rpc_mutex;
static bool g_rpc_initialized = false;
static uint32_t g_rpc_next_id = 1;
static uint32_t g_rpc_packet_index = 0;
static uint32_t g_rpc_server_index = 0;
static uint32_t g_rpc_active_queue = 0;
static constexpr uint32_t kDtxRpcSid = 0x7D000000u;
static constexpr uint32_t kDtxUrpcObjBase = 0x01F18000u;
static constexpr uint32_t kDtxUrpcObjLimit = 0x01F1FF00u;
static constexpr uint32_t kDtxUrpcFnTableBase = 0x0034FED0u;
static constexpr uint32_t kDtxUrpcObjTableBase = 0x0034FFD0u;
static std::mutex g_dtx_rpc_mutex;
static std::unordered_map<uint32_t, uint32_t> g_dtx_remote_by_id;
static uint32_t g_dtx_next_urpc_obj = kDtxUrpcObjBase;
struct DtxSjrmtState
{
uint32_t handle = 0;
uint32_t mode = 0;
uint32_t wkAddr = 0;
uint32_t wkSize = 0;
uint32_t readPos = 0;
uint32_t writePos = 0;
uint32_t roomBytes = 0;
uint32_t dataBytes = 0;
uint32_t uuid0 = 0;
uint32_t uuid1 = 0;
uint32_t uuid2 = 0;
uint32_t uuid3 = 0;
};
static std::unordered_map<uint32_t, DtxSjrmtState> g_dtx_sjrmt_by_handle;
static uint32_t dtxNormalizeSjrmtCapacity(uint32_t requestedBytes)
{
if (requestedBytes == 0u || requestedBytes > 0x01000000u)
{
return 0x4000u;
}
return requestedBytes;
}
static uint32_t dtxAllocUrpcHandleLocked()
{
for (uint32_t i = 0; i < 4096u; ++i)
{
uint32_t candidate = g_dtx_next_urpc_obj;
g_dtx_next_urpc_obj += 0x20u;
if (g_dtx_next_urpc_obj < kDtxUrpcObjBase || g_dtx_next_urpc_obj >= kDtxUrpcObjLimit)
{
g_dtx_next_urpc_obj = kDtxUrpcObjBase;
}
if (candidate < kDtxUrpcObjBase || candidate >= kDtxUrpcObjLimit)
{
continue;
}
if (g_dtx_sjrmt_by_handle.find(candidate) != g_dtx_sjrmt_by_handle.end())
{
continue;
}
bool inUseByDtxRemote = false;
for (const auto &entry : g_dtx_remote_by_id)
{
if (entry.second == candidate)
{
inUseByDtxRemote = true;
break;
}
}
if (!inUseByDtxRemote)
{
return candidate;
}
}
return kDtxUrpcObjBase;
}
struct ExitHandlerEntry
{
uint32_t func = 0;
uint32_t arg = 0;
};
static std::mutex g_exit_handler_mutex;
static std::unordered_map<int, std::vector<ExitHandlerEntry>> g_exit_handlers;
static std::mutex g_bootmode_mutex;
static bool g_bootmode_initialized = false;
static uint32_t g_bootmode_pool_offset = 0;
static std::unordered_map<uint8_t, uint32_t> g_bootmode_addresses;
static std::mutex g_tls_mutex;
static uint32_t g_tls_index = 0;
static std::mutex g_osd_mutex;
static bool g_osd_config_initialized = false;
static uint32_t g_osd_config_raw = 0;
static std::mutex g_ps2_path_mutex;
static bool g_ps2_paths_initialized = false;
static std::filesystem::path g_host_base;
static std::filesystem::path g_cdrom_base;
static std::filesystem::path g_host_cwd;
static std::filesystem::path g_cdrom_cwd;
static std::string g_ps2_cwd_device = "host0";
static constexpr uint32_t kRpcPacketSize = 64;
static constexpr uint32_t kRpcPacketPoolBase = 0x01F00000;
static constexpr uint32_t kRpcPacketPoolBytes = 0x00010000;
static constexpr uint32_t kRpcPacketPoolCount = kRpcPacketPoolBytes / kRpcPacketSize;
static constexpr uint32_t kRpcServerPoolBase = 0x01F10000;
static constexpr uint32_t kRpcServerPoolBytes = 0x00010000;
static constexpr uint32_t kRpcServerStride = 0x80;
static constexpr uint32_t kRpcServerPoolCount = kRpcServerPoolBytes / kRpcServerStride;
static constexpr uint32_t kTlsPoolBase = 0x01F20000;
static constexpr uint32_t kTlsPoolBytes = 0x00010000;
static constexpr uint32_t kTlsBlockSize = 0x100;
static constexpr uint32_t kTlsPoolCount = kTlsPoolBytes / kTlsBlockSize;
static constexpr uint32_t kBootModePoolBase = 0x01F30000;
static constexpr uint32_t kBootModePoolBytes = 0x00001000;
static constexpr uint32_t kSifRpcModeNowait = 0x01;
static constexpr uint32_t kSifRpcModeNoWbDc = 0x02;
static constexpr size_t kMaxSifModulePathBytes = 260;
static constexpr uint32_t kMaxSifModuleLogs = 24;
static constexpr size_t kSifModuleBufferProbeBytes = 2048;
static constexpr size_t kLoadfilePathMaxBytes = 252;
static constexpr size_t kLoadfileArgMaxBytes = 252;
static constexpr uint32_t kElfMagic = 0x464C457Fu;
static constexpr uint16_t kElfMachineMips = 8u;
static constexpr uint16_t kElfTypeExec = 2u;
static constexpr uint32_t kElfPtLoad = 1u;
static constexpr uint32_t kElfPtMipsRegInfo = 0x70000000u;
static constexpr uint32_t kElfShtMipsRegInfo = 0x70000006u;
#pragma pack(push, 1)
struct Elf32Header
{
uint32_t magic;
uint8_t elfClass;
uint8_t endianness;
uint8_t version;
uint8_t osAbi;
uint8_t abiVersion;
uint8_t pad[7];
uint16_t type;
uint16_t machine;
uint32_t version2;
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 Elf32ProgramHeader
{
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 Elf32SectionHeader
{
uint32_t name;
uint32_t type;
uint32_t flags;
uint32_t addr;
uint32_t offset;
uint32_t size;
uint32_t link;
uint32_t info;
uint32_t addralign;
uint32_t entsize;
};
struct GuestExecData
{
uint32_t epc;
uint32_t gp;
uint32_t sp;
uint32_t dummy;
};
#pragma pack(pop)
static_assert(sizeof(Elf32Header) == 52u, "Unexpected ELF32 header layout.");
static_assert(sizeof(Elf32ProgramHeader) == 32u, "Unexpected ELF32 program header layout.");
static_assert(sizeof(Elf32SectionHeader) == 40u, "Unexpected ELF32 section header layout.");
static_assert(sizeof(GuestExecData) == 16u, "Unexpected GuestExecData layout.");
struct SifModuleRecord
{
int32_t id = 0;
std::string path;
std::string pathKey;
uint32_t refCount = 0;
bool loaded = false;
};
static std::mutex g_sif_module_mutex;
static std::unordered_map<int32_t, SifModuleRecord> g_sif_modules_by_id;
static std::unordered_map<std::string, int32_t> g_sif_module_id_by_path;
static int32_t g_next_sif_module_id = 1;
static uint32_t g_sif_module_log_count = 0;
@@ -0,0 +1,450 @@
static int allocatePs2Fd(FILE *file)
{
if (!file)
return -1;
std::lock_guard<std::mutex> lock(g_fd_mutex);
int fd = g_nextFd++;
g_fileDescriptors[fd] = file;
return fd;
}
static FILE *getHostFile(int ps2Fd)
{
std::lock_guard<std::mutex> lock(g_fd_mutex);
auto it = g_fileDescriptors.find(ps2Fd);
if (it != g_fileDescriptors.end())
{
return it->second;
}
return nullptr;
}
static void releasePs2Fd(int ps2Fd)
{
std::lock_guard<std::mutex> lock(g_fd_mutex);
g_fileDescriptors.erase(ps2Fd);
}
static const char *translateFioMode(int ps2Flags)
{
bool read = (ps2Flags & PS2_FIO_O_RDONLY) || (ps2Flags & PS2_FIO_O_RDWR);
bool write = (ps2Flags & PS2_FIO_O_WRONLY) || (ps2Flags & PS2_FIO_O_RDWR);
bool append = (ps2Flags & PS2_FIO_O_APPEND);
bool create = (ps2Flags & PS2_FIO_O_CREAT);
bool truncate = (ps2Flags & PS2_FIO_O_TRUNC);
if (read && write)
{
if (create && truncate)
return "w+b";
if (create)
return "a+b";
return "r+b";
}
else if (write)
{
if (append)
return "ab";
if (create && truncate)
return "wb";
if (create)
return "wx";
return "r+b";
}
else if (read)
{
return "rb";
}
return "rb";
}
void fioOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t pathAddr = getRegU32(ctx, 4); // $a0
int flags = (int)getRegU32(ctx, 5); // $a1 (PS2 FIO flags)
const char *ps2Path = reinterpret_cast<const char *>(getConstMemPtr(rdram, pathAddr));
if (!ps2Path)
{
std::cerr << "fioOpen error: Invalid path address" << std::endl;
setReturnS32(ctx, -1);
return;
}
std::string hostPath = translatePs2Path(ps2Path);
if (hostPath.empty())
{
std::cerr << "fioOpen error: Failed to translate path '" << ps2Path << "'" << std::endl;
setReturnS32(ctx, -1);
return;
}
const char *mode = translateFioMode(flags);
std::cout << "fioOpen: '" << hostPath << "' flags=0x" << std::hex << flags << std::dec << " mode='" << mode << "'" << std::endl;
FILE *fp = ::fopen(hostPath.c_str(), mode);
if (!fp)
{
std::cerr << "fioOpen error: fopen failed for '" << hostPath << "': " << strerror(errno) << std::endl;
setReturnS32(ctx, -1); // e.g., -ENOENT, -EACCES
return;
}
int ps2Fd = allocatePs2Fd(fp);
if (ps2Fd < 0)
{
std::cerr << "fioOpen error: Failed to allocate PS2 file descriptor" << std::endl;
::fclose(fp);
setReturnS32(ctx, -1); // e.g., -EMFILE
return;
}
// returns the PS2 file descriptor
setReturnS32(ctx, ps2Fd);
}
void fioClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int ps2Fd = (int)getRegU32(ctx, 4); // $a0
std::cout << "fioClose: fd=" << ps2Fd << std::endl;
FILE *fp = getHostFile(ps2Fd);
if (!fp)
{
std::cerr << "fioClose warning: Invalid PS2 file descriptor " << ps2Fd << std::endl;
setReturnS32(ctx, -1); // e.g., -EBADF
return;
}
int ret = ::fclose(fp);
releasePs2Fd(ps2Fd);
// returns 0 on success, -1 on error
setReturnS32(ctx, ret == 0 ? 0 : -1);
}
void fioRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int ps2Fd = (int)getRegU32(ctx, 4); // $a0
uint32_t bufAddr = getRegU32(ctx, 5); // $a1
size_t size = getRegU32(ctx, 6); // $a2
uint8_t *hostBuf = getMemPtr(rdram, bufAddr);
FILE *fp = getHostFile(ps2Fd);
if (!hostBuf)
{
std::cerr << "fioRead error: Invalid buffer address for fd " << ps2Fd << std::endl;
setReturnS32(ctx, -1); // -EFAULT
return;
}
if (!fp)
{
std::cerr << "fioRead error: Invalid file descriptor " << ps2Fd << std::endl;
setReturnS32(ctx, -1); // -EBADF
return;
}
if (size == 0)
{
setReturnS32(ctx, 0); // Read 0 bytes
return;
}
size_t bytesRead = 0;
{
std::lock_guard<std::mutex> lock(g_sys_fd_mutex);
bytesRead = fread(hostBuf, 1, size, fp);
}
if (bytesRead < size && ferror(fp))
{
std::cerr << "fioRead error: fread failed for fd " << ps2Fd << ": " << strerror(errno) << std::endl;
clearerr(fp);
setReturnS32(ctx, -1); // -EIO or other appropriate error
return;
}
// returns number of bytes read (can be 0 for EOF)
setReturnS32(ctx, (int32_t)bytesRead);
}
void fioWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int ps2Fd = (int)getRegU32(ctx, 4); // $a0
uint32_t bufAddr = getRegU32(ctx, 5); // $a1
size_t size = getRegU32(ctx, 6); // $a2
const uint8_t *hostBuf = getConstMemPtr(rdram, bufAddr);
if (!hostBuf)
{
setReturnS32(ctx, -1);
return;
}
size_t bytesWritten = 0;
{
std::lock_guard<std::mutex> lock(g_fd_mutex);
FILE *fp = getHostFile(ps2Fd);
if (!fp)
{
setReturnS32(ctx, -1); // -EFAULT
return;
}
if (size == 0)
{
setReturnS32(ctx, 0); // Wrote 0 bytes
return;
}
bytesWritten = ::fwrite(hostBuf, 1, size, fp);
if (bytesWritten < size && ferror(fp))
{
clearerr(fp);
setReturnS32(ctx, -1); // -EIO, -ENOSPC etc.
return;
}
}
// returns number of bytes written
setReturnS32(ctx, (int32_t)bytesWritten);
}
void fioLseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int ps2Fd = (int)getRegU32(ctx, 4); // $a0
int32_t offset = getRegU32(ctx, 5); // $a1 (PS2 seems to use 32-bit offset here commonly)
int whence = (int)getRegU32(ctx, 6); // $a2 (PS2 FIO_SEEK constants)
FILE *fp = getHostFile(ps2Fd);
if (!fp)
{
std::cerr << "fioLseek error: Invalid file descriptor " << ps2Fd << std::endl;
setReturnS32(ctx, -1); // -EBADF
return;
}
int hostWhence;
switch (whence)
{
case PS2_FIO_SEEK_SET:
hostWhence = SEEK_SET;
break;
case PS2_FIO_SEEK_CUR:
hostWhence = SEEK_CUR;
break;
case PS2_FIO_SEEK_END:
hostWhence = SEEK_END;
break;
default:
std::cerr << "fioLseek error: Invalid whence value " << whence << " for fd " << ps2Fd << std::endl;
setReturnS32(ctx, -1); // -EINVAL
return;
}
if (::fseek(fp, static_cast<long>(offset), hostWhence) != 0)
{
std::cerr << "fioLseek error: fseek failed for fd " << ps2Fd << ": " << strerror(errno) << std::endl;
setReturnS32(ctx, -1); // Return error code
return;
}
long newPos = ::ftell(fp);
if (newPos < 0)
{
std::cerr << "fioLseek error: ftell failed after fseek for fd " << ps2Fd << ": " << strerror(errno) << std::endl;
setReturnS32(ctx, -1);
}
else
{
if (newPos > 0xFFFFFFFFL)
{
std::cerr << "fioLseek warning: New position exceeds 32-bit for fd " << ps2Fd << std::endl;
setReturnS32(ctx, -1);
}
else
{
setReturnS32(ctx, (int32_t)newPos);
}
}
}
void fioMkdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t pathAddr = getRegU32(ctx, 4); // $a0
// int mode = (int)getRegU32(ctx, 5);
const char *ps2Path = reinterpret_cast<const char *>(getConstMemPtr(rdram, pathAddr));
if (!ps2Path)
{
std::cerr << "fioMkdir error: Invalid path address" << std::endl;
setReturnS32(ctx, -1); // -EFAULT
return;
}
std::string hostPath = translatePs2Path(ps2Path);
if (hostPath.empty())
{
std::cerr << "fioMkdir error: Failed to translate path '" << ps2Path << "'" << std::endl;
setReturnS32(ctx, -1);
return;
}
#ifdef _WIN32
int ret = -1;
#else
int ret = ::mkdir(hostPath.c_str(), 0775);
#endif
if (ret != 0)
{
std::cerr << "fioMkdir error: mkdir failed for '" << hostPath << "': " << strerror(errno) << std::endl;
setReturnS32(ctx, -1); // errno
}
else
{
setReturnS32(ctx, 0); // Success
}
}
void fioChdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t pathAddr = getRegU32(ctx, 4); // $a0
const char *ps2Path = reinterpret_cast<const char *>(getConstMemPtr(rdram, pathAddr));
if (!ps2Path)
{
std::cerr << "fioChdir error: Invalid path address" << std::endl;
setReturnS32(ctx, -1);
return;
}
std::string hostPath = translatePs2Path(ps2Path);
if (hostPath.empty())
{
std::cerr << "fioChdir error: Failed to translate path '" << ps2Path << "'" << std::endl;
setReturnS32(ctx, -1);
return;
}
std::cerr << "fioChdir: Attempting host chdir to '" << hostPath << "' (Stub - Check side effects)" << std::endl;
#ifdef _WIN32
int ret = -1;
#else
int ret = ::chdir(hostPath.c_str());
#endif
if (ret != 0)
{
std::cerr << "fioChdir error: chdir failed for '" << hostPath << "': " << strerror(errno) << std::endl;
setReturnS32(ctx, -1);
}
else
{
setReturnS32(ctx, 0); // Success
}
}
void fioRmdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t pathAddr = getRegU32(ctx, 4); // $a0
const char *ps2Path = reinterpret_cast<const char *>(getConstMemPtr(rdram, pathAddr));
if (!ps2Path)
{
std::cerr << "fioRmdir error: Invalid path address" << std::endl;
setReturnS32(ctx, -1);
return;
}
std::string hostPath = translatePs2Path(ps2Path);
if (hostPath.empty())
{
std::cerr << "fioRmdir error: Failed to translate path '" << ps2Path << "'" << std::endl;
setReturnS32(ctx, -1);
return;
}
#ifdef _WIN32
int ret = -1;
#else
int ret = ::rmdir(hostPath.c_str());
#endif
if (ret != 0)
{
std::cerr << "fioRmdir error: rmdir failed for '" << hostPath << "': " << strerror(errno) << std::endl;
setReturnS32(ctx, -1);
}
else
{
setReturnS32(ctx, 0); // Success
}
}
void fioGetstat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
// we wont implement this for now.
uint32_t pathAddr = getRegU32(ctx, 4); // $a0
uint32_t statBufAddr = getRegU32(ctx, 5); // $a1
const char *ps2Path = reinterpret_cast<const char *>(getConstMemPtr(rdram, pathAddr));
uint8_t *ps2StatBuf = getMemPtr(rdram, statBufAddr);
if (!ps2Path)
{
std::cerr << "fioGetstat error: Invalid path addr" << std::endl;
setReturnS32(ctx, -1);
return;
}
if (!ps2StatBuf)
{
std::cerr << "fioGetstat error: Invalid buffer addr" << std::endl;
setReturnS32(ctx, -1);
return;
}
std::string hostPath = translatePs2Path(ps2Path);
if (hostPath.empty())
{
std::cerr << "fioGetstat error: Bad path translate" << std::endl;
setReturnS32(ctx, -1);
return;
}
setReturnS32(ctx, -1);
}
void fioRemove(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t pathAddr = getRegU32(ctx, 4); // $a0
const char *ps2Path = reinterpret_cast<const char *>(getConstMemPtr(rdram, pathAddr));
if (!ps2Path)
{
std::cerr << "fioRemove error: Invalid path" << std::endl;
setReturnS32(ctx, -1);
return;
}
std::string hostPath = translatePs2Path(ps2Path);
if (hostPath.empty())
{
std::cerr << "fioRemove error: Path translate fail" << std::endl;
setReturnS32(ctx, -1);
return;
}
#ifdef _WIN32
int ret = -1;
#else
int ret = ::unlink(hostPath.c_str());
#endif
if (ret != 0)
{
std::cerr << "fioRemove error: unlink failed for '" << hostPath << "': " << strerror(errno) << std::endl;
setReturnS32(ctx, -1);
}
else
{
setReturnS32(ctx, 0); // Success
}
}
@@ -0,0 +1,649 @@
void CreateSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t paramAddr = getRegU32(ctx, 4); // $a0
const uint32_t *param = reinterpret_cast<const uint32_t *>(getConstMemPtr(rdram, paramAddr));
int init = 0;
int max = 1;
uint32_t attr = 0;
uint32_t option = 0;
if (param)
{
// sceSemaParam layout commonly: attr(0), option(1), initCount(2), maxCount(3)
attr = param[0];
option = param[1];
init = static_cast<int>(param[2]);
max = static_cast<int>(param[3]);
}
if (max <= 0)
{
max = 1;
}
if (init > max)
{
init = max;
}
int id = 0;
auto info = std::make_shared<SemaInfo>();
info->count = init;
info->maxCount = max;
info->initCount = init;
info->attr = attr;
info->option = option;
{
std::lock_guard<std::mutex> lock(g_sema_map_mutex);
id = g_nextSemaId++;
g_semas.emplace(id, info);
}
std::cout << "[CreateSema] id=" << id << " init=" << init << " max=" << max << std::endl;
setReturnS32(ctx, id);
}
void DeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int sid = static_cast<int>(getRegU32(ctx, 4));
std::shared_ptr<SemaInfo> sema;
{
std::lock_guard<std::mutex> lock(g_sema_map_mutex);
auto it = g_semas.find(sid);
if (it == g_semas.end())
{
setReturnS32(ctx, KE_UNKNOWN_SEMID);
return;
}
sema = it->second;
g_semas.erase(it);
}
{
std::lock_guard<std::mutex> lock(sema->m);
sema->deleted = true;
}
sema->cv.notify_all();
setReturnS32(ctx, KE_OK);
}
void SignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int sid = static_cast<int>(getRegU32(ctx, 4));
auto sema = lookupSemaInfo(sid);
if (sema)
{
std::lock_guard<std::mutex> lock(sema->m);
if (sema->count < sema->maxCount)
{
sema->count++;
}
sema->cv.notify_one();
}
setReturnS32(ctx, 0);
}
void iSignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
SignalSema(rdram, ctx, runtime);
}
void WaitSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int sid = static_cast<int>(getRegU32(ctx, 4));
auto sema = lookupSemaInfo(sid);
if (!sema)
{
setReturnS32(ctx, KE_UNKNOWN_SEMID);
return;
}
auto info = ensureCurrentThreadInfo(ctx);
throwIfTerminated(info);
std::unique_lock<std::mutex> lock(sema->m);
int ret = 0;
if (sema->count == 0)
{
if (info)
{
std::lock_guard<std::mutex> tLock(info->m);
info->status = THS_WAIT;
info->waitType = TSW_SEMA;
info->waitId = sid;
info->forceRelease = false;
}
sema->waiters++;
sema->cv.wait(lock, [&]()
{
bool forced = info ? info->forceRelease.load() : false;
bool terminated = info ? info->terminated.load() : false;
return sema->count > 0 || sema->deleted || forced || terminated; //
});
sema->waiters--;
if (sema->deleted)
{
ret = KE_WAIT_DELETE;
}
if (info)
{
std::lock_guard<std::mutex> tLock(info->m);
info->status = THS_RUN;
info->waitType = TSW_NONE;
info->waitId = 0;
if (info->forceRelease)
{
info->forceRelease = false;
ret = KE_RELEASE_WAIT;
}
}
if (info && info->terminated.load())
{
throw ThreadExitException();
}
}
if (ret == 0 && sema->count > 0)
{
sema->count--;
}
lock.unlock();
waitWhileSuspended(info);
setReturnS32(ctx, ret);
}
void PollSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int sid = static_cast<int>(getRegU32(ctx, 4));
auto sema = lookupSemaInfo(sid);
if (!sema)
{
setReturnS32(ctx, KE_UNKNOWN_SEMID);
return;
}
std::lock_guard<std::mutex> lock(sema->m);
if (sema->count > 0)
{
sema->count--;
setReturnS32(ctx, KE_OK);
return;
}
setReturnS32(ctx, KE_SEMA_ZERO);
}
void iPollSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
PollSema(rdram, ctx, runtime);
}
void ReferSemaStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int sid = static_cast<int>(getRegU32(ctx, 4));
uint32_t statusAddr = getRegU32(ctx, 5);
auto sema = lookupSemaInfo(sid);
if (!sema)
{
setReturnS32(ctx, -1);
return;
}
ee_sema_t *status = reinterpret_cast<ee_sema_t *>(getMemPtr(rdram, statusAddr));
if (!status)
{
setReturnS32(ctx, -1);
return;
}
std::lock_guard<std::mutex> lock(sema->m);
status->count = sema->count;
status->max_count = sema->maxCount;
status->init_count = sema->initCount;
status->wait_threads = sema->waiters;
status->attr = sema->attr;
status->option = sema->option;
setReturnS32(ctx, 0);
}
void iReferSemaStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
ReferSemaStatus(rdram, ctx, runtime);
}
constexpr uint32_t WEF_OR = 1;
constexpr uint32_t WEF_CLEAR = 0x10;
constexpr uint32_t WEF_CLEAR_ALL = 0x20;
constexpr uint32_t WEF_MODE_MASK = WEF_OR | WEF_CLEAR | WEF_CLEAR_ALL;
constexpr uint32_t EA_MULTI = 0x2;
void CreateEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t paramAddr = getRegU32(ctx, 4); // $a0
const uint32_t *param = reinterpret_cast<const uint32_t *>(getConstMemPtr(rdram, paramAddr));
auto info = std::make_shared<EventFlagInfo>();
if (param)
{
info->attr = param[0];
info->option = param[1];
info->initBits = param[2];
info->bits = info->initBits;
}
int id = 0;
{
std::lock_guard<std::mutex> mapLock(g_event_flag_map_mutex);
id = g_nextEventFlagId++;
g_eventFlags[id] = info;
}
setReturnS32(ctx, id);
}
void DeleteEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int eid = static_cast<int>(getRegU32(ctx, 4));
std::shared_ptr<EventFlagInfo> info;
{
std::lock_guard<std::mutex> mapLock(g_event_flag_map_mutex);
auto it = g_eventFlags.find(eid);
if (it == g_eventFlags.end())
{
setReturnS32(ctx, KE_UNKNOWN_EVFID);
return;
}
info = it->second;
g_eventFlags.erase(it);
}
if (!info)
{
setReturnS32(ctx, KE_UNKNOWN_EVFID);
return;
}
{
std::lock_guard<std::mutex> lock(info->m);
info->deleted = true;
}
info->cv.notify_all();
setReturnS32(ctx, 0);
}
void SetEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int eid = static_cast<int>(getRegU32(ctx, 4));
uint32_t bits = getRegU32(ctx, 5);
auto info = lookupEventFlagInfo(eid);
if (!info)
{
setReturnS32(ctx, KE_UNKNOWN_EVFID);
return;
}
if (bits == 0)
{
setReturnS32(ctx, KE_OK);
return;
}
{
std::lock_guard<std::mutex> lock(info->m);
info->bits |= bits;
}
info->cv.notify_all();
setReturnS32(ctx, 0);
}
void iSetEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
SetEventFlag(rdram, ctx, runtime);
}
void ClearEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int eid = static_cast<int>(getRegU32(ctx, 4));
uint32_t bits = getRegU32(ctx, 5);
auto info = lookupEventFlagInfo(eid);
if (!info)
{
setReturnS32(ctx, KE_UNKNOWN_EVFID);
return;
}
{
std::lock_guard<std::mutex> lock(info->m);
info->bits &= bits;
}
info->cv.notify_all();
setReturnS32(ctx, KE_OK);
}
void iClearEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
ClearEventFlag(rdram, ctx, runtime);
}
void WaitEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int eid = static_cast<int>(getRegU32(ctx, 4));
uint32_t waitBits = getRegU32(ctx, 5);
uint32_t mode = getRegU32(ctx, 6);
uint32_t resBitsAddr = getRegU32(ctx, 7);
if ((mode & ~WEF_MODE_MASK) != 0)
{
setReturnS32(ctx, KE_ILLEGAL_MODE);
return;
}
if (waitBits == 0)
{
setReturnS32(ctx, KE_EVF_ILPAT);
return;
}
auto info = lookupEventFlagInfo(eid);
if (!info)
{
setReturnS32(ctx, KE_UNKNOWN_EVFID);
return;
}
uint32_t *resBitsPtr = resBitsAddr ? reinterpret_cast<uint32_t *>(getMemPtr(rdram, resBitsAddr)) : nullptr;
std::unique_lock<std::mutex> lock(info->m);
if ((info->attr & EA_MULTI) == 0 && info->waiters > 0)
{
setReturnS32(ctx, KE_EVF_MULTI);
return;
}
auto tInfo = ensureCurrentThreadInfo(ctx);
throwIfTerminated(tInfo);
int ret = KE_OK;
auto satisfied = [&]()
{
if (tInfo && tInfo->forceRelease.load())
return true;
if (tInfo && tInfo->terminated.load())
return true;
if (info->deleted)
{
return true;
}
if (mode & WEF_OR)
{
return (info->bits & waitBits) != 0;
}
return (info->bits & waitBits) == waitBits;
};
if (!satisfied())
{
if (tInfo)
{
std::lock_guard<std::mutex> tLock(tInfo->m);
tInfo->status = THS_WAIT;
tInfo->waitType = TSW_EVENT;
tInfo->waitId = eid;
tInfo->forceRelease = false;
}
info->waiters++;
info->cv.wait(lock, satisfied);
info->waiters--;
if (tInfo)
{
std::lock_guard<std::mutex> tLock(tInfo->m);
tInfo->status = THS_RUN;
tInfo->waitType = TSW_NONE;
tInfo->waitId = 0;
if (tInfo->forceRelease)
{
tInfo->forceRelease = false;
ret = KE_RELEASE_WAIT;
}
}
if (tInfo && tInfo->terminated.load())
{
throw ThreadExitException();
}
}
if (ret == KE_OK && info->deleted)
{
ret = KE_WAIT_DELETE;
}
if (ret == KE_OK && resBitsPtr)
{
*resBitsPtr = info->bits;
}
if (ret == KE_OK)
{
if (resBitsPtr)
{
*resBitsPtr = info->bits;
}
if (mode & WEF_CLEAR_ALL)
{
info->bits = 0;
}
else if (mode & WEF_CLEAR)
{
info->bits &= ~waitBits;
}
}
lock.unlock();
waitWhileSuspended(tInfo);
setReturnS32(ctx, ret);
}
void PollEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int eid = static_cast<int>(getRegU32(ctx, 4));
uint32_t waitBits = getRegU32(ctx, 5);
uint32_t mode = getRegU32(ctx, 6);
uint32_t resBitsAddr = getRegU32(ctx, 7);
if ((mode & ~WEF_MODE_MASK) != 0)
{
setReturnS32(ctx, KE_ILLEGAL_MODE);
return;
}
if (waitBits == 0)
{
setReturnS32(ctx, KE_EVF_ILPAT);
return;
}
auto info = lookupEventFlagInfo(eid);
if (!info)
{
setReturnS32(ctx, KE_UNKNOWN_EVFID);
return;
}
uint32_t *resBitsPtr = resBitsAddr ? reinterpret_cast<uint32_t *>(getMemPtr(rdram, resBitsAddr)) : nullptr;
std::lock_guard<std::mutex> lock(info->m);
if ((info->attr & EA_MULTI) == 0 && info->waiters > 0)
{
setReturnS32(ctx, KE_EVF_MULTI);
return;
}
bool ok = false;
if (mode & WEF_OR)
{
ok = (info->bits & waitBits) != 0;
}
else
{
ok = (info->bits & waitBits) == waitBits;
}
if (!ok)
{
setReturnS32(ctx, KE_EVF_COND);
return;
}
if (resBitsPtr)
{
*resBitsPtr = info->bits;
}
if (mode & (WEF_CLEAR | WEF_CLEAR_ALL))
{
info->bits = 0;
}
setReturnS32(ctx, KE_OK);
}
void iPollEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
PollEventFlag(rdram, ctx, runtime);
}
void ReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int eid = static_cast<int>(getRegU32(ctx, 4));
uint32_t infoAddr = getRegU32(ctx, 5);
struct Ps2EventFlagInfo
{
uint32_t attr;
uint32_t option;
uint32_t initBits;
uint32_t currBits;
int32_t numThreads;
int32_t reserved1;
int32_t reserved2;
};
auto info = lookupEventFlagInfo(eid);
if (!info)
{
setReturnS32(ctx, KE_UNKNOWN_EVFID);
return;
}
Ps2EventFlagInfo *out = infoAddr ? reinterpret_cast<Ps2EventFlagInfo *>(getMemPtr(rdram, infoAddr)) : nullptr;
if (!out)
{
setReturnS32(ctx, -1);
return;
}
std::lock_guard<std::mutex> lock(info->m);
out->attr = info->attr;
out->option = info->option;
out->initBits = info->initBits;
out->currBits = info->bits;
out->numThreads = info->waiters;
out->reserved1 = 0;
out->reserved2 = 0;
setReturnS32(ctx, 0);
}
void iReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
ReferEventFlagStatus(rdram, ctx, runtime);
}
void SetAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint16_t ticks = static_cast<uint16_t>(getRegU32(ctx, 4) & 0xFFFFu);
uint32_t handler = getRegU32(ctx, 5);
uint32_t arg = getRegU32(ctx, 6);
static int logCount = 0;
if (logCount < 5)
{
std::cout << "[SetAlarm] ticks=" << ticks
<< " handler=0x" << std::hex << handler
<< " arg=0x" << arg << std::dec << std::endl;
++logCount;
}
if (!runtime || !handler || !runtime->hasFunction(handler))
{
setReturnS32(ctx, KE_ERROR);
return;
}
auto info = std::make_shared<AlarmInfo>();
info->ticks = ticks;
info->handler = handler;
info->commonArg = arg;
info->gp = getRegU32(ctx, 28);
info->sp = getRegU32(ctx, 29);
info->rdram = rdram;
info->runtime = runtime;
info->dueAt = std::chrono::steady_clock::now() + alarmTicksToDuration(ticks);
int alarmId = 0;
{
std::lock_guard<std::mutex> lock(g_alarm_mutex);
alarmId = g_nextAlarmId++;
if (g_nextAlarmId <= 0)
{
g_nextAlarmId = 1;
}
info->id = alarmId;
g_alarms[alarmId] = info;
}
ensureAlarmWorkerRunning();
g_alarm_cv.notify_all();
setReturnS32(ctx, alarmId);
}
void iSetAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
SetAlarm(rdram, ctx, runtime);
}
void CancelAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int alarmId = static_cast<int>(getRegU32(ctx, 4));
if (alarmId <= 0)
{
setReturnS32(ctx, KE_ERROR);
return;
}
bool removed = false;
{
std::lock_guard<std::mutex> lock(g_alarm_mutex);
removed = g_alarms.erase(alarmId) != 0;
}
if (removed)
{
g_alarm_cv.notify_all();
setReturnS32(ctx, KE_OK);
return;
}
setReturnS32(ctx, KE_ERROR);
}
void iCancelAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
CancelAlarm(rdram, ctx, runtime);
}
@@ -0,0 +1,105 @@
void EnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0);
}
void DisableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0);
}
void AddIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
IrqHandlerInfo info{};
info.cause = getRegU32(ctx, 4);
info.handler = getRegU32(ctx, 5);
info.arg = getRegU32(ctx, 6);
info.enabled = true;
const int handlerId = g_nextIntcHandlerId++;
g_intcHandlers[handlerId] = info;
setReturnS32(ctx, handlerId);
}
void RemoveIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
if (handlerId > 0)
{
g_intcHandlers.erase(handlerId);
}
setReturnS32(ctx, 0);
}
void AddDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
IrqHandlerInfo info{};
info.cause = getRegU32(ctx, 4);
info.handler = getRegU32(ctx, 5);
info.arg = getRegU32(ctx, 6);
info.enabled = true;
const int handlerId = g_nextDmacHandlerId++;
g_dmacHandlers[handlerId] = info;
setReturnS32(ctx, handlerId);
}
void RemoveDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
if (handlerId > 0)
{
g_dmacHandlers.erase(handlerId);
}
setReturnS32(ctx, 0);
}
void EnableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end())
{
it->second.enabled = true;
}
setReturnS32(ctx, 0);
}
void DisableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end())
{
it->second.enabled = false;
}
setReturnS32(ctx, 0);
}
void EnableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end())
{
it->second.enabled = true;
}
setReturnS32(ctx, 0);
}
void DisableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end())
{
it->second.enabled = false;
}
setReturnS32(ctx, 0);
}
void EnableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0);
}
void DisableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,347 @@
void GsSetCrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int interlaced = getRegU32(ctx, 4); // $a0 - 0=non-interlaced, 1=interlaced
int videoMode = getRegU32(ctx, 5); // $a1 - 0=NTSC, 1=PAL, 2=VESA, 3=HiVision
int frameMode = getRegU32(ctx, 6); // $a2 - 0=field, 1=frame
std::cout << "PS2 GsSetCrt: interlaced=" << interlaced
<< ", videoMode=" << videoMode
<< ", frameMode=" << frameMode << std::endl;
}
void GsGetIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint64_t imr = 0;
if (runtime)
{
imr = runtime->memory().gs().imr;
}
std::cout << "PS2 GsGetIMR: Returning IMR=0x" << std::hex << imr << std::dec << std::endl;
setReturnU64(ctx, imr); // Return in $v0/$v1
}
void GsPutIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint64_t newImr = getRegU32(ctx, 4) | ((uint64_t)getRegU32(ctx, 5) << 32); // $a0 = lower 32 bits, $a1 = upper 32 bits
uint64_t oldImr = 0;
if (runtime)
{
oldImr = runtime->memory().gs().imr;
runtime->memory().gs().imr = newImr;
}
std::cout << "PS2 GsPutIMR: Setting IMR=0x" << std::hex << newImr << std::dec << std::endl;
setReturnU64(ctx, oldImr);
}
void GsSetVideoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int mode = getRegU32(ctx, 4); // $a0 - video mode (various flags)
std::cout << "PS2 GsSetVideoMode: mode=0x" << std::hex << mode << std::dec << std::endl;
// Do nothing for now.
}
void GetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - pointer to parameter structure
if (!getMemPtr(rdram, paramAddr))
{
std::cerr << "PS2 GetOsdConfigParam error: Invalid parameter address: 0x"
<< std::hex << paramAddr << std::dec << std::endl;
setReturnS32(ctx, -1);
return;
}
uint32_t *param = reinterpret_cast<uint32_t *>(getMemPtr(rdram, paramAddr));
ensureOsdConfigInitialized();
uint32_t raw;
{
std::lock_guard<std::mutex> lock(g_osd_mutex);
raw = g_osd_config_raw;
}
*param = raw;
setReturnS32(ctx, 0);
}
void SetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - pointer to parameter structure
if (!getConstMemPtr(rdram, paramAddr))
{
std::cerr << "PS2 SetOsdConfigParam error: Invalid parameter address: 0x"
<< std::hex << paramAddr << std::dec << std::endl;
setReturnS32(ctx, -1);
return;
}
const uint32_t *param = reinterpret_cast<const uint32_t *>(getConstMemPtr(rdram, paramAddr));
uint32_t raw = param ? *param : 0;
raw = sanitizeOsdConfigRaw(raw);
{
std::lock_guard<std::mutex> lock(g_osd_mutex);
g_osd_config_raw = raw;
g_osd_config_initialized = true;
}
setReturnS32(ctx, 0);
}
void GetRomName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t bufAddr = getRegU32(ctx, 4); // $a0
size_t bufSize = getRegU32(ctx, 5); // $a1
char *hostBuf = reinterpret_cast<char *>(getMemPtr(rdram, bufAddr));
const char *romName = "ROMVER 0100";
if (!hostBuf)
{
std::cerr << "GetRomName error: Invalid buffer address" << std::endl;
setReturnS32(ctx, -1); // Error
return;
}
if (bufSize == 0)
{
setReturnS32(ctx, 0);
return;
}
strncpy(hostBuf, romName, bufSize - 1);
hostBuf[bufSize - 1] = '\0';
// returns the length of the string (excluding null?) or error
setReturnS32(ctx, (int32_t)strlen(hostBuf));
}
void SifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - path
const uint32_t secNameAddr = getRegU32(ctx, 5); // $a1 - section name ("all" typically)
const uint32_t execDataAddr = getRegU32(ctx, 6); // $a2 - t_ExecData*
std::string secName = readGuestCStringBounded(rdram, secNameAddr, kLoadfileArgMaxBytes);
if (secName.empty())
{
secName = "all";
}
const int32_t ret = runSifLoadElfPart(rdram, ctx, runtime, pathAddr, secName, execDataAddr);
setReturnS32(ctx, ret);
}
void sceSifLoadElf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - path
const uint32_t execDataAddr = getRegU32(ctx, 5); // $a1 - t_ExecData*
const int32_t ret = runSifLoadElfPart(rdram, ctx, runtime, pathAddr, "all", execDataAddr);
setReturnS32(ctx, ret);
}
void sceSifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
SifLoadElfPart(rdram, ctx, runtime);
}
void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
// Use the same tracker as SifLoadModule so both APIs return the same module IDs.
SifLoadModule(rdram, ctx, runtime);
}
void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t bufferAddr = getRegU32(ctx, 4); // $a0
if (!rdram || bufferAddr == 0u)
{
setReturnS32(ctx, -1);
return;
}
// Match buffer-based module loads to stable synthetic tags so module ID lookup remains deterministic.
const std::string moduleTag = makeSifModuleBufferTag(rdram, bufferAddr);
const int32_t moduleId = trackSifModuleLoad(moduleTag);
if (moduleId <= 0)
{
setReturnS32(ctx, -1);
return;
}
uint32_t refs = 0;
{
std::lock_guard<std::mutex> lock(g_sif_module_mutex);
auto it = g_sif_modules_by_id.find(moduleId);
if (it != g_sif_modules_by_id.end())
{
refs = it->second.refCount;
}
}
logSifModuleAction("load-buffer", moduleId, moduleTag, refs);
setReturnS32(ctx, moduleId);
}
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId)
{
// a bit more detail mayber reomve old logic, lets get it more raw
std::cerr << "[Syscall TODO]"
<< " encoded=0x" << std::hex << encodedSyscallId
<< " v1=0x" << getRegU32(ctx, 3)
<< " v0=0x" << getRegU32(ctx, 2)
<< " a0=0x" << getRegU32(ctx, 4)
<< " a1=0x" << getRegU32(ctx, 5)
<< " a2=0x" << getRegU32(ctx, 6)
<< " a3=0x" << getRegU32(ctx, 7)
<< " pc=0x" << ctx->pc
<< std::dec << std::endl;
const uint32_t v0 = getRegU32(ctx, 2);
const uint32_t v1 = getRegU32(ctx, 3);
const uint32_t caller_ra = getRegU32(ctx, 31);
uint32_t syscallId = encodedSyscallId;
if (syscallId == 0u)
{
syscallId = (v0 != 0u) ? v0 : v1;
}
std::cerr << "Warning: Unimplemented PS2 syscall called. PC=0x" << std::hex << ctx->pc
<< ", RA=0x" << caller_ra
<< ", Encoded=0x" << encodedSyscallId
<< ", v0=0x" << v0
<< ", v1=0x" << v1
<< ", Chosen=0x" << syscallId
<< std::dec << std::endl;
std::cerr << " Args: $a0=0x" << std::hex << getRegU32(ctx, 4)
<< ", $a1=0x" << getRegU32(ctx, 5)
<< ", $a2=0x" << getRegU32(ctx, 6)
<< ", $a3=0x" << getRegU32(ctx, 7) << std::dec << std::endl;
// Common syscalls:
// 0x04: Exit
// 0x06: LoadExecPS2
// 0x07: ExecPS2
if (syscallId == 0x04u)
{
std::cerr << " -> Syscall is Exit(), calling ExitThread stub." << std::endl;
ExitThread(rdram, ctx, runtime);
return;
}
static std::mutex s_unknownMutex;
static std::unordered_map<uint32_t, uint64_t> s_unknownCounts;
{
std::lock_guard<std::mutex> lock(s_unknownMutex);
const uint64_t count = ++s_unknownCounts[syscallId];
if (count == 1 || (count % 5000u) == 0u)
{
std::cerr << " -> Unknown syscallId=0x" << std::hex << syscallId
<< " hits=" << std::dec << count << std::endl;
}
}
// Bootstrap default: avoid hard-failing loops that probe syscall availability.
setReturnS32(ctx, 0);
}
// 0x3C SetupThread: returns stack pointer (stack + stack_size)
// args: $a0 = stack base, $a1 = stack size, $a2 = gp, $a3 = entry point
void SetupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t stackBase = getRegU32(ctx, 4);
uint32_t stackSize = getRegU32(ctx, 5);
uint32_t sp = stackBase + stackSize;
setReturnS32(ctx, sp);
}
// 0x3D SetupHeap: returns heap base/start pointer
void SetupHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t heapBase = getRegU32(ctx, 4); // $a0
const uint32_t heapSize = getRegU32(ctx, 5); // $a1 (optional size)
if (runtime)
{
uint32_t heapLimit = PS2_RAM_SIZE;
if (heapSize != 0u && heapBase < PS2_RAM_SIZE)
{
const uint64_t candidateLimit = static_cast<uint64_t>(heapBase) + static_cast<uint64_t>(heapSize);
heapLimit = static_cast<uint32_t>(std::min<uint64_t>(candidateLimit, PS2_RAM_SIZE));
}
runtime->configureGuestHeap(heapBase, heapLimit);
setReturnU32(ctx, runtime->guestHeapBase());
return;
}
setReturnU32(ctx, heapBase);
}
// 0x3E EndOfHeap: commonly returns current heap end; keep it stable for now.
void EndOfHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
if (runtime)
{
setReturnU32(ctx, runtime->guestHeapEnd());
return;
}
setReturnU32(ctx, getRegU32(ctx, 4));
}
// 0x5A QueryBootMode (stub): return 0 for now
void QueryBootMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t mode = getRegU32(ctx, 4);
ensureBootModeTable(rdram);
uint32_t addr = 0;
{
std::lock_guard<std::mutex> lock(g_bootmode_mutex);
auto it = g_bootmode_addresses.find(static_cast<uint8_t>(mode));
if (it != g_bootmode_addresses.end())
addr = it->second;
}
setReturnU32(ctx, addr);
}
// 0x5B GetThreadTLS (stub): return 0
void GetThreadTLS(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
auto info = ensureCurrentThreadInfo(ctx);
if (!info)
{
setReturnU32(ctx, 0);
return;
}
if (info->tlsBase == 0)
{
info->tlsBase = allocTlsAddr(rdram);
}
setReturnU32(ctx, info->tlsBase);
}
// 0x74 RegisterExitHandler (stub): return 0
void RegisterExitHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t func = getRegU32(ctx, 4);
uint32_t arg = getRegU32(ctx, 5);
if (func == 0)
{
setReturnS32(ctx, -1);
return;
}
int tid = g_currentThreadId;
{
std::lock_guard<std::mutex> lock(g_exit_handler_mutex);
g_exit_handlers[tid].push_back({func, arg});
}
setReturnS32(ctx, 0);
}
@@ -0,0 +1,764 @@
static void applySuspendStatusLocked(ThreadInfo &info)
{
if (info.waitType != TSW_NONE)
{
info.status = THS_WAITSUSPEND;
}
else
{
info.status = THS_SUSPEND;
}
}
static void runExitHandlersForThread(int tid, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
if (!runtime || !ctx)
return;
std::vector<ExitHandlerEntry> handlers;
{
std::lock_guard<std::mutex> lock(g_exit_handler_mutex);
auto it = g_exit_handlers.find(tid);
if (it == g_exit_handlers.end())
return;
handlers = std::move(it->second);
g_exit_handlers.erase(it);
}
for (const auto &handler : handlers)
{
if (!handler.func)
continue;
try
{
rpcInvokeFunction(rdram, ctx, runtime, handler.func, handler.arg, 0, 0, 0, nullptr);
}
catch (const ThreadExitException &)
{
// ignore
}
catch (const std::exception &)
{
}
}
}
void FlushCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0);
}
void ResetEE(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
std::cerr << "Syscall: ResetEE - Halting Execution (Not fully implemented)" << std::endl;
exit(0); // Should we exit or just halt the execution?
}
void SetMemoryMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0);
}
void CreateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t paramAddr = getRegU32(ctx, 4); // $a0 points to ThreadParam
const uint32_t *param = reinterpret_cast<const uint32_t *>(getConstMemPtr(rdram, paramAddr));
if (!param)
{
std::cerr << "CreateThread error: invalid ThreadParam address 0x" << std::hex << paramAddr << std::dec << std::endl;
setReturnS32(ctx, -1);
return;
}
auto info = std::make_shared<ThreadInfo>();
info->attr = param[0];
info->entry = param[1];
info->stack = param[2];
info->stackSize = param[3];
auto looksLikeGuestPtr = [](uint32_t v) -> bool
{
if (v == 0)
{
return true;
}
const uint32_t norm = v & 0x1FFFFFFFu;
return norm < PS2_RAM_SIZE && norm >= 0x10000u;
};
auto looksLikePriority = [](uint32_t v) -> bool
{
// Typical EE priorities are very small integers (1..127).
return v <= 0x400u;
};
const uint32_t gpA = param[4];
const uint32_t prioA = param[5];
const uint32_t gpB = param[5];
const uint32_t prioB = param[4];
// Prefer the standard EE layout (gp at +0x10, priority at +0x14),
// but keep a fallback for callsites that used the swapped decode.
if (looksLikeGuestPtr(gpA) && looksLikePriority(prioA))
{
info->gp = gpA;
info->priority = prioA;
}
else if (looksLikeGuestPtr(gpB) && looksLikePriority(prioB))
{
info->gp = gpB;
info->priority = prioB;
}
else
{
info->gp = gpA;
info->priority = prioA;
}
info->option = param[6];
info->currentPriority = static_cast<int>(info->priority);
int id = 0;
{
std::lock_guard<std::mutex> lock(g_thread_map_mutex);
id = g_nextThreadId++;
g_threads[id] = info;
}
std::cout << "[CreateThread] id=" << id
<< " entry=0x" << std::hex << info->entry
<< " stack=0x" << info->stack
<< " size=0x" << info->stackSize
<< " gp=0x" << info->gp
<< " prio=" << std::dec << info->priority << std::endl;
setReturnS32(ctx, id);
}
void DeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int tid = static_cast<int>(getRegU32(ctx, 4)); // $a0
auto info = lookupThreadInfo(tid);
if (!info)
{
setReturnS32(ctx, KE_UNKNOWN_THID);
return;
}
{
std::lock_guard<std::mutex> lock(info->m);
if (info->status != THS_DORMANT)
{
setReturnS32(ctx, KE_NOT_WAIT); // for now
return;
}
}
{
std::lock_guard<std::mutex> lock(g_thread_map_mutex);
g_threads.erase(tid);
}
setReturnS32(ctx, KE_OK);
}
void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int tid = static_cast<int>(getRegU32(ctx, 4)); // $a0 = thread id
uint32_t arg = getRegU32(ctx, 5); // $a1 = user arg
auto info = lookupThreadInfo(tid);
if (!info)
{
std::cerr << "StartThread error: unknown thread id " << tid << std::endl;
setReturnS32(ctx, -1);
return;
}
{
std::lock_guard<std::mutex> lock(info->m);
if (info->started)
{
setReturnS32(ctx, tid); // Already started
return;
}
info->started = true;
info->status = THS_RUN;
info->arg = arg;
}
if (!runtime->hasFunction(info->entry))
{
std::cerr << "[StartThread] entry 0x" << std::hex << info->entry << std::dec << " is not registered" << std::endl;
setReturnS32(ctx, -1);
return;
}
const uint32_t callerSp = getRegU32(ctx, 29);
const uint32_t callerGp = getRegU32(ctx, 28);
{
std::lock_guard<std::mutex> lock(info->m);
if (info->stack == 0 && info->stackSize != 0)
{
const uint32_t autoStack = runtime->guestMalloc(info->stackSize, 16u);
if (autoStack != 0)
{
info->stack = autoStack;
std::cout << "[StartThread] id=" << tid
<< " auto-stack=0x" << std::hex << autoStack
<< " size=0x" << info->stackSize << std::dec << std::endl;
}
}
if (info->stack != 0 && info->stackSize == 0)
{
// Some games leave size zero in the thread param even though a stack
// buffer is supplied; use a conservative default instead of caller SP.
info->stackSize = 0x800u;
}
}
g_activeThreads.fetch_add(1, std::memory_order_relaxed);
std::thread([=]() mutable
{
{
std::string name = "PS2Thread_" + std::to_string(tid);
ThreadNaming::SetCurrentThreadName(name);
}
R5900Context threadCtxCopy{};
R5900Context *threadCtx = &threadCtxCopy;
uint32_t threadSp = callerSp;
if (info->stack)
{
const uint32_t stackSize = (info->stackSize != 0) ? info->stackSize : 0x800u;
threadSp = (info->stack + stackSize) & ~0xFu;
}
uint32_t threadGp = info->gp;
const uint32_t normalizedGp = threadGp & 0x1FFFFFFFu;
if (threadGp == 0 || normalizedGp < 0x10000u || normalizedGp >= PS2_RAM_SIZE)
{
threadGp = callerGp;
}
SET_GPR_U32(threadCtx, 29, threadSp);
SET_GPR_U32(threadCtx, 28, threadGp);
SET_GPR_U32(threadCtx, 4, info->arg);
SET_GPR_U32(threadCtx, 31, 0);
threadCtx->pc = info->entry;
PS2Runtime::RecompiledFunction func = runtime->lookupFunction(info->entry);
g_currentThreadId = tid;
std::cout << "[StartThread] id=" << tid
<< " entry=0x" << std::hex << info->entry
<< " sp=0x" << GPR_U32(threadCtx, 29)
<< " gp=0x" << GPR_U32(threadCtx, 28)
<< " arg=0x" << info->arg << std::dec << std::endl;
bool exited = false;
try
{
func(rdram, threadCtx, runtime);
}
catch (const ThreadExitException &)
{
exited = true;
}
catch (const std::exception &e)
{
std::cerr << "[StartThread] id=" << tid << " exception: " << e.what() << std::endl;
}
if (!exited)
{
std::cout << "[StartThread] id=" << tid << " returned (pc=0x"
<< std::hex << threadCtx->pc << std::dec << ")" << std::endl;
}
runExitHandlersForThread(tid, rdram, threadCtx, runtime);
{
std::lock_guard<std::mutex> lock(info->m);
info->started = false;
info->status = THS_DORMANT;
}
g_activeThreads.fetch_sub(1, std::memory_order_relaxed); })
.detach();
// for now report success to the caller.
setReturnS32(ctx, 0);
}
void ExitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
runExitHandlersForThread(g_currentThreadId, rdram, ctx, runtime);
auto info = ensureCurrentThreadInfo(ctx);
if (info)
{
std::lock_guard<std::mutex> lock(info->m);
info->terminated = true;
info->forceRelease = true;
info->status = THS_DORMANT;
info->waitType = TSW_NONE;
info->waitId = 0;
info->wakeupCount = 0;
}
if (info)
{
info->cv.notify_all();
}
throw ThreadExitException();
}
void ExitDeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int tid = g_currentThreadId;
runExitHandlersForThread(tid, rdram, ctx, runtime);
auto info = ensureCurrentThreadInfo(ctx);
if (info)
{
std::lock_guard<std::mutex> lock(info->m);
info->terminated = true;
info->forceRelease = true;
info->status = THS_DORMANT;
info->waitType = TSW_NONE;
info->waitId = 0;
info->wakeupCount = 0;
}
if (info)
{
info->cv.notify_all();
}
{
std::lock_guard<std::mutex> lock(g_thread_map_mutex);
g_threads.erase(tid);
}
throw ThreadExitException();
}
void TerminateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int tid = static_cast<int>(getRegU32(ctx, 4));
if (tid == 0)
tid = g_currentThreadId;
auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid);
if (!info)
{
setReturnS32(ctx, -1);
return;
}
{
std::lock_guard<std::mutex> lock(info->m);
info->terminated = true;
info->forceRelease = true;
info->status = THS_DORMANT;
info->waitType = TSW_NONE;
info->waitId = 0;
info->wakeupCount = 0;
}
info->cv.notify_all();
if (tid == g_currentThreadId)
{
runExitHandlersForThread(tid, rdram, ctx, runtime);
throw ThreadExitException();
}
setReturnS32(ctx, 0);
}
void SuspendThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int tid = static_cast<int>(getRegU32(ctx, 4));
if (tid == 0)
tid = g_currentThreadId;
auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid);
if (!info)
{
setReturnS32(ctx, -1);
return;
}
{
std::lock_guard<std::mutex> lock(info->m);
if (info->status == THS_DORMANT)
{
setReturnS32(ctx, -1);
return;
}
info->suspendCount++;
applySuspendStatusLocked(*info);
}
info->cv.notify_all();
if (tid == g_currentThreadId)
{
std::unique_lock<std::mutex> lock(info->m);
info->cv.wait(lock, [&]()
{ return info->suspendCount == 0 || info->terminated.load(); });
if (info->terminated.load())
{
throw ThreadExitException();
}
info->status = THS_RUN;
}
setReturnS32(ctx, 0);
}
void ResumeThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int tid = static_cast<int>(getRegU32(ctx, 4));
if (tid == 0)
tid = g_currentThreadId;
auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid);
if (!info)
{
setReturnS32(ctx, -1);
return;
}
{
std::lock_guard<std::mutex> lock(info->m);
if (info->suspendCount <= 0)
{
setReturnS32(ctx, -1);
return;
}
info->suspendCount--;
if (info->suspendCount == 0)
{
if (info->waitType != TSW_NONE)
{
info->status = THS_WAIT;
}
else
{
info->status = (tid == g_currentThreadId) ? THS_RUN : THS_READY;
}
}
}
info->cv.notify_all();
setReturnS32(ctx, 0);
}
void GetThreadId(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, g_currentThreadId);
}
void ReferThreadStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int tid = static_cast<int>(getRegU32(ctx, 4));
uint32_t statusAddr = getRegU32(ctx, 5);
if (tid == 0) // TH_SELF
{
tid = g_currentThreadId;
}
auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid);
if (!info)
{
setReturnS32(ctx, -1);
return;
}
ee_thread_status_t *status = reinterpret_cast<ee_thread_status_t *>(getMemPtr(rdram, statusAddr));
if (!status)
{
setReturnS32(ctx, -1);
return;
}
std::lock_guard<std::mutex> lock(info->m);
status->status = info->status;
status->func = info->entry;
status->stack = info->stack;
status->stack_size = info->stackSize;
status->gp_reg = info->gp;
status->initial_priority = info->priority;
status->current_priority = info->currentPriority;
status->attr = info->attr;
status->option = info->option;
status->waitType = info->waitType;
status->waitId = info->waitId;
status->wakeupCount = info->wakeupCount;
setReturnS32(ctx, 0);
}
void SleepThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
auto info = ensureCurrentThreadInfo(ctx);
if (!info)
{
setReturnS32(ctx, KE_UNKNOWN_THID);
return;
}
throwIfTerminated(info);
int ret = 0;
std::unique_lock<std::mutex> lock(info->m);
if (info->wakeupCount > 0)
{
info->wakeupCount--;
info->status = THS_RUN;
info->waitType = TSW_NONE;
info->waitId = 0;
ret = 0;
}
else
{
info->status = THS_WAIT;
info->waitType = TSW_SLEEP;
info->waitId = 0;
info->forceRelease = false;
info->cv.wait(lock, [&]()
{ return info->wakeupCount > 0 || info->forceRelease.load() || info->terminated.load(); });
if (info->terminated.load())
{
throw ThreadExitException();
}
info->status = THS_RUN;
info->waitType = TSW_NONE;
info->waitId = 0;
if (info->forceRelease.load())
{
info->forceRelease = false;
ret = KE_RELEASE_WAIT;
}
else
{
if (info->wakeupCount > 0)
info->wakeupCount--;
ret = 0;
}
}
lock.unlock();
waitWhileSuspended(info);
setReturnS32(ctx, ret);
}
void WakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int tid = static_cast<int>(getRegU32(ctx, 4));
if (tid == 0)
{
setReturnS32(ctx, KE_ILLEGAL_THID);
return;
}
auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid);
if (!info)
{
setReturnS32(ctx, KE_UNKNOWN_THID);
return;
}
{
std::lock_guard<std::mutex> lock(info->m);
if (info->status == THS_DORMANT)
{
setReturnS32(ctx, KE_DORMANT);
return;
}
if (info->status == THS_WAIT && info->waitType == TSW_SLEEP)
{
if (info->suspendCount > 0)
{
info->status = THS_SUSPEND;
}
else
{
info->status = THS_READY;
}
info->waitType = TSW_NONE;
info->waitId = 0;
info->wakeupCount++;
info->cv.notify_one();
}
else
{
info->wakeupCount++;
}
}
setReturnS32(ctx, 0);
}
void iWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
WakeupThread(rdram, ctx, runtime);
}
void CancelWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int tid = static_cast<int>(getRegU32(ctx, 4));
if (tid == 0)
tid = g_currentThreadId;
auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid);
if (!info)
{
setReturnS32(ctx, -1);
return;
}
int previous = 0;
{
std::lock_guard<std::mutex> lock(info->m);
previous = info->wakeupCount;
info->wakeupCount = 0;
}
setReturnS32(ctx, previous);
}
void iCancelWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int tid = static_cast<int>(getRegU32(ctx, 4));
if (tid == 0)
{
setReturnS32(ctx, KE_ILLEGAL_THID);
return;
}
auto info = lookupThreadInfo(tid);
if (!info)
{
setReturnS32(ctx, KE_UNKNOWN_THID);
return;
}
int previous = 0;
{
std::lock_guard<std::mutex> lock(info->m);
previous = info->wakeupCount;
info->wakeupCount = 0;
}
setReturnS32(ctx, previous);
}
void ChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int tid = static_cast<int>(getRegU32(ctx, 4));
int newPrio = static_cast<int>(getRegU32(ctx, 5));
if (tid == 0)
tid = g_currentThreadId;
auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid);
if (info)
{
int oldPrio = info->currentPriority;
info->currentPriority = newPrio;
setReturnS32(ctx, oldPrio); // Return old priority?
}
else
{
setReturnS32(ctx, -1);
}
}
void RotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
int prio = static_cast<int>(getRegU32(ctx, 4));
if (logCount < 16)
{
std::cout << "[RotateThreadReadyQueue] prio=" << prio << std::endl;
++logCount;
}
if (prio >= 128)
{
setReturnS32(ctx, -1);
return;
}
setReturnS32(ctx, 0);
}
void ReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int tid = static_cast<int>(getRegU32(ctx, 4));
if (tid == 0)
{
setReturnS32(ctx, KE_ILLEGAL_THID);
return;
}
auto info = lookupThreadInfo(tid);
if (!info)
{
setReturnS32(ctx, KE_UNKNOWN_THID);
return;
}
bool wasWaiting = false;
int waitType = 0;
int waitId = 0;
{
std::lock_guard<std::mutex> lock(info->m);
if (info->status == THS_WAIT)
{
wasWaiting = true;
waitType = info->waitType;
waitId = info->waitId;
info->forceRelease = true;
info->waitType = TSW_NONE;
info->waitId = 0;
if (info->suspendCount > 0)
{
info->status = THS_SUSPEND;
}
else
{
info->status = THS_READY;
}
}
}
if (!wasWaiting)
{
setReturnS32(ctx, KE_NOT_WAIT);
return;
}
info->cv.notify_all();
if (waitType == TSW_SEMA)
{
auto sema = lookupSemaInfo(waitId);
if (sema)
{
sema->cv.notify_all();
}
}
else if (waitType == TSW_EVENT)
{
auto eventFlag = lookupEventFlagInfo(waitId);
if (eventFlag)
{
eventFlag->cv.notify_all();
}
}
setReturnS32(ctx, 0);
}
void iReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
ReleaseWaitThread(rdram, ctx, runtime);
}
+10 -1
View File
@@ -1,6 +1,7 @@
#include "MiniTest.h"
#include "ps2recomp/code_generator.h"
#include "ps2recomp/instructions.h"
#include "ps2recomp/ps2_recompiler.h"
#include "ps2recomp/types.h"
#include <filesystem>
#include <fstream>
@@ -612,7 +613,15 @@ void register_code_generator_tests()
t.IsTrue(generated.find("switch (jumpTarget)") != std::string::npos, "JR $31 should emit switch for internal targets");
t.IsTrue(generated.find("case 0x1308u: goto label_1308;") != std::string::npos, "switch should include return address from internal JAL");
});
tc.Run("resolveStubTarget allows leading underscore alias", [](TestCase &t) {
t.Equals(PS2Recompiler::resolveStubTarget("_rand"), StubTarget::Stub,
"_rand should resolve via rand stub alias");
t.Equals(PS2Recompiler::resolveStubTarget("_GetThreadId"), StubTarget::Syscall,
"_GetThreadId should resolve via GetThreadId syscall alias");
t.Equals(PS2Recompiler::resolveStubTarget("_DefinitelyNotARealCall"), StubTarget::Unknown,
"unknown names must still stay unknown");
});
});
}