mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-27 17:11:31 -04:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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 ®ion);
|
||||
void markModified(uint32_t address, uint32_t size);
|
||||
bool isScratchpad(uint32_t address) const;
|
||||
};
|
||||
|
||||
#endif // PS2_MEMORY_H
|
||||
@@ -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 ®ion);
|
||||
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
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 §ionName,
|
||||
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 §ionName,
|
||||
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);
|
||||
}
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user