Feature/runtime gs recompiler instructions (#73)

* feat: basic gs
feat: basic rasterizer
fix: a lot of fixes to runtime stubs
feat: basic vif intercepter
feat: return "ok" for some stubs
feat: disassembly code as comment
fix: fix code gen instructions set
fix: again jump
feat: remove unused macro
fix: fix some problematic macros
feat: track delayslots on runtime
and many more

* feat: added missing files

* fix: fix instruction test
This commit is contained in:
Ranieri
2026-02-22 02:20:42 -03:00
committed by GitHub
parent 130b8d2583
commit 8584c0613a
26 changed files with 3849 additions and 679 deletions
+26 -4
View File
@@ -14,13 +14,14 @@ namespace ps2recomp
struct Instruction;
struct Function;
struct Symbol;
struct Section;
extern const std::unordered_set<std::string> kKeywords;
class CodeGenerator
{
public:
explicit CodeGenerator(const std::vector<Symbol> &symbols);
explicit CodeGenerator(const std::vector<Symbol> &symbols, const std::vector<Section> &sections);
~CodeGenerator();
struct BootstrapInfo
@@ -33,21 +34,28 @@ namespace ps2recomp
std::string entryName;
};
struct AnalysisResult {
std::unordered_set<uint32_t> entryPoints;
std::unordered_map<uint32_t, std::vector<uint32_t>> jumpTableTargets;
};
std::string generateFunction(const Function &function, const std::vector<Instruction> &instructions, const bool &useHeaders);
std::string generateFunctionRegistration(const std::vector<Function> &functions, const std::map<uint32_t, std::string> &stubs);
std::string handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot,
const Function &function, const std::unordered_set<uint32_t> &internalTargets);
const Function &function, const AnalysisResult &analysisResult);
void setRenamedFunctions(const std::unordered_map<uint32_t, std::string> &renames);
void setBootstrapInfo(const BootstrapInfo &info);
void setRelocationCallNames(const std::unordered_map<uint32_t, std::string> &callNames);
std::unordered_set<uint32_t> collectInternalBranchTargets(const Function &function,
const std::vector<Instruction> &instructions);
AnalysisResult collectInternalBranchTargets(const Function &function,
const std::vector<Instruction> &instructions);
public:
std::unordered_map<uint32_t, Symbol> m_symbols;
std::unordered_map<uint32_t, std::string> m_renamedFunctions;
std::unordered_map<uint32_t, std::string> m_relocationCallNames;
const std::vector<Section>& m_sections;
BootstrapInfo m_bootstrapInfo;
std::string translateInstruction(const Instruction &inst);
@@ -69,6 +77,14 @@ namespace ps2recomp
// Instruction Helpers
std::string translateQFSRV(const Instruction &inst);
std::string translatePMADDW(const Instruction &inst);
std::string translatePMULTW(const Instruction &inst);
std::string translatePMSUBW(const Instruction &inst);
std::string translatePEXT5(const Instruction &inst);
std::string translatePPAC5(const Instruction &inst);
std::string translatePADSBH(const Instruction &inst);
std::string translatePMSUBH(const Instruction &inst);
std::string translatePHMSBH(const Instruction &inst);
std::string translatePMADDUW(const Instruction &inst);
std::string translatePDIVW(const Instruction &inst);
std::string translatePCPYLD(const Instruction &inst);
std::string translatePMADDH(const Instruction &inst);
@@ -149,6 +165,12 @@ namespace ps2recomp
std::string translateVU_VMSUB(const Instruction &inst);
std::string translateVU_VMSUBq(const Instruction &inst);
std::string translateVU_VMSUBi(const Instruction &inst);
std::string translateVU_VMULq(const Instruction &inst);
std::string translateVU_VMULi(const Instruction &inst);
std::string translateVU_VADDq(const Instruction &inst);
std::string translateVU_VADDi(const Instruction &inst);
std::string translateVU_VSUBq(const Instruction &inst);
std::string translateVU_VSUBi(const Instruction &inst);
std::string translateVU_VITOF(const Instruction &inst, int shift);
std::string translateVU_VFTOI(const Instruction &inst, int shift);
std::string translateVU_VLQI(const Instruction &inst);
+1
View File
@@ -45,6 +45,7 @@ namespace ps2recomp
bool isMmio = false;
uint32_t mmioAddress = 0;
std::string disassembly;
struct
{
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -816,7 +816,7 @@ namespace ps2recomp
<< m_relocations.size() << " relocations." << std::endl;
m_decoder = std::make_unique<R5900Decoder>();
m_codeGenerator = std::make_unique<CodeGenerator>(m_symbols);
m_codeGenerator = std::make_unique<CodeGenerator>(m_symbols, m_sections);
std::unordered_map<uint32_t, std::string> relocationCallNames;
relocationCallNames.reserve(m_relocations.size());
for (const auto &reloc : m_relocations)
+8
View File
@@ -168,6 +168,14 @@ namespace ps2recomp
inst.vectorInfo.isVector = inst.isVU; // Only VU ops are truly vector
}
size_t bufferSize = RabbitizerInstruction_getSizeForBuffer(&rabbitizerInst, 0, 0);
if (bufferSize > 0)
{
std::vector<char> buffer(bufferSize + 1, '\0');
RabbitizerInstruction_disassemble(&rabbitizerInst, buffer.data(), nullptr, 0, 0);
inst.disassembly = buffer.data();
}
RabbitizerInstructionR5900_destroy(&rabbitizerInst);
return inst;
+3
View File
@@ -20,10 +20,13 @@ FetchContent_MakeAvailable(raylib)
add_library(ps2_runtime STATIC
src/lib/game_overrides.cpp
src/lib/ps2_gs_gpu.cpp
src/lib/ps2_gs_rasterizer.cpp
src/lib/ps2_memory.cpp
src/lib/ps2_runtime.cpp
src/lib/ps2_stubs.cpp
src/lib/ps2_syscalls.cpp
src/lib/ps2_vif1_interpreter.cpp
)
file(GLOB RUNNER_SRC_FILES CONFIGURE_DEPENDS
+62
View File
@@ -0,0 +1,62 @@
#ifndef PS2_GS_GPU_H
#define PS2_GS_GPU_H
#include <cstdint>
#include <vector>
#include <mutex>
#include <atomic>
enum GsGpuPrimType : uint8_t
{
GS_GPU_POINT = 0,
GS_GPU_LINE = 1,
GS_GPU_TRIANGLE = 2,
GS_GPU_QUAD = 3,
};
struct GsGpuVertex
{
float x, y, z; // screen-space position (after PS2 12.4 fixed → float)
uint8_t r, g, b, a; // vertex color
float u, v; // texture coords (for future use)
};
struct GsGpuPrimitive
{
GsGpuPrimType type;
uint8_t vertexCount; // 1 (point), 2 (line), 3 (tri), 4 (quad)
GsGpuVertex verts[4];
};
class GsGpuFrameData
{
public:
GsGpuFrameData();
void pushPrimitive(const GsGpuPrimitive &prim);
const std::vector<GsGpuPrimitive> &swapAndGetFront();
bool hasGpuPrimitives() const;
void setScreenSize(uint32_t w, uint32_t h)
{
m_screenW = w;
m_screenH = h;
}
uint32_t screenWidth() const { return m_screenW; }
uint32_t screenHeight() const { return m_screenH; }
private:
std::vector<GsGpuPrimitive> m_buffers[2];
int m_backIdx = 0; // index into m_buffers for the current write target
mutable std::mutex m_mutex;
std::atomic<bool> m_hasData{false};
uint32_t m_screenW = 640;
uint32_t m_screenH = 448;
};
GsGpuFrameData &gsGpuGetFrameData();
bool gsGpuRenderFrame();
#endif // PS2_GS_GPU_H
+36 -9
View File
@@ -6,13 +6,14 @@
#include <vector>
#include <unordered_map>
#include <atomic>
#include <iostream>
#if defined(_MSC_VER)
#include <intrin.h>
#include <intrin.h>
#elif defined(USE_SSE2NEON)
#include "sse2neon.h"
#include "sse2neon.h"
#else
#include <immintrin.h> // For SSE/AVX instructions
#include <smmintrin.h> // For SSE4.1 instructions
#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
@@ -96,11 +97,6 @@ inline bool ps2ResolveGuestPointer(uint32_t addr, uint32_t &offset, bool &scratc
{
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)
{
@@ -271,6 +267,34 @@ public:
bool writeIORegister(uint32_t address, uint32_t value);
uint32_t readIORegister(uint32_t address);
// Software GS/VIF path used by GIF and VIF1 DMA channels.
void processGIFPacket(uint32_t srcPhysAddr, uint32_t qwCount);
void processVIF1Data(uint32_t srcPhysAddr, uint32_t sizeBytes);
// Poll DMA registers from rdram shadow (workaround for KSEG1 fast-path bypass)
int pollDmaRegisters();
struct GSDrawContext
{
uint64_t bitbltbuf = 0;
uint64_t trxpos = 0;
uint64_t trxreg = 0;
uint64_t trxdir = 0;
bool xferActive = false;
uint32_t xferDestX = 0;
uint32_t xferDestY = 0;
uint32_t xferWidth = 0;
uint32_t xferHeight = 0;
uint32_t xferDBP = 0;
uint32_t xferDBW = 0;
uint32_t xferDPSM = 0;
uint32_t xferPixelsWritten = 0;
uint32_t gifTagsProcessed = 0;
uint32_t adWrites = 0;
uint32_t imageTransfers = 0;
uint32_t primitivesDrawn = 0;
};
// Track code modifications for self-modifying code
void registerCodeRegion(uint32_t start, uint32_t end);
bool isCodeModified(uint32_t address, uint32_t size);
@@ -281,6 +305,8 @@ public:
const GSRegisters &gs() const { return gs_regs; }
uint8_t *getGSVRAM() { return m_gsVRAM; }
const uint8_t *getGSVRAM() const { return m_gsVRAM; }
GSDrawContext &gsDrawCtx() { return m_gsDrawCtx; }
const GSDrawContext &gsDrawCtx() const { return m_gsDrawCtx; }
bool hasSeenGifCopy() const { return m_seenGifCopy; }
// Main RAM (32MB)
uint8_t *m_rdram;
@@ -301,6 +327,7 @@ public:
// Registers
GSRegisters gs_regs;
GSDrawContext m_gsDrawCtx;
uint8_t *m_gsVRAM;
VIFRegisters vif0_regs;
VIFRegisters vif1_regs;
+34 -28
View File
@@ -8,12 +8,12 @@
#include <string>
#include <functional>
#if defined(_MSC_VER)
#include <intrin.h>
#include <intrin.h>
#elif defined(USE_SSE2NEON)
#include "sse2neon.h"
#include "sse2neon.h"
#else
#include <immintrin.h> // For SSE/AVX instructions
#include <smmintrin.h> // For SSE4.1 instructions
#include <immintrin.h> // For SSE/AVX instructions
#include <smmintrin.h> // For SSE4.1 instructions
#endif
#include <atomic>
#include <mutex>
@@ -75,6 +75,7 @@ struct alignas(16) R5900Context
uint32_t vu0_fbrst3; // FBRST3
uint32_t vu0_fbrst4; // FBRST4
uint32_t vu0_itop;
uint32_t vu0_top;
uint32_t vu0_info;
uint32_t vu0_xitop; // VU0 XITOP - input ITOP for VIF/VU sync
uint32_t vu0_pc;
@@ -109,6 +110,10 @@ struct alignas(16) R5900Context
uint32_t llbit;
uint32_t lladdr;
// Delay slot state tracking
bool in_delay_slot;
uint32_t branch_pc;
// COP2 control registers (VU0 integer + control)
uint32_t cop2_ccr[32];
@@ -130,6 +135,9 @@ struct alignas(16) R5900Context
// 0x00000000 = Normal mode (after BIOS handoff).
cop0_status = 0x00000000;
cop0_prid = 0x00002e20; // CPU ID for R5900
in_delay_slot = false;
branch_pc = 0;
}
void dump() const
@@ -171,8 +179,8 @@ inline uint32_t getRegU32(const R5900Context *ctx, int reg)
inline void setReturnU32(R5900Context *ctx, uint32_t value)
{
// Keep low 64-bits coherent for helpers that read GPRs as 64-bit.
ctx->r[2] = _mm_set_epi64x(0, static_cast<int64_t>(value)); // $v0
// R5900 sign-extends 32-bit results into 64-bit GPR, even for unsigned values.
ctx->r[2] = _mm_set_epi64x(0, static_cast<int64_t>(static_cast<int32_t>(value))); // $v0
}
inline void setReturnS32(R5900Context *ctx, int32_t value)
@@ -413,34 +421,33 @@ public:
static inline bool isSpecialAddress(uint32_t addr)
{
// BIOS (physical + cached/uncached aliases)
if ((addr >= PS2_BIOS_BASE && addr < (PS2_BIOS_BASE + PS2_BIOS_SIZE)) ||
(addr >= 0xBFC00000u && addr < (0xBFC00000u + PS2_BIOS_SIZE)))
auto inRange = [](uint32_t value, uint32_t base, uint32_t size) -> bool
{
return true;
}
return (value - base) < size;
};
// Scratchpad (16KB)
if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE))
return true;
// EE MMIO window (Timers, DMAC, INTC, etc)
if (addr >= PS2_IO_BASE && addr < (PS2_IO_BASE + PS2_IO_SIZE))
return true;
// GS privileged regs
if (addr >= PS2_GS_PRIV_REG_BASE && addr < (PS2_GS_PRIV_REG_BASE + PS2_GS_PRIV_REG_SIZE))
return true;
auto isPhysicalSpecial = [&](uint32_t physAddr) -> bool
{
if (inRange(physAddr, PS2_BIOS_BASE, PS2_BIOS_SIZE))
return true;
if (inRange(physAddr, PS2_SCRATCHPAD_BASE, PS2_SCRATCHPAD_SIZE))
return true;
if (inRange(physAddr, PS2_IO_BASE, PS2_IO_SIZE))
return true;
if (inRange(physAddr, PS2_GS_PRIV_REG_BASE, PS2_GS_PRIV_REG_SIZE))
return true;
if (physAddr >= PS2_VU0_CODE_BASE && physAddr < (PS2_VU1_DATA_BASE + PS2_VU1_DATA_SIZE))
return true;
return false;
};
// KSEG2/KSEG3 (TLB mapped)
if (addr >= 0xC0000000u)
return true;
// VU Memory (Micro/Data) mapped into EE space
if (addr >= PS2_VU0_CODE_BASE && addr < (PS2_VU1_DATA_BASE + PS2_VU1_DATA_SIZE))
return true;
return false;
// KSEG0/KSEG1 aliases → physical
const uint32_t physAddr = (addr >= 0x80000000u) ? (addr & 0x1FFFFFFFu) : addr;
return isPhysicalSpecial(physAddr);
}
public:
@@ -504,4 +511,3 @@ private:
};
#endif // PS2_RUNTIME_H
+135 -10
View File
@@ -1,6 +1,8 @@
#ifndef PS2_RUNTIME_MACROS_H
#define PS2_RUNTIME_MACROS_H
#include <cstdint>
#include <cmath>
#include <cstring>
#include <bit>
#if defined(_MSC_VER)
#include <intrin.h>
@@ -44,6 +46,29 @@ static inline uint32_t ps2_clz32(uint32_t x)
return static_cast<uint32_t>(std::countl_zero(x));
}
static inline uint64_t Ps2HiLoToU64(uint64_t hi, uint64_t lo)
{
return ((hi & 0xFFFFFFFFull) << 32) | (lo & 0xFFFFFFFFull);
}
static inline uint64_t Ps2SignExt32ToU64(uint32_t v)
{
return (uint64_t)(int64_t)(int32_t)v;
}
// PLZCW: Count leading bits that match the sign bit, minus 1.
// For positive values: count leading zeros minus 1 (excludes sign bit).
// For negative values: count leading ones minus 1 (excludes sign bit).
// Special cases: 0x00000000 -> 31, 0xFFFFFFFF -> 31.
static inline uint32_t ps2_plzcw32(uint32_t x)
{
if (x == 0 || x == 0xFFFFFFFF)
return 31;
if (x & 0x80000000u)
x = ~x; // If sign bit set, invert to count leading ones as zeros
return static_cast<uint32_t>(std::countl_zero(x)) - 1;
}
#define PS2_BLENDV_PS(a, b, mask) _mm_blendv_ps((a), (b), (mask))
#define PS2_MIN_EPI32(a, b) _mm_min_epi32((a), (b))
#define PS2_MAX_EPI32(a, b) _mm_max_epi32((a), (b))
@@ -305,11 +330,52 @@ static inline void Ps2FastWrite128(uint8_t *rdram, uint32_t addr, __m128i value)
#define PS2_PABSW(a) _mm_abs_epi32((__m128i)(a))
#define PS2_PABSH(a) _mm_abs_epi16((__m128i)(a))
#define PS2_PABSB(a) _mm_abs_epi8((__m128i)(a))
// Packed Pack (PPAC) - Packs larger elements into smaller ones
#define PS2_PPACW(a, b) _mm_packs_epi32((__m128i)(b), (__m128i)(a))
#define PS2_PPACH(a, b) _mm_packs_epi16((__m128i)(b), (__m128i)(a))
#define PS2_PPACB(a, b) _mm_packus_epi16(_mm_packs_epi32((__m128i)(b), (__m128i)(a)), _mm_setzero_si128())
inline __m128i ps2_paddu32(__m128i a, __m128i b)
{
__m128i sum = _mm_add_epi32(a, b);
__m128i overflow = _mm_cmpgt_epi32(_mm_xor_si128(a, _mm_set1_epi32(INT32_MIN)),
_mm_xor_si128(sum, _mm_set1_epi32(INT32_MIN)));
return _mm_or_si128(sum, overflow); // overflow lanes become all-1s
}
inline __m128i ps2_psubu32(__m128i a, __m128i b)
{
__m128i diff = _mm_sub_epi32(a, b);
// Underflow if a < b (unsigned). Clamp to 0.
__m128i underflow = _mm_cmpgt_epi32(_mm_xor_si128(b, _mm_set1_epi32(INT32_MIN)),
_mm_xor_si128(a, _mm_set1_epi32(INT32_MIN)));
return _mm_andnot_si128(underflow, diff); // underflow lanes become 0
}
inline __m128i ps2_ppacw(__m128i rs, __m128i rt)
{
// rs = [rs3 rs2 rs1 rs0], rt = [rt3 rt2 rt1 rt0]
return _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(rt), _mm_castsi128_ps(rs), _MM_SHUFFLE(2, 0, 2, 0)));
}
#define PS2_PPACW(a, b) ps2_ppacw((__m128i)(a), (__m128i)(b))
inline __m128i ps2_ppach(__m128i rs, __m128i rt)
{
const __m128i mask = _mm_setr_epi8(
0, 1, 4, 5, 8, 9, 12, 13, // from rt: halfwords 0,2,4,6
0, 1, 4, 5, 8, 9, 12, 13); // from rs: halfwords 0,2,4,6
__m128i lo = _mm_shuffle_epi8(rt, mask);
__m128i hi = _mm_shuffle_epi8(rs, mask);
return _mm_unpacklo_epi64(lo, hi);
}
#define PS2_PPACH(a, b) ps2_ppach((__m128i)(a), (__m128i)(b))
inline __m128i ps2_ppacb(__m128i rs, __m128i rt)
{
const __m128i mask = _mm_setr_epi8(
0, 2, 4, 6, 8, 10, 12, 14, // from rt: bytes 0,2,4,6,8,10,12,14
0, 2, 4, 6, 8, 10, 12, 14); // from rs
__m128i lo = _mm_shuffle_epi8(rt, mask);
__m128i hi = _mm_shuffle_epi8(rs, mask);
return _mm_unpacklo_epi64(lo, hi);
}
#define PS2_PPACB(a, b) ps2_ppacb((__m128i)(a), (__m128i)(b))
// Packed Interleave (PINT)
#define PS2_PINTH(a, b) _mm_unpacklo_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3, 2, 1, 0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3, 2, 1, 0)))
@@ -391,13 +457,13 @@ inline __m128i ps2_u64_to_epi64_pair(uint64_t value)
#define FPU_TRUNC_L_S(a) ((int64_t)(float)(a))
#define FPU_CEIL_L_S(a) ((int64_t)ceilf((float)(a)))
#define FPU_FLOOR_L_S(a) ((int64_t)floorf((float)(a)))
#define FPU_ROUND_W_S(a) ((int32_t)roundf((float)(a)))
#define FPU_ROUND_W_S(a) ((int32_t)nearbyintf((float)(a)))
#define FPU_TRUNC_W_S(a) ((int32_t)(float)(a))
#define FPU_CEIL_W_S(a) ((int32_t)ceilf((float)(a)))
#define FPU_FLOOR_W_S(a) ((int32_t)floorf((float)(a)))
#define FPU_CVT_S_W(a) ((float)(int32_t)(a))
#define FPU_CVT_S_L(a) ((float)(int64_t)(a))
#define FPU_CVT_W_S(a) ((int32_t)(float)(a))
#define FPU_CVT_W_S(a) ((int32_t)nearbyintf((float)(a)))
#define FPU_CVT_L_S(a) ((int64_t)(float)(a))
#define FPU_C_F_S(a, b) (0)
#define FPU_C_UN_S(a, b) (isnan((float)(a)) || isnan((float)(b)))
@@ -416,7 +482,68 @@ inline __m128i ps2_u64_to_epi64_pair(uint64_t value)
#define FPU_C_LE_S(a, b) ((float)(a) <= (float)(b))
#define FPU_C_NGT_S(a, b) ((float)(a) <= (float)(b) || isnan((float)(a)) || isnan((float)(b)))
#define PS2_QFSRV(rs, rt, sa) _mm_or_si128(_mm_srl_epi32(rt, _mm_cvtsi32_si128(sa)), _mm_sll_epi32(rs, _mm_cvtsi32_si128(32 - sa)))
// QFSRV: Quadword Funnel Shift Right Variable
// Concatenates rs || rt (256 bits) and right-shifts by SA bits, taking lower 128 bits.
inline __m128i ps2_qfsrv(__m128i rs, __m128i rt, uint32_t sa)
{
if (sa == 0) return rt;
if (sa >= 128) {
if (sa >= 256) return _mm_setzero_si128();
uint32_t shift = sa - 128;
if (shift == 0) return rs;
// Shift rs right by (sa-128) bits
uint32_t byteShift = shift / 8;
uint32_t bitShift = shift % 8;
// Byte shift rs right
alignas(16) uint8_t buf[16] = {};
alignas(16) uint8_t src[16];
_mm_store_si128((__m128i*)src, rs);
for (uint32_t i = 0; i + byteShift < 16; i++)
buf[i] = src[i + byteShift];
__m128i result = _mm_load_si128((__m128i*)buf);
if (bitShift > 0)
result = _mm_or_si128(_mm_srli_epi64(result, bitShift),
_mm_slli_epi64(_mm_bsrli_si128(result, 8), 64 - bitShift));
return result;
}
// sa is 1..127: result = (rs || rt) >> sa, lower 128 bits
uint32_t byteShift = sa / 8;
uint32_t bitShift = sa % 8;
alignas(16) uint8_t combined[32];
_mm_store_si128((__m128i*)(combined), rt); // low 128 bits
_mm_store_si128((__m128i*)(combined + 16), rs); // high 128 bits
// Shift right by byteShift bytes
alignas(16) uint8_t shifted[16];
for (uint32_t i = 0; i < 16; i++)
shifted[i] = (i + byteShift < 32) ? combined[i + byteShift] : 0;
__m128i result = _mm_load_si128((__m128i*)shifted);
if (bitShift > 0) {
uint8_t extra = (byteShift + 16 < 32) ? combined[byteShift + 16] : 0;
__m128i hi_byte = _mm_insert_epi8(_mm_setzero_si128(), extra, 15);
alignas(16) uint8_t src32[32];
for (uint32_t i = 0; i < 32; i++) src32[i] = combined[i];
uint64_t lo0, lo1, hi0, hi1;
std::memcpy(&lo0, src32, 8);
std::memcpy(&lo1, src32 + 8, 8);
std::memcpy(&hi0, src32 + 16, 8);
std::memcpy(&hi1, src32 + 24, 8);
// 256-bit right shift by sa bits
uint64_t r0, r1;
if (sa < 64) {
r0 = (lo0 >> sa) | (lo1 << (64 - sa));
r1 = (lo1 >> sa) | (hi0 << (64 - sa));
} else if (sa < 128) {
uint32_t s = sa - 64;
if (s == 0) { r0 = lo1; r1 = hi0; }
else { r0 = (lo1 >> s) | (hi0 << (64 - s)); r1 = (hi0 >> s) | (hi1 << (64 - s)); }
} else {
r0 = 0; r1 = 0; // handled above
}
result = _mm_set_epi64x((long long)r1, (long long)r0);
}
return result;
}
#define PS2_QFSRV(rs, rt, sa) ps2_qfsrv((__m128i)(rs), (__m128i)(rt), (uint32_t)(sa))
#define PS2_PCPYLD(rs, rt) _mm_unpacklo_epi64(rt, rs)
#define PS2_PEXEH(rs) _mm_shufflelo_epi16(_mm_shufflehi_epi16(rs, _MM_SHUFFLE(2, 3, 0, 1)), _MM_SHUFFLE(2, 3, 0, 1))
#define PS2_PEXEW(rs) _mm_shuffle_epi32(rs, _MM_SHUFFLE(2, 3, 0, 1))
@@ -425,8 +552,6 @@ inline __m128i ps2_u64_to_epi64_pair(uint64_t value)
// Additional VU0 operations
#define PS2_VSQRT(x) sqrtf(x)
#define PS2_VRSQRT(x) (1.0f / sqrtf(x))
#define PS2_VCALLMS(addr) // VU0 microprogram calls not supported directly
#define PS2_VCALLMSR(reg) // VU0 microprogram calls not supported directly
#define GPR_U32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0U : static_cast<uint32_t>(PS2_EXTRACT_EPI32_0(ctx_ptr->r[reg_idx])))
#define GPR_S32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0 : PS2_EXTRACT_EPI32_0(ctx_ptr->r[reg_idx]))
@@ -447,7 +572,7 @@ static inline void Ps2SetGprLow64(R5900Context *ctx, int reg, __m128i new_low)
{ \
if ((reg_idx) != 0) \
{ \
__m128i _newVal = _mm_cvtsi32_si128((int)(val)); \
__m128i _newVal = _mm_cvtsi64_si128((int64_t)(int32_t)(val)); \
\
Ps2SetGprLow64(ctx_ptr, reg_idx, _newVal); \
} \
+3 -1
View File
@@ -5,8 +5,9 @@
#include "ps2_call_list.h"
#include <mutex>
#include <atomic>
#include <cstdint>
#include <cstring>
// Number of active host threads spawned for PS2 thread emulation
extern std::atomic<int> g_activeThreads;
static std::mutex g_sys_fd_mutex;
@@ -20,6 +21,7 @@ namespace ps2_syscalls
bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId);
void notifyRuntimeStop();
void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime);
}
#endif // PS2_SYSCALLS_H
+141
View File
@@ -0,0 +1,141 @@
#include "ps2_gs_gpu.h"
#include "raylib.h"
#include "rlgl.h"
GsGpuFrameData::GsGpuFrameData()
{
m_buffers[0].reserve(8192);
m_buffers[1].reserve(8192);
}
void GsGpuFrameData::pushPrimitive(const GsGpuPrimitive &prim)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_buffers[m_backIdx].push_back(prim);
m_hasData.store(true, std::memory_order_relaxed);
}
const std::vector<GsGpuPrimitive> &GsGpuFrameData::swapAndGetFront()
{
std::lock_guard<std::mutex> lock(m_mutex);
int frontIdx = m_backIdx;
m_backIdx = 1 - m_backIdx;
m_buffers[m_backIdx].clear();
m_hasData.store(false, std::memory_order_relaxed);
return m_buffers[frontIdx];
}
bool GsGpuFrameData::hasGpuPrimitives() const
{
return m_hasData.load(std::memory_order_relaxed);
}
GsGpuFrameData &gsGpuGetFrameData()
{
static GsGpuFrameData instance;
return instance;
}
bool gsGpuRenderFrame()
{
GsGpuFrameData &fd = gsGpuGetFrameData();
const std::vector<GsGpuPrimitive> &prims = fd.swapAndGetFront();
if (prims.empty())
{
return false;
}
const float screenW = static_cast<float>(fd.screenWidth());
const float screenH = static_cast<float>(fd.screenHeight());
// Set up 2D orthographic projection matching PS2 screen coords
rlMatrixMode(RL_PROJECTION);
rlPushMatrix();
rlLoadIdentity();
rlOrtho(0.0, static_cast<double>(screenW),
static_cast<double>(screenH), 0.0,
-1.0, 1.0);
rlMatrixMode(RL_MODELVIEW);
rlPushMatrix();
rlLoadIdentity();
// Disable depth test for 2D rendering (PS2 GS handles Z separately)
rlDisableDepthTest();
// Disable backface culling — PS2 games rely on both winding orders
rlDisableBackfaceCulling();
// Render each primitive
for (const GsGpuPrimitive &prim : prims)
{
switch (prim.type)
{
case GS_GPU_TRIANGLE:
{
rlBegin(RL_TRIANGLES);
for (int i = 0; i < 3; ++i)
{
const GsGpuVertex &v = prim.verts[i];
rlColor4ub(v.r, v.g, v.b, v.a);
rlVertex3f(v.x, v.y, v.z);
}
rlEnd();
break;
}
case GS_GPU_QUAD:
{
// QUAD: v0=top-left, v1=top-right, v2=bottom-left, v3=bottom-right
// Raylib RL_QUADS expects: v0, v1, v2, v3 in order
rlBegin(RL_QUADS);
for (int i = 0; i < 4; ++i)
{
const GsGpuVertex &v = prim.verts[i];
rlColor4ub(v.r, v.g, v.b, v.a);
rlVertex3f(v.x, v.y, v.z);
}
rlEnd();
break;
}
case GS_GPU_LINE:
{
rlBegin(RL_LINES);
for (int i = 0; i < 2; ++i)
{
const GsGpuVertex &v = prim.verts[i];
rlColor4ub(v.r, v.g, v.b, v.a);
rlVertex3f(v.x, v.y, v.z);
}
rlEnd();
break;
}
case GS_GPU_POINT:
{
const GsGpuVertex &v = prim.verts[0];
rlBegin(RL_TRIANGLES);
rlColor4ub(v.r, v.g, v.b, v.a);
rlVertex3f(v.x - 0.5f, v.y - 0.5f, v.z);
rlVertex3f(v.x + 0.5f, v.y - 0.5f, v.z);
rlVertex3f(v.x, v.y + 0.5f, v.z);
rlEnd();
break;
}
}
}
rlDrawRenderBatchActive();
rlEnableBackfaceCulling();
rlEnableDepthTest();
rlMatrixMode(RL_MODELVIEW);
rlPopMatrix();
rlMatrixMode(RL_PROJECTION);
rlPopMatrix();
return true;
}
File diff suppressed because it is too large Load Diff
+252 -40
View File
@@ -176,6 +176,7 @@ bool PS2Memory::initialize(size_t ramSize)
// Initialize GS registers
memset(&gs_regs, 0, sizeof(gs_regs));
m_gsDrawCtx = GSDrawContext{};
// Allocate GS VRAM (4MB)
m_gsVRAM = new uint8_t[PS2_GS_VRAM_SIZE];
@@ -363,8 +364,10 @@ uint32_t PS2Memory::read32(uint32_t address)
if (isGsPrivReg(address))
{
uint64_t *reg = gsRegPtr(gs_regs, address);
if (!reg)
return 0;
uint32_t off = address & 7;
uint64_t val = reg ? *reg : 0;
uint64_t val = *reg;
return (uint32_t)(val >> (off * 8));
}
@@ -412,7 +415,14 @@ uint64_t PS2Memory::read64(uint32_t address)
return loadScalar<uint64_t>(m_rdram, physAddr, PS2_RAM_SIZE, "read64 rdram", address);
}
// 64-bit IO operations are not common, but who knows
// 64-bit IO read: compose from the two adjacent 32-bit IO register slots
// to avoid any side-effects from read32 handlers.
if (address >= PS2_IO_BASE && address < (PS2_IO_BASE + PS2_IO_SIZE))
{
uint32_t lo = m_ioRegisters.count(address) ? m_ioRegisters[address] : 0u;
uint32_t hi = m_ioRegisters.count(address + 4) ? m_ioRegisters[address + 4] : 0u;
return static_cast<uint64_t>(lo) | (static_cast<uint64_t>(hi) << 32);
}
return (uint64_t)read32(address) | ((uint64_t)read32(address + 4) << 32);
}
@@ -560,6 +570,7 @@ void PS2Memory::write64(uint32_t address, uint64_t value)
}
else if (physAddr < PS2_RAM_SIZE)
{
markModified(address, 8);
storeScalar<uint64_t>(m_rdram, physAddr, PS2_RAM_SIZE, value, "write64 rdram", address);
}
else
@@ -586,6 +597,7 @@ void PS2Memory::write128(uint32_t address, __m128i value)
}
else if (physAddr < PS2_RAM_SIZE)
{
markModified(address, 16);
inRange(physAddr, sizeof(__m128i), PS2_RAM_SIZE, "write128 rdram", address);
_mm_storeu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr]), value);
}
@@ -602,8 +614,70 @@ void PS2Memory::write128(uint32_t address, __m128i value)
bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
{
// ── IPU registers (0x10002000-0x10002030) ──────────────────
// On real PS2, IPU_CTRL bit 31 (BUSY) is READ-ONLY — set by hardware.
// We must NOT store the raw value for IPU_CTRL because the game
// might write 0x40000000 (RST) and we'd return 0 with no BUSY,
// but if any stale value had bit 31, the polling loop would hang.
if (address >= 0x10002000 && address <= 0x10002030)
{
static int ipuWriteLog = 0;
if (ipuWriteLog < 30)
{
std::cerr << "[IPU] write addr=0x" << std::hex << address
<< " val=0x" << value << std::dec << std::endl;
++ipuWriteLog;
}
if (address == 0x10002010)
{
// IPU_CTRL write: bit 30 = RST (reset). After reset,
// all status bits clear. Never store BUSY (bit 31).
if (value & (1u << 30))
{
// Reset IPU — clear all IPU registers
m_ioRegisters[0x10002000] = 0;
m_ioRegisters[0x10002010] = 0;
m_ioRegisters[0x10002020] = 0;
m_ioRegisters[0x10002030] = 0;
}
else
{
// Store without BUSY bit
m_ioRegisters[address] = value & ~(1u << 31);
}
}
else
{
// IPU_CMD (0x10002000) — store command, don't set busy
m_ioRegisters[address] = value;
}
return true;
}
m_ioRegisters[address] = value;
{
static int io_total_log = 0;
if (io_total_log < 100)
{
std::cerr << "[IO_WRITE] addr=0x" << std::hex << address << " val=0x" << value << std::dec << std::endl;
++io_total_log;
}
}
if (address >= 0x10008000 && address < 0x1000F000)
{
static int dma_io_log = 0;
if (dma_io_log < 200)
{
uint32_t ch = (address >> 8) & 0xFF;
uint32_t off = address & 0xFF;
std::cerr << "[DMA_IO] ch=0x" << std::hex << (address & 0xFFFFFF00)
<< " off=0x" << off << " val=0x" << value << std::dec << std::endl;
++dma_io_log;
}
}
if (address >= 0x10008000 && address < 0x1000F000)
{
if ((address & 0xFF) == 0x00 && (value & 0x100))
@@ -615,67 +689,127 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
if ((channelBase == 0x1000A000 || channelBase == 0x10009000) && m_gsVRAM)
{
auto doCopy = [&](uint32_t srcAddr, uint32_t qwCount)
auto dispatchTransfer = [&](uint32_t srcAddr, uint32_t qwCount)
{
const uint64_t bytes64 = static_cast<uint64_t>(qwCount) * 16ull;
uint32_t bytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast<uint32_t>(bytes64);
uint32_t src = 0;
if (qwCount == 0)
{
return;
}
uint32_t srcPhys = 0;
try
{
src = translateAddress(srcAddr);
srcPhys = translateAddress(srcAddr);
}
catch (const std::exception &)
{
return;
}
uint32_t basePage = static_cast<uint32_t>(gs_regs.dispfb1 & 0x1FF);
uint32_t dest = basePage * 2048;
if (dest >= PS2_GS_VRAM_SIZE)
if (srcPhys >= PS2_RAM_SIZE)
{
return;
}
if (dest + bytes > PS2_GS_VRAM_SIZE)
{
bytes = std::min<uint32_t>(bytes, PS2_GS_VRAM_SIZE - dest);
}
if (src >= PS2_RAM_SIZE)
if (channelBase == 0x1000A000)
{
processGIFPacket(srcPhys, qwCount);
return;
}
if (src + bytes > PS2_RAM_SIZE)
const uint64_t bytes64 = static_cast<uint64_t>(qwCount) * 16ull;
uint32_t bytes = bytes64 > static_cast<uint64_t>(PS2_RAM_SIZE)
? PS2_RAM_SIZE
: static_cast<uint32_t>(bytes64);
if (srcPhys + bytes > PS2_RAM_SIZE)
{
bytes = std::min<uint32_t>(bytes, PS2_RAM_SIZE - src);
bytes = PS2_RAM_SIZE - srcPhys;
}
if (bytes == 0)
processVIF1Data(srcPhys, bytes);
};
auto walkChain = [&](uint32_t startTadr)
{
uint32_t curTadr = startTadr;
constexpr int kMaxTags = 4096;
for (int i = 0; i < kMaxTags; ++i)
{
return;
uint32_t physTag = 0;
try
{
physTag = translateAddress(curTadr);
}
catch (const std::exception &)
{
break;
}
if (physTag + 16 > PS2_RAM_SIZE)
{
break;
}
const uint64_t tag = loadScalar<uint64_t>(m_rdram, physTag, PS2_RAM_SIZE, "dma chain tag", curTadr);
const uint16_t tagQwc = static_cast<uint16_t>(tag & 0xFFFFu);
const uint32_t id = static_cast<uint32_t>((tag >> 28) & 0x7u);
const uint32_t addr = static_cast<uint32_t>((tag >> 32) & 0x7FFFFFF0u);
const bool irq = ((tag >> 31) & 0x1u) != 0;
uint32_t dataAddr = 0;
uint32_t nextTag = 0;
bool endChain = false;
switch (id)
{
case 0: // REFE
dataAddr = addr;
endChain = true;
break;
case 1: // CNT
dataAddr = curTadr + 16u;
nextTag = curTadr + 16u + static_cast<uint32_t>(tagQwc) * 16u;
break;
case 2: // NEXT
dataAddr = curTadr + 16u;
nextTag = addr;
break;
case 3: // REF
case 4: // REFS
dataAddr = addr;
nextTag = curTadr + 16u;
break;
case 7: // END
dataAddr = curTadr + 16u;
endChain = true;
break;
default:
endChain = true;
break;
}
if (tagQwc > 0 && dataAddr != 0)
{
dispatchTransfer(dataAddr, tagQwc);
}
if (endChain || irq)
{
break;
}
curTadr = nextTag;
}
std::memcpy(m_gsVRAM + dest, m_rdram + src, bytes);
m_seenGifCopy = true;
m_gifCopyCount.fetch_add(1, std::memory_order_relaxed);
};
if (qwc > 0)
{
doCopy(madr, qwc);
dispatchTransfer(madr, qwc);
}
else
{
uint32_t tadr = m_ioRegisters[channelBase + 0x30];
uint32_t physTag = translateAddress(tadr);
if (physTag + 16 <= PS2_RAM_SIZE)
{
const uint8_t *tp = m_rdram + physTag;
uint64_t tag = loadScalar<uint64_t>(tp, 0, 16, "dma chain tag", tadr);
uint16_t tagQwc = static_cast<uint16_t>(tag & 0xFFFF);
uint32_t id = static_cast<uint32_t>((tag >> 28) & 0x7);
uint32_t addr = static_cast<uint32_t>((tag >> 32) & 0x7FFFFFF);
if (id == 0 || id == 1 || id == 2)
{
doCopy(addr, tagQwc);
}
}
const uint32_t tadr = m_ioRegisters[channelBase + 0x30];
walkChain(tadr);
}
m_ioRegisters[address] &= ~0x100;
}
}
@@ -711,8 +845,60 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
return false;
}
// ============================================================================
// pollDmaRegisters: Workaround for KSEG1 fast-path bypass
// When libsles.a is compiled with old headers, isSpecialAddress() doesn't
// recognize KSEG0/KSEG1 addresses (0x8xxx/0xBxxx). Game writes to e.g.
// 0xB000A000 (GIF DMA CHCR via KSEG1) go through Ps2FastWrite32 which
// stores to rdram[addr & 0x01FFFFFF] = rdram[0x1000A000], bypassing
// writeIORegister entirely. This function polls those shadow locations
// and triggers DMA processing when CHCR.STR (bit 8) is set.
//
// NOTE: DISABLED — sho_runner writes DMA regs via physical addresses which
// go through writeIORegister correctly. This function was reading garbage
// from rdram shadow (ELF code area) and triggering bogus DMA transfers.
// ============================================================================
int PS2Memory::pollDmaRegisters()
{
// Disabled — DMA writes go through writeIORegister, not KSEG1 shadow
return 0;
}
uint32_t PS2Memory::readIORegister(uint32_t address)
{
// ── IPU registers (0x10002000-0x10002030) ──────────────────
// IPU_CMD 0x10002000: command result / FIFO output
// IPU_CTRL 0x10002010: status — bit 31=BUSY (always 0: we don't decode)
// IPU_BP 0x10002020: bitstream pointer
// IPU_TOP 0x10002030: top 32 bits of FIFO
if (address >= 0x10002000 && address <= 0x10002030)
{
static int ipuReadLog = 0;
uint32_t val = 0;
switch (address)
{
case 0x10002000: // IPU_CMD — command result
val = m_ioRegisters[address];
break;
case 0x10002010: // IPU_CTRL — always NOT busy, ECD=0
val = m_ioRegisters[address] & ~(1u << 31); // clear BUSY
break;
case 0x10002020: // IPU_BP
case 0x10002030: // IPU_TOP
val = m_ioRegisters[address];
break;
default:
val = 0;
break;
}
if (ipuReadLog < 30)
{
std::cerr << "[IPU] read addr=0x" << std::hex << address
<< " val=0x" << val << std::dec << std::endl;
++ipuReadLog;
}
return val;
}
if (address >= 0x10000000 && address < 0x10010000)
{
if (address >= 0x10000000 && address < 0x10000100)
@@ -727,9 +913,9 @@ uint32_t PS2Memory::readIORegister(uint32_t address)
{
if ((address & 0xFF) == 0x00)
{
uint32_t channelStatus = m_ioRegisters[address] & ~0x100;
m_ioRegisters[address] = channelStatus;
return channelStatus;
// Return CHCR as-is. STR (bit 8) is cleared after DMA
// completion in writeIORegister, not on read.
return m_ioRegisters[address];
}
}
@@ -737,6 +923,32 @@ uint32_t PS2Memory::readIORegister(uint32_t address)
{
return 0;
}
// SIF hardware registers — HLE: pretend IOP is always ready
// 0x1000F200: SIF_SMCOM — IOP communication status
// 0x1000F210: SIF_MSCOM — EE→IOP command
// 0x1000F220: SIF_MSFLG — Main→Sub flags
// 0x1000F230: SIF_SMFLG — Sub→Main flags (IOP ready bits)
// 0x1000F240: SIF_CTRL — SIF control
if (address >= 0x1000F200 && address <= 0x1000F260)
{
static std::atomic<uint64_t> sifReads{0};
uint64_t n = sifReads.fetch_add(1);
if (n < 5 || (n % 100000) == 0)
{
std::cerr << "[SIF-HW] read 0x" << std::hex << address
<< " #" << std::dec << n << std::endl;
}
if (address == 0x1000F230)
{
return 0x60000;
}
if (address == 0x1000F240)
{
return 0xF0000002;
}
return 0;
}
}
auto it = m_ioRegisters.find(address);
+46 -12
View File
@@ -14,6 +14,7 @@
#include <thread>
#include <unordered_map>
#include "raylib.h"
#include "ps2_gs_gpu.h"
#include <ThreadNaming.h>
#define ELF_MAGIC 0x464C457F // "\x7FELF" in little endian
@@ -87,11 +88,23 @@ namespace
void raiseCop0Exception(R5900Context *ctx, uint32_t exceptionCode, bool tlbRefill = false)
{
ctx->cop0_epc = ctx->pc;
ctx->cop0_cause = (ctx->cop0_cause & ~(COP0_CAUSE_EXCCODE_MASK | COP0_CAUSE_BD)) |
((exceptionCode << 2) & COP0_CAUSE_EXCCODE_MASK);
if (ctx->in_delay_slot)
{
ctx->cop0_epc = ctx->branch_pc;
ctx->cop0_cause = (ctx->cop0_cause & ~COP0_CAUSE_EXCCODE_MASK) |
((exceptionCode << 2) & COP0_CAUSE_EXCCODE_MASK) |
COP0_CAUSE_BD;
}
else
{
ctx->cop0_epc = ctx->pc;
ctx->cop0_cause = (ctx->cop0_cause & ~(COP0_CAUSE_EXCCODE_MASK | COP0_CAUSE_BD)) |
((exceptionCode << 2) & COP0_CAUSE_EXCCODE_MASK);
}
ctx->cop0_status |= COP0_STATUS_EXL;
ctx->pc = selectExceptionVector(ctx, tlbRefill);
ctx->in_delay_slot = false;
}
std::filesystem::path normalizeAbsolutePath(const std::filesystem::path &path)
@@ -182,13 +195,15 @@ static void UploadFrame(Texture2D &tex, PS2Runtime *rt)
uint32_t fbw = (dispfb >> 9) & 0x3F;
uint32_t psm = (dispfb >> 15) & 0x1F;
// DISPLAY1 fields used here: DW bits 32-43, DH bits 44-54.
// DISPLAY1 fields used here: DX[11:0], DY[22:12], MAGH[25:23], MAGV[27:26], DW[43:32], DH[54:44].
uint64_t display64 = gs.display1;
uint32_t magh = static_cast<uint32_t>((display64 >> 23) & 0x7); // magnification H (0-7)
uint32_t dw = static_cast<uint32_t>((display64 >> 32) & 0xFFF);
uint32_t dh = static_cast<uint32_t>((display64 >> 44) & 0x7FF);
// Default to 640x448 if regs look strange.
uint32_t width = (dw + 1);
// DW is in VCK units: actual pixel width = (DW + 1) / (MAGH + 1).
uint32_t maghDiv = magh + 1;
uint32_t width = (dw + 1) / maghDiv;
uint32_t height = (dh + 1);
if (dw == 0)
width = FB_WIDTH;
@@ -213,7 +228,8 @@ static void UploadFrame(Texture2D &tex, PS2Runtime *rt)
const uint32_t bytesPerPixel = (psm == 2u || psm == 0x0Au) ? 2u : 4u;
uint32_t strideBytes = (fbw ? fbw : (FB_WIDTH / 64)) * 64 * bytesPerPixel;
std::vector<uint8_t> scratch(FB_WIDTH * FB_HEIGHT * 4, 0); // maybe we can do this static
static std::vector<uint8_t> scratch(FB_WIDTH * FB_HEIGHT * 4, 0);
std::memset(scratch.data(), 0, scratch.size());
uint8_t *rdram = rt->memory().getRDRAM();
uint8_t *gsvram = rt->memory().getGSVRAM();
@@ -645,6 +661,12 @@ void PS2Runtime::handleSyscall(uint8_t *rdram, R5900Context *ctx)
void PS2Runtime::handleSyscall(uint8_t *rdram, R5900Context *ctx, uint32_t encodedSyscallId)
{
if (ctx->in_delay_slot)
{
throw std::runtime_error("Attempted to execute a syscall inside a branch delay slot! "
"This breaks the atomic basic block model and is structurally unsupported by the emulator.");
}
// Try immediate first
if (encodedSyscallId != 0 && ps2_syscalls::dispatchNumericSyscall(encodedSyscallId, rdram, ctx, this))
{
@@ -1025,7 +1047,9 @@ uint32_t PS2Runtime::guestCalloc(uint32_t count, uint32_t size, uint32_t alignme
uint8_t *rdram = m_memory.getRDRAM();
if (rdram)
{
std::memset(rdram + guestAddr, 0, totalSize);
uint32_t physAddr = guestAddr & PS2_RAM_MASK;
if (physAddr + totalSize <= PS2_RAM_SIZE)
std::memset(rdram + physAddr, 0, totalSize);
}
}
@@ -1117,7 +1141,10 @@ uint32_t PS2Runtime::guestRealloc(uint32_t guestAddr, uint32_t newSize, uint32_t
if (rdram)
{
const uint32_t copyBytes = std::min(oldSize, newSize);
std::memmove(rdram + newAddr, rdram + oldAddr, copyBytes);
uint32_t dstPhys = newAddr & PS2_RAM_MASK;
uint32_t srcPhys = oldAddr & PS2_RAM_MASK;
if (dstPhys + copyBytes <= PS2_RAM_SIZE && srcPhys + copyBytes <= PS2_RAM_SIZE)
std::memmove(rdram + dstPhys, rdram + srcPhys, copyBytes);
}
freeGuestBlockLocked(oldAddr);
@@ -1419,11 +1446,18 @@ void PS2Runtime::run()
lastVif = curVif;
}
}
UploadFrame(frameTex, this);
BeginDrawing();
ClearBackground(BLACK);
DrawTexture(frameTex, 0, 0, WHITE);
bool gpuRendered = gsGpuRenderFrame();
if (!gpuRendered)
{
// lets draw for now as debug but we wont need this in future
UploadFrame(frameTex, this);
DrawTexture(frameTex, 0, 0, WHITE);
}
EndDrawing();
if (WindowShouldClose())
+1
View File
@@ -17,6 +17,7 @@
#include <unordered_set>
#include <filesystem>
#include <mutex>
#include <limits>
#include "stubs/helpers/ps2_stubs_helpers.inl"
@@ -0,0 +1,268 @@
// Based on Blackline Interactive implementation
#include "ps2_memory.h"
#include <cstring>
#include <iostream>
enum VIFCmd : uint8_t
{
VIF_NOP = 0x00,
VIF_STCYCL = 0x01,
VIF_OFFSET = 0x02,
VIF_BASE = 0x03,
VIF_ITOP = 0x04,
VIF_STMOD = 0x05,
VIF_MSKPATH3 = 0x06,
VIF_MARK = 0x07,
VIF_FLUSHE = 0x10,
VIF_FLUSH = 0x11,
VIF_FLUSHA = 0x13,
VIF_MSCAL = 0x14,
VIF_MSCALF = 0x15,
VIF_MSCNT = 0x17,
VIF_STMASK = 0x20,
VIF_STROW = 0x30,
VIF_STCOL = 0x31,
VIF_MPG = 0x4A,
VIF_DIRECT = 0x50,
VIF_DIRECTHL = 0x51,
// UNPACK range: 0x60-0x6F (V4-32..V4-5)
};
namespace
{
static int g_vifLogCount = 0;
static uint32_t g_vifDirectCount = 0;
static uint32_t g_vifUnpackCount = 0;
static uint32_t g_vifTotalCmds = 0;
} // namespace
void PS2Memory::processVIF1Data(uint32_t srcPhys, uint32_t sizeBytes)
{
if (!m_rdram || !m_gsVRAM || sizeBytes == 0u)
return;
if (srcPhys >= PS2_RAM_SIZE)
return;
const uint64_t requestedEnd = static_cast<uint64_t>(srcPhys) + static_cast<uint64_t>(sizeBytes);
if (requestedEnd > static_cast<uint64_t>(PS2_RAM_SIZE))
sizeBytes = PS2_RAM_SIZE - srcPhys;
const uint8_t *data = m_rdram + srcPhys;
uint32_t pos = 0; // byte offset
while (pos + 4 <= sizeBytes)
{
// Read VIF command word (32 bits)
uint32_t cmd;
memcpy(&cmd, data + pos, 4);
pos += 4;
uint8_t opcode = (cmd >> 24) & 0x7F; // bits 30:24
// bool irq = (cmd >> 31) & 1; // bit 31: interrupt
uint16_t imm = cmd & 0xFFFF; // bits 15:0 (IMMEDIATE)
uint8_t num = (cmd >> 16) & 0xFF; // bits 23:16 (NUM)
g_vifTotalCmds++;
if (opcode == VIF_NOP)
{
// No operation
continue;
}
else if (opcode == VIF_STCYCL)
{
// Set write cycle: CL in bits 7:0, WL in bits 15:8
// Used with UNPACK - store for later
continue;
}
else if (opcode == VIF_OFFSET)
{
// Set double-buffer offset
continue;
}
else if (opcode == VIF_BASE)
{
// Set double-buffer base
continue;
}
else if (opcode == VIF_ITOP)
{
// Set ITOP register
continue;
}
else if (opcode == VIF_STMOD)
{
// Set decompression mode
continue;
}
else if (opcode == VIF_MSKPATH3)
{
// Mask/unmask GIF PATH3
continue;
}
else if (opcode == VIF_MARK)
{
// Set MARK register
continue;
}
else if (opcode == VIF_FLUSHE || opcode == VIF_FLUSH || opcode == VIF_FLUSHA)
{
// Wait for pipeline flush - no-op in software
continue;
}
else if (opcode == VIF_MSCAL || opcode == VIF_MSCALF)
{
// Start VU1 microprogram at address IMM - skip (no VU1 emu)
continue;
}
else if (opcode == VIF_MSCNT)
{
// Continue VU1 execution - skip
continue;
}
else if (opcode == VIF_STMASK)
{
// Next QW contains write mask - skip 4 bytes
pos += 4;
if (pos > sizeBytes)
break;
continue;
}
else if (opcode == VIF_STROW)
{
// Next 4 words (16 bytes) = fill row registers
pos += 16;
if (pos > sizeBytes)
break;
continue;
}
else if (opcode == VIF_STCOL)
{
// Next 4 words (16 bytes) = fill column registers
pos += 16;
if (pos > sizeBytes)
break;
continue;
}
else if (opcode == VIF_MPG)
{
// Upload microprogram to VU1: NUM*8 bytes of data follow
uint32_t mpgBytes = (uint32_t)num * 8;
// Align to QW
mpgBytes = (mpgBytes + 15) & ~15u;
pos += mpgBytes;
if (pos > sizeBytes)
break;
continue;
}
else if (opcode == VIF_DIRECT || opcode == VIF_DIRECTHL)
{
// IMM = number of 128-bit quadwords of GIF data following
uint32_t qwCount = imm;
if (qwCount == 0)
qwCount = 65536; // 0 means 65536
const uint32_t availableQw = (sizeBytes - pos) / 16u;
const bool truncated = qwCount > availableQw;
if (qwCount > availableQw)
{
qwCount = availableQw;
}
if (qwCount > 0)
{
// The GIF data starts at current position in the source buffer
// processGIFPacket expects a physical RAM address
uint32_t gifPhysAddr = srcPhys + pos;
processGIFPacket(gifPhysAddr, qwCount);
g_vifDirectCount++;
}
pos += qwCount * 16;
if (truncated)
{
pos = sizeBytes;
break;
}
continue;
}
else if ((opcode & 0x60) == 0x60)
{
// UNPACK commands (0x60-0x7F)
// Format: VN in bits 25:24, VL in bits 27:26
// NUM = number of vectors, IMM = VU addr
// Skip the data payload
uint8_t vn = (opcode >> 2) & 0x3; // 0=S, 1=V2, 2=V3, 3=V4
uint8_t vl = opcode & 0x3; // 0=32, 1=16, 2=8, 3=5
// Calculate component count and size
int components = vn + 1;
int bitsPerComponent;
switch (vl)
{
case 0:
bitsPerComponent = 32;
break;
case 1:
bitsPerComponent = 16;
break;
case 2:
bitsPerComponent = 8;
break;
case 3:
bitsPerComponent = 16;
break; // V4-5 is special (4x16 packed)
default:
bitsPerComponent = 32;
break;
}
// Total bits per vector
int bitsPerVector;
if (vl == 3 && vn == 3)
{
// V4-5: 4 components × 4-bit nibbles = 16 bits per vector.
bitsPerVector = 16;
}
else
{
bitsPerVector = components * bitsPerComponent;
}
uint32_t bytesPerVector = (bitsPerVector + 7) / 8;
uint32_t totalBytes = (uint32_t)num * bytesPerVector;
// Align to 32-bit word boundary
totalBytes = (totalBytes + 3) & ~3u;
pos += totalBytes;
g_vifUnpackCount++;
if (pos > sizeBytes)
break;
continue;
}
else
{
// Unknown VIF command - try to continue
if (g_vifLogCount < 10)
{
std::cerr << "[VIF1] Unknown opcode 0x" << std::hex << (int)opcode
<< " at offset 0x" << (pos - 4) << std::dec << std::endl;
g_vifLogCount++;
}
continue;
}
}
static uint32_t s_logInterval = 0;
if (++s_logInterval >= 100)
{
if (g_vifLogCount < 50)
{
std::cerr << "[VIF1] stats: total_cmds=" << g_vifTotalCmds
<< " direct=" << g_vifDirectCount
<< " unpack=" << g_vifUnpackCount << std::endl;
g_vifLogCount++;
}
s_logInterval = 0;
}
}
@@ -33,7 +33,7 @@ namespace
constexpr uint32_t kIopHeapBase = 0x01A00000;
constexpr uint32_t kIopHeapLimit = 0x01F00000;
constexpr uint32_t kIopHeapAlign = 16;
constexpr uint32_t kIopHeapAlign = 64;
uint32_t g_iopHeapNext = kIopHeapBase;
std::string toLowerAscii(std::string value)
+49 -4
View File
@@ -225,7 +225,12 @@ void sceGsResetPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void sceGsSetDefClear(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("sceGsSetDefClear", rdram, ctx, runtime);
const uint32_t clearAddr = getRegU32(ctx, 4);
if (uint8_t *clear = getMemPtr(rdram, clearAddr))
{
std::memset(clear, 0, 64);
}
setReturnS32(ctx, 0);
}
void sceGsSetDefDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
@@ -257,12 +262,50 @@ void sceGsSetDefDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void sceGsSetDefDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("sceGsSetDefDrawEnv", rdram, ctx, runtime);
const uint32_t envAddr = getRegU32(ctx, 4);
uint32_t psm = getRegU32(ctx, 5);
uint32_t w = getRegU32(ctx, 6);
uint32_t h = getRegU32(ctx, 7);
const uint32_t vramAddr = readStackU32(rdram, ctx, 16);
const uint32_t vramX = readStackU32(rdram, ctx, 20);
const uint32_t vramY = readStackU32(rdram, ctx, 24);
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 = static_cast<uint16_t>(vramAddr & 0xFFFFu);
env.fbw = static_cast<uint8_t>((w + 63u) / 64u);
env.psm = static_cast<uint8_t>(psm & 0xFFu);
env.vram_x = static_cast<uint16_t>(vramX & 0xFFFFu);
env.vram_y = static_cast<uint16_t>(vramY & 0xFFFFu);
env.draw_mask = 0;
env.auto_clear = 1;
env.bg_r = 0;
env.bg_g = 0;
env.bg_b = 0;
env.bg_a = 0x80;
env.bg_q = 0.0f;
if (uint8_t *ptr = getMemPtr(rdram, envAddr))
{
std::memcpy(ptr, &env, sizeof(env));
}
setReturnS32(ctx, 0);
}
void sceGsSetDefDrawEnv2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("sceGsSetDefDrawEnv2", rdram, ctx, runtime);
sceGsSetDefDrawEnv(rdram, ctx, runtime);
}
void sceGsSetDefLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
@@ -303,15 +346,17 @@ void sceGsSyncPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void sceGsSyncV(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
ps2_syscalls::WaitVSyncTick(rdram, runtime);
setReturnS32(ctx, 0);
}
void sceGsSyncVCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
ps2_syscalls::WaitVSyncTick(rdram, runtime);
setReturnS32(ctx, 0);
}
void sceGszbufaddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("sceGszbufaddr", rdram, ctx, runtime);
setReturnU32(ctx, getRegU32(ctx, 4));
}
File diff suppressed because it is too large Load Diff
@@ -9,6 +9,57 @@ void syRtcInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
setReturnS32(ctx, 0);
}
namespace
{
constexpr uint32_t kCvSyMallocAddr = 0x002D9A70u;
constexpr uint32_t kCvMallocMaxSizeAddr = 0x01140B60u;
constexpr uint32_t kCvMallocFreeSizeAddr = 0x01140B68u;
constexpr uint32_t kCvMallocHeadPtrAddr = 0x01140B70u;
constexpr uint32_t kCvMallocPoolAddr = 0x01140B80u;
constexpr uint32_t kCvMallocPoolSize = 0x00CCD000u;
constexpr uint32_t kCvMallocUseSizeOff = 0x00u;
constexpr uint32_t kCvMallocTotalSizeOff = 0x04u;
constexpr uint32_t kCvMallocNextOff = 0x0Cu;
constexpr uint32_t kCvMallocHeaderSize = 0x40u;
constexpr uint32_t kCvMallocInitialFreeSize = kCvMallocPoolSize - kCvMallocHeaderSize;
uint32_t cvReadU32(const uint8_t *rdram, uint32_t addr)
{
if (!rdram)
{
return 0u;
}
const uint32_t offset = addr & PS2_RAM_MASK;
if (offset + sizeof(uint32_t) > PS2_RAM_SIZE)
{
return 0u;
}
uint32_t value = 0u;
std::memcpy(&value, rdram + offset, sizeof(value));
return value;
}
void cvWriteU32(uint8_t *rdram, uint32_t addr, uint32_t value)
{
if (!rdram)
{
return;
}
const uint32_t offset = addr & PS2_RAM_MASK;
if (offset + sizeof(uint32_t) > PS2_RAM_SIZE)
{
return;
}
std::memcpy(rdram + offset, &value, sizeof(value));
}
}
void syFree(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
static int logCount = 0;
@@ -19,7 +70,47 @@ void syFree(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
}
const uint32_t guestAddr = getRegU32(ctx, 4); // $a0
if (runtime && guestAddr != 0u)
bool released = false;
if (rdram && guestAddr != 0u)
{
uint32_t search = cvReadU32(rdram, kCvMallocHeadPtrAddr);
if (search < PS2_RAM_SIZE)
{
for (uint32_t guard = 0; guard < 0x100000u; ++guard)
{
const uint32_t next = cvReadU32(rdram, search + kCvMallocNextOff);
if (next == 0u)
{
break;
}
if (guestAddr == (next + kCvMallocHeaderSize))
{
const uint32_t searchTotal = cvReadU32(rdram, search + kCvMallocTotalSizeOff);
const uint32_t nextTotal = cvReadU32(rdram, next + kCvMallocTotalSizeOff);
const uint32_t nextUsed = cvReadU32(rdram, next + kCvMallocUseSizeOff);
const uint32_t nextNext = cvReadU32(rdram, next + kCvMallocNextOff);
const uint32_t freeSize = cvReadU32(rdram, kCvMallocFreeSizeAddr);
cvWriteU32(rdram, search + kCvMallocTotalSizeOff, searchTotal + nextTotal + kCvMallocHeaderSize);
cvWriteU32(rdram, search + kCvMallocNextOff, nextNext);
cvWriteU32(rdram, kCvMallocFreeSizeAddr, freeSize + nextUsed + kCvMallocHeaderSize);
released = true;
break;
}
search = next;
if (search >= PS2_RAM_SIZE)
{
break;
}
}
}
}
if (!released && runtime && guestAddr != 0u)
{
runtime->guestFree(guestAddr);
}
@@ -32,7 +123,21 @@ 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)
if (runtime && requestedSize != 0u && runtime->hasFunction(kCvSyMallocAddr) && ctx->pc != kCvSyMallocAddr)
{
const uint32_t returnPc = getRegU32(ctx, 31);
PS2Runtime::RecompiledFunction syMallocFn = runtime->lookupFunction(kCvSyMallocAddr);
ctx->pc = kCvSyMallocAddr;
syMallocFn(rdram, ctx, runtime);
if (ctx->pc == kCvSyMallocAddr || ctx->pc == 0u)
{
ctx->pc = returnPc;
}
resultAddr = getRegU32(ctx, 2);
}
else if (runtime && requestedSize != 0u)
{
// Match game expectation for allocator alignment while keeping pointers in EE RAM.
resultAddr = runtime->guestMalloc(requestedSize, 64u);
@@ -75,54 +180,26 @@ void Ps2_pad_actuater(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void syMallocInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t heapBase = getRegU32(ctx, 4); // $a0 (ignored by original CV allocator)
const uint32_t heapSize = getRegU32(ctx, 5); // $a1 (ignored by original CV allocator)
cvWriteU32(rdram, kCvMallocMaxSizeAddr, 0u);
cvWriteU32(rdram, kCvMallocFreeSizeAddr, kCvMallocInitialFreeSize);
cvWriteU32(rdram, kCvMallocHeadPtrAddr, kCvMallocPoolAddr);
cvWriteU32(rdram, kCvMallocPoolAddr + kCvMallocUseSizeOff, 0u);
cvWriteU32(rdram, kCvMallocPoolAddr + kCvMallocTotalSizeOff, kCvMallocInitialFreeSize);
cvWriteU32(rdram, kCvMallocPoolAddr + kCvMallocNextOff, 0u);
static int logCount = 0;
if (runtime)
if (logCount < 8)
{
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;
std::cout << "ps2_stub syMallocInit"
<< " reqBase=0x" << std::hex << heapBase
<< " reqSize=0x" << heapSize
<< " pool=0x" << kCvMallocPoolAddr
<< " free=0x" << kCvMallocInitialFreeSize
<< std::dec << std::endl;
++logCount;
}
@@ -237,11 +314,18 @@ void sndr_trans_func(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
++logCount;
}
// For now just clear the snd busy flag used by sdMultiUnitDownload/SysServer loops.
constexpr uint32_t kSndBusyAddr = 0x01E0E170;
// small hack for code veronica
constexpr uint32_t kSndBusyAddrCv = 0x01E1E190;
constexpr uint32_t kSndBusyAddrLegacy = 0x01E0E170;
if (rdram)
{
uint32_t offset = kSndBusyAddr & PS2_RAM_MASK;
uint32_t offset = kSndBusyAddrCv & PS2_RAM_MASK;
if (offset + sizeof(uint32_t) <= PS2_RAM_SIZE)
{
*reinterpret_cast<uint32_t *>(rdram + offset) = 0;
}
offset = kSndBusyAddrLegacy & PS2_RAM_MASK;
if (offset + sizeof(uint32_t) <= PS2_RAM_SIZE)
{
*reinterpret_cast<uint32_t *>(rdram + offset) = 0;
@@ -346,217 +430,235 @@ void cvFsSetDefDev(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
setReturnS32(ctx, 0);
}
namespace
{
int32_t g_cvMcFileCursor = 0;
constexpr int32_t kCvMcFreeCapacityBytes = 0x01000000;
constexpr int32_t kCvMcSaveCapacityBytes = 0x00080000;
constexpr int32_t kCvMcConfigCapacityBytes = 0x00008000;
constexpr int32_t kCvMcIconCapacityBytes = 0x00004000;
}
void mcCallMessageTypeSe(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCallMessageTypeSe", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mcCheckReadStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCheckReadStartConfigFile", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcCheckReadStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCheckReadStartSaveFile", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcCheckWriteStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCheckWriteStartConfigFile", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcCheckWriteStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCheckWriteStartSaveFile", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcCreateConfigInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCreateConfigInit", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcCreateFileSelectWindow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCreateFileSelectWindow", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcCreateIconInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCreateIconInit", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcCreateSaveFileInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcCreateSaveFileInit", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcDispFileName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDispFileName", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mcDispFileNumber(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDispFileNumber", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mcDisplayFileSelectWindow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDisplayFileSelectWindow", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mcDisplaySelectFileInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDisplaySelectFileInfo", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mcDisplaySelectFileInfoMesCount(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDisplaySelectFileInfoMesCount", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mcDispWindowCurSol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDispWindowCurSol", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mcDispWindowFoundtion(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcDispWindowFoundtion", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mceGetInfoApdx(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mceGetInfoApdx", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mceIntrReadFixAlign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mceIntrReadFixAlign", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mceStorePwd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mceStorePwd", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mcGetConfigCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetConfigCapacitySize", rdram, ctx, runtime);
setReturnS32(ctx, kCvMcConfigCapacityBytes);
}
void mcGetFileSelectWindowCursol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetFileSelectWindowCursol", rdram, ctx, runtime);
setReturnS32(ctx, g_cvMcFileCursor);
}
void mcGetFreeCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetFreeCapacitySize", rdram, ctx, runtime);
setReturnS32(ctx, kCvMcFreeCapacityBytes);
}
void mcGetIconCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetIconCapacitySize", rdram, ctx, runtime);
setReturnS32(ctx, kCvMcIconCapacityBytes);
}
void mcGetIconFileCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetIconFileCapacitySize", rdram, ctx, runtime);
setReturnS32(ctx, kCvMcIconCapacityBytes);
}
void mcGetPortSelectDirInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetPortSelectDirInfo", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mcGetSaveFileCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetSaveFileCapacitySize", rdram, ctx, runtime);
setReturnS32(ctx, kCvMcSaveCapacityBytes);
}
void mcGetStringEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcGetStringEnd", rdram, ctx, runtime);
const uint32_t strAddr = getRegU32(ctx, 4);
const std::string value = readPs2CStringBounded(rdram, runtime, strAddr, 1024);
setReturnU32(ctx, strAddr + static_cast<uint32_t>(value.size()));
}
void mcMoveFileSelectWindowCursor(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcMoveFileSelectWindowCursor", rdram, ctx, runtime);
const int32_t delta = static_cast<int32_t>(getRegU32(ctx, 5));
g_cvMcFileCursor += delta;
g_cvMcFileCursor = std::clamp(g_cvMcFileCursor, -1, 15);
setReturnS32(ctx, 0);
}
void mcNewCreateConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcNewCreateConfigFile", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcNewCreateIcon(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcNewCreateIcon", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcNewCreateSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcNewCreateSaveFile", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcReadIconData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcReadIconData", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcReadStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcReadStartConfigFile", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcReadStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcReadStartSaveFile", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcSelectFileInfoInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcSelectFileInfoInit", rdram, ctx, runtime);
g_cvMcFileCursor = 0;
setReturnS32(ctx, 1);
}
void mcSelectSaveFileCheck(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcSelectSaveFileCheck", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcSetFileSelectWindowCursol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcSetFileSelectWindowCursol", rdram, ctx, runtime);
g_cvMcFileCursor = static_cast<int32_t>(getRegU32(ctx, 5));
g_cvMcFileCursor = std::clamp(g_cvMcFileCursor, -1, 15);
setReturnS32(ctx, 0);
}
void mcSetFileSelectWindowCursolInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcSetFileSelectWindowCursolInit", rdram, ctx, runtime);
g_cvMcFileCursor = 0;
setReturnS32(ctx, 0);
}
void mcSetStringSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcSetStringSaveFile", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mcSetTyepWriteMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcSetTyepWriteMode", rdram, ctx, runtime);
setReturnS32(ctx, 0);
}
void mcWriteIconData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcWriteIconData", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcWriteStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcWriteStartConfigFile", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
void mcWriteStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
TODO_NAMED("mcWriteStartSaveFile", rdram, ctx, runtime);
setReturnS32(ctx, 1);
}
@@ -315,12 +315,14 @@ static uint32_t rpcAllocServerAddr(uint8_t *rdram)
struct IrqHandlerInfo
{
int id = 0;
uint32_t cause = 0;
uint32_t handler = 0;
uint32_t arg = 0;
uint32_t gp = 0;
uint32_t sp = 0;
bool enabled = true;
int order = 0;
};
static std::unordered_map<int, IrqHandlerInfo> g_intcHandlers;
@@ -328,6 +330,11 @@ static std::unordered_map<int, IrqHandlerInfo> g_dmacHandlers;
static int g_nextIntcHandlerId = 1;
static int g_nextDmacHandlerId = 1;
static int g_intc_head_order = 0;
static int g_intc_tail_order = 1000;
static int g_dmac_head_order = 0;
static int g_dmac_tail_order = 1000;
std::string translatePs2Path(const char *ps2Path)
{
if (!ps2Path || !*ps2Path)
@@ -13,7 +13,9 @@ namespace
static std::mutex g_irq_handler_mutex;
static std::mutex g_irq_worker_mutex;
static std::condition_variable g_irq_worker_cv;
static std::mutex g_vsync_flag_mutex;
static std::condition_variable g_vsync_cv;
static std::atomic<bool> g_irq_worker_stop{false};
static std::atomic<bool> g_irq_worker_running{false};
static uint32_t g_enabled_intc_mask = 0xFFFFFFFFu;
@@ -85,6 +87,9 @@ static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, ui
}
handlers.push_back(info);
}
std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b) {
return a.order < b.order;
});
}
for (const IrqHandlerInfo &info : handlers)
@@ -107,8 +112,15 @@ static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, ui
SET_GPR_U32(&irqCtx, 7, 0u);
irqCtx.pc = info.handler;
PS2Runtime::RecompiledFunction func = runtime->lookupFunction(info.handler);
func(rdram, &irqCtx, runtime);
while (irqCtx.pc != 0u && runtime && !runtime->isStopRequested())
{
PS2Runtime::RecompiledFunction step = runtime->lookupFunction(irqCtx.pc);
if (!step)
{
break;
}
step(rdram, &irqCtx, runtime);
}
}
catch (const ThreadExitException &)
{
@@ -126,16 +138,18 @@ static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, ui
}
}
static void signalVSyncFlag(uint8_t *rdram, uint64_t tickValue)
static uint64_t signalVSyncFlag(uint8_t *rdram)
{
VSyncFlagRegistration reg{};
uint64_t tickValue = 0u;
{
std::lock_guard<std::mutex> lock(g_vsync_flag_mutex);
reg = g_vsync_registration;
g_vsync_registration = {};
g_vsync_tick_counter = tickValue;
tickValue = ++g_vsync_tick_counter;
}
g_vsync_cv.notify_all();
if (reg.flagAddr != 0u)
{
writeGuestU32NoThrow(rdram, reg.flagAddr, 1u);
@@ -144,18 +158,25 @@ static void signalVSyncFlag(uint8_t *rdram, uint64_t tickValue)
{
writeGuestU64NoThrow(rdram, reg.tickAddr, tickValue);
}
return tickValue;
}
static void interruptWorkerMain(uint8_t *rdram, PS2Runtime *runtime)
{
g_currentThreadId = -1;
using clock = std::chrono::steady_clock;
auto nextTick = clock::now() + kVblankPeriod;
while (!g_irq_worker_stop.load(std::memory_order_acquire) &&
runtime != nullptr &&
!runtime->isStopRequested())
while (runtime != nullptr && !runtime->isStopRequested())
{
std::this_thread::sleep_until(nextTick);
{
std::unique_lock<std::mutex> lock(g_irq_worker_mutex);
if (g_irq_worker_cv.wait_until(lock, nextTick, []() { return g_irq_worker_stop.load(std::memory_order_acquire); }))
{
break;
}
}
const auto now = clock::now();
int ticksToProcess = 0;
@@ -171,19 +192,15 @@ static void interruptWorkerMain(uint8_t *rdram, PS2Runtime *runtime)
for (int i = 0; i < ticksToProcess; ++i)
{
uint64_t tickValue = 0u;
{
std::lock_guard<std::mutex> lock(g_vsync_flag_mutex);
tickValue = ++g_vsync_tick_counter;
}
signalVSyncFlag(rdram, tickValue);
signalVSyncFlag(rdram);
dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankStart);
std::this_thread::sleep_for(std::chrono::microseconds(500));
dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankEnd);
}
}
g_irq_worker_running.store(false, std::memory_order_release);
g_irq_worker_cv.notify_all();
}
static void ensureInterruptWorkerRunning(uint8_t *rdram, PS2Runtime *runtime)
@@ -214,10 +231,19 @@ static void ensureInterruptWorkerRunning(uint8_t *rdram, PS2Runtime *runtime)
void stopInterruptWorker()
{
g_irq_worker_stop.store(true, std::memory_order_release);
for (int i = 0; i < 100 && g_irq_worker_running.load(std::memory_order_acquire); ++i)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
g_irq_worker_cv.notify_all();
std::unique_lock<std::mutex> lock(g_irq_worker_mutex);
g_irq_worker_cv.wait_for(lock, std::chrono::milliseconds(500), []() {
return !g_irq_worker_running.load(std::memory_order_acquire);
});
}
void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime)
{
ensureInterruptWorkerRunning(rdram, runtime);
std::unique_lock<std::mutex> lock(g_vsync_flag_mutex);
uint64_t current = g_vsync_tick_counter;
g_vsync_cv.wait(lock, [current]() { return g_vsync_tick_counter > current; });
}
void SetVSyncFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
@@ -264,6 +290,7 @@ void AddIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
IrqHandlerInfo info{};
info.cause = getRegU32(ctx, 4);
info.handler = getRegU32(ctx, 5);
uint32_t next = getRegU32(ctx, 6);
info.arg = getRegU32(ctx, 7);
info.gp = getRegU32(ctx, 28);
info.sp = getRegU32(ctx, 29);
@@ -272,7 +299,9 @@ void AddIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
int handlerId = 0;
{
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
info.order = (next == 0) ? --g_intc_head_order : ++g_intc_tail_order;
handlerId = g_nextIntcHandlerId++;
info.id = handlerId;
g_intcHandlers[handlerId] = info;
}
@@ -282,11 +311,16 @@ void AddIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void RemoveIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t cause = getRegU32(ctx, 4);
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
if (handlerId > 0)
{
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
g_intcHandlers.erase(handlerId);
auto it = g_intcHandlers.find(handlerId);
if (it != g_intcHandlers.end() && it->second.cause == cause)
{
g_intcHandlers.erase(it);
}
}
setReturnS32(ctx, KE_OK);
}
@@ -296,6 +330,7 @@ void AddDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
IrqHandlerInfo info{};
info.cause = getRegU32(ctx, 4);
info.handler = getRegU32(ctx, 5);
uint32_t next = getRegU32(ctx, 6);
info.arg = getRegU32(ctx, 7);
info.gp = getRegU32(ctx, 28);
info.sp = getRegU32(ctx, 29);
@@ -304,7 +339,9 @@ void AddDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
int handlerId = 0;
{
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
info.order = (next == 0) ? --g_dmac_head_order : ++g_dmac_tail_order;
handlerId = g_nextDmacHandlerId++;
info.id = handlerId;
g_dmacHandlers[handlerId] = info;
}
setReturnS32(ctx, handlerId);
@@ -312,11 +349,16 @@ void AddDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void RemoveDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t cause = getRegU32(ctx, 4);
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
if (handlerId > 0)
{
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
g_dmacHandlers.erase(handlerId);
auto it = g_dmacHandlers.find(handlerId);
if (it != g_dmacHandlers.end() && it->second.cause == cause)
{
g_dmacHandlers.erase(it);
}
}
setReturnS32(ctx, KE_OK);
}
+104 -52
View File
@@ -66,7 +66,7 @@ void SifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void SifInitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
std::lock_guard<std::mutex> lock(g_rpc_mutex);
std::scoped_lock lock(g_rpc_mutex, g_dtx_rpc_mutex);
if (!g_rpc_initialized)
{
g_rpc_servers.clear();
@@ -75,11 +75,8 @@ void SifInitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
g_rpc_packet_index = 0;
g_rpc_server_index = 0;
g_rpc_active_queue = 0;
{
std::lock_guard<std::mutex> dtxLock(g_dtx_rpc_mutex);
g_dtx_remote_by_id.clear();
g_dtx_next_urpc_obj = kDtxUrpcObjBase;
}
g_dtx_remote_by_id.clear();
g_dtx_next_urpc_obj = kDtxUrpcObjBase;
g_rpc_initialized = true;
std::cout << "[SifInitRpc] Initialized" << std::endl;
}
@@ -169,25 +166,66 @@ void SifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
uint32_t endFunc = 0;
uint32_t endParam = 0;
// EE-side calls use extended arg registers:
// a0-a3 => r4-r7, arg5-arg8 => r8-r11, arg9 => stack + 0x0.
// Keep O32 stack-layout fallback for compatibility with other call sites.
// Decode both extended-reg convention (EE default) and standard O32 stack convention,
// picking REG whenever plausible, to avoid zero-collision on the stack.
uint32_t sp = getRegU32(ctx, 29);
sendSize = getRegU32(ctx, 8);
recvBuf = getRegU32(ctx, 9);
recvSize = getRegU32(ctx, 10);
endFunc = getRegU32(ctx, 11);
(void)readStackU32(rdram, sp, 0x0, endParam);
if (sendSize == 0 && recvBuf == 0 && recvSize == 0 && endFunc == 0)
uint32_t sendSizeReg = getRegU32(ctx, 8);
uint32_t recvBufReg = getRegU32(ctx, 9);
uint32_t recvSizeReg = getRegU32(ctx, 10);
uint32_t endFuncReg = getRegU32(ctx, 11);
uint32_t endParamReg = 0;
(void)readStackU32(rdram, sp, 0x0, endParamReg);
uint32_t sendSizeStk = 0;
uint32_t recvBufStk = 0;
uint32_t recvSizeStk = 0;
uint32_t endFuncStk = 0;
uint32_t endParamStk = 0;
(void)readStackU32(rdram, sp, 0x10, sendSizeStk);
(void)readStackU32(rdram, sp, 0x14, recvBufStk);
(void)readStackU32(rdram, sp, 0x18, recvSizeStk);
(void)readStackU32(rdram, sp, 0x1C, endFuncStk);
(void)readStackU32(rdram, sp, 0x20, endParamStk);
auto looksLikeGuestPtr = [&](uint32_t v) -> bool
{
readStackU32(rdram, sp, 0x10, sendSize);
readStackU32(rdram, sp, 0x14, recvBuf);
readStackU32(rdram, sp, 0x18, recvSize);
readStackU32(rdram, sp, 0x1C, endFunc);
readStackU32(rdram, sp, 0x20, endParam);
if (v == 0)
return true;
const uint32_t norm = v & 0x1FFFFFFFu;
return norm >= 0x10000u && norm < PS2_RAM_SIZE;
};
auto looksLikeSize = [&](uint32_t v) -> bool
{
return v <= 0x100000u;
};
auto looksLikeFunc = [&](uint32_t v) -> bool
{
return v == 0 || looksLikeGuestPtr(v);
};
auto plausiblePack = [&](uint32_t sendSz, uint32_t rbuf, uint32_t rsz, uint32_t endFn) -> bool
{
return looksLikeSize(sendSz) && looksLikeGuestPtr(rbuf) && looksLikeSize(rsz) && looksLikeFunc(endFn);
};
bool useRegConvention = true;
if (!plausiblePack(sendSizeReg, recvBufReg, recvSizeReg, endFuncReg))
{
if (plausiblePack(sendSizeStk, recvBufStk, recvSizeStk, endFuncStk))
{
useRegConvention = false;
}
}
sendSize = useRegConvention ? sendSizeReg : sendSizeStk;
recvBuf = useRegConvention ? recvBufReg : recvBufStk;
recvSize = useRegConvention ? recvSizeReg : recvSizeStk;
endFunc = useRegConvention ? endFuncReg : endFuncStk;
endParam = useRegConvention ? endParamReg : endParamStk;
t_SifRpcClientData *client = reinterpret_cast<t_SifRpcClientData *>(getMemPtr(rdram, clientPtr));
if (!client)
@@ -799,17 +837,22 @@ void SifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
if ((mode & kSifRpcModeNowait) != 0u)
{
(void)signalRpcCompletionSema(endParam);
uint32_t semaId = static_cast<uint32_t>(client->hdr.sema_id);
if (semaId == 0xFFFFFFFFu || semaId == 0u)
{
semaId = endParam;
}
(void)signalRpcCompletionSema(semaId);
}
}
if (recvBuf && recvSize > 0)
{
if (handled && resultPtr)
if (handled && resultPtr && resultPtr != recvBuf)
{
rpcCopyToRdram(rdram, recvBuf, resultPtr, recvSize);
}
else if (!handled && sendBuf && sendSize > 0)
else if (!handled && sendBuf && sendSize > 0 && sendBuf != recvBuf)
{
size_t copySize = (sendSize < recvSize) ? sendSize : recvSize;
rpcCopyToRdram(rdram, recvBuf, sendBuf, copySize);
@@ -850,22 +893,23 @@ void SifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
bool callbackInvoked = rpcInvokeFunction(rdram, ctx, runtime, endFunc, endParam, 0, 0, 0, nullptr);
// Some generated callsites may pass 0x2fac20/0x2fac30 instead of
// 0x2eac20/0x2eac30 for sound-driver RPC callbacks.
if (!callbackInvoked && (endFunc == 0x2fac20u || endFunc == 0x2fac30u))
{
const uint32_t normalizedEndFunc = endFunc - 0x10000u;
callbackInvoked = rpcInvokeFunction(rdram, ctx, runtime, normalizedEndFunc, endParam, 0, 0, 0, nullptr);
}
// Guard against callback dispatch gaps that would leak the semaphore
// acquired in SdrSendReq/SdrGetStateSend.
const bool isSoundRpcCallback =
(endFunc == 0x2eac20u || endFunc == 0x2eac30u ||
endFunc == 0x2fac20u || endFunc == 0x2fac30u);
if (isSoundRpcCallback)
{
(void)signalRpcCompletionSema(endParam);
uint32_t semaId = static_cast<uint32_t>(client->hdr.sema_id);
if (semaId == 0xFFFFFFFFu || semaId == 0u)
{
semaId = endParam;
}
(void)signalRpcCompletionSema(semaId);
if (rdram && (endFunc == 0x2eac30u || endFunc == 0x2fac30u))
{
constexpr uint32_t kSndBusyFlagAddr = 0x01E212C8u;
@@ -878,13 +922,18 @@ void SifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
if (!callbackInvoked)
{
const bool fallbackSignaledSema = signalRpcCompletionSema(endParam);
uint32_t semaId = static_cast<uint32_t>(client->hdr.sema_id);
if (semaId == 0xFFFFFFFFu || semaId == 0u)
{
semaId = endParam;
}
const bool fallbackSignaledSema = signalRpcCompletionSema(semaId);
static uint32_t unresolvedEndFuncWarnCount = 0;
if (unresolvedEndFuncWarnCount < 32u)
{
std::cerr << "[SifCallRpc] unresolved end callback endFunc=0x" << std::hex << endFunc
<< " endParam=0x" << endParam
<< " semaId=0x" << semaId
<< " fallbackSignal=" << std::dec << (fallbackSignaledSema ? 1 : 0)
<< std::endl;
++unresolvedEndFuncWarnCount;
@@ -954,38 +1003,39 @@ void SifRegisterRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
sd->link = 0;
sd->next = 0;
if (qd)
{
t_SifRpcDataQueue *queue = reinterpret_cast<t_SifRpcDataQueue *>(getMemPtr(rdram, qd));
if (queue)
std::lock_guard<std::mutex> lock(g_rpc_mutex);
if (qd)
{
if (!queue->link)
t_SifRpcDataQueue *queue = reinterpret_cast<t_SifRpcDataQueue *>(getMemPtr(rdram, qd));
if (queue)
{
queue->link = sdPtr;
}
else
{
uint32_t curPtr = queue->link;
for (int guard = 0; guard < 1024 && curPtr; ++guard)
if (!queue->link)
{
t_SifRpcServerData *cur = reinterpret_cast<t_SifRpcServerData *>(getMemPtr(rdram, curPtr));
if (!cur)
break;
if (!cur->link)
queue->link = sdPtr;
}
else
{
uint32_t curPtr = queue->link;
for (int guard = 0; guard < 1024 && curPtr; ++guard)
{
cur->link = sdPtr;
break;
t_SifRpcServerData *cur = reinterpret_cast<t_SifRpcServerData *>(getMemPtr(rdram, curPtr));
if (!cur)
break;
if (!cur->link)
{
cur->link = sdPtr;
break;
}
if (cur->link == sdPtr)
break;
curPtr = cur->link;
}
if (cur->link == sdPtr)
break;
curPtr = cur->link;
}
}
}
}
{
std::lock_guard<std::mutex> lock(g_rpc_mutex);
g_rpc_servers[sid] = {sid, sdPtr};
for (auto &entry : g_rpc_clients)
{
@@ -1122,6 +1172,8 @@ void SifRemoveRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
return;
}
std::lock_guard<std::mutex> lock(g_rpc_mutex);
if (qd->link == sdPtr)
{
t_SifRpcServerData *sd = reinterpret_cast<t_SifRpcServerData *>(getMemPtr(rdram, sdPtr));
@@ -50,7 +50,7 @@ void FlushCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void ResetEE(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
std::cerr << "Syscall: ResetEE - requesting runtime stop" << std::endl;
std::cerr << "Syscall: ResetEE - requesting runtime stop" << std::endl;
runtime->requestStop();
setReturnS32(ctx, KE_OK);
}
@@ -194,7 +194,7 @@ void DeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
uint32_t autoStackToFree = 0;
{
std::lock_guard<std::mutex> lock(info->m);
if (info->status != THS_DORMANT)
if (info->started || info->status != THS_DORMANT)
{
setReturnS32(ctx, KE_NOT_DORMANT);
return;
@@ -346,6 +346,13 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
while (runtime && !runtime->isStopRequested())
{
if (info->terminated.load(std::memory_order_relaxed))
{
throw ThreadExitException();
}
waitWhileSuspended(info);
const uint32_t pc = threadCtx->pc;
if (pc == 0u)
{
@@ -374,6 +381,12 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
}
PS2Runtime::RecompiledFunction step = runtime->lookupFunction(pc);
if (!step)
{
std::cerr << "[StartThread] id=" << tid << " missing function for pc=0x"
<< std::hex << pc << std::dec << std::endl;
throw ThreadExitException();
}
step(rdram, threadCtx, runtime);
}
}
@@ -430,6 +443,9 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
runtime->guestFree(detachedAutoStack);
}
// Notify anybody waiting for termination (like TerminateThread)
info->cv.notify_all();
g_activeThreads.fetch_sub(1, std::memory_order_relaxed);
});
worker.detach();
@@ -463,7 +479,6 @@ void ExitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
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;
@@ -485,7 +500,6 @@ void ExitDeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
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;
@@ -523,10 +537,6 @@ void TerminateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
}
info->terminated = true;
info->forceRelease = true;
info->status = THS_DORMANT;
info->waitType = TSW_NONE;
info->waitId = 0;
info->wakeupCount = 0;
}
info->cv.notify_all();
@@ -535,6 +545,15 @@ void TerminateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
runExitHandlersForThread(tid, rdram, ctx, runtime);
throw ThreadExitException();
}
else
{
// Block until the target thread actually finishes unwinding and becomes dormant
std::unique_lock<std::mutex> lock(info->m);
info->cv.wait(lock, [&]() {
return !info->started && info->status == THS_DORMANT;
});
}
setReturnS32(ctx, KE_OK);
}
@@ -889,6 +908,9 @@ void RotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runti
setReturnS32(ctx, KE_ILLEGAL_PRIORITY);
return;
}
std::this_thread::yield();
setReturnS32(ctx, KE_OK);
}
+24 -24
View File
@@ -133,7 +133,7 @@ void register_code_generator_tests()
instructions.push_back(makeNop(0x100c)); // branch target
instructions.push_back(makeNop(0x1010)); // extra
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("emits labels and gotos for internal branches", generated);
@@ -156,7 +156,7 @@ void register_code_generator_tests()
instructions.push_back(makeNop(0x2004)); // delay slot and target
instructions.push_back(makeNop(0x2008)); // extra
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("labels delay slot when it is a branch target", generated);
@@ -180,7 +180,7 @@ void register_code_generator_tests()
instructions.push_back(br);
instructions.push_back(makeNop(0x3004)); // delay slot
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("branches outside function still set pc", generated);
@@ -212,7 +212,7 @@ void register_code_generator_tests()
std::vector<Instruction> instructions{j, delay, makeNop(0x4008)};
CodeGenerator gen({targetSym});
CodeGenerator gen({targetSym}, {});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("jumps to known symbols call by name", generated);
@@ -238,7 +238,7 @@ void register_code_generator_tests()
std::vector<Instruction> instructions{j, delay};
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("jump to unknown target sets pc", generated);
@@ -262,7 +262,7 @@ void register_code_generator_tests()
Instruction inst{};
inst.opcode = OPCODE_REGIMM;
CodeGenerator gen({});
CodeGenerator gen({}, {});
gen.setRenamedFunctions({{0x8000, "renamed_target"}});
std::string sw = gen.generateJumpTableSwitch(inst, 0x0, entries);
@@ -295,7 +295,7 @@ void register_code_generator_tests()
std::vector<Instruction> instructions{j, delay};
CodeGenerator gen({targetSym});
CodeGenerator gen({targetSym}, {});
gen.setRenamedFunctions({{targetSym.address, "ps2___is_pointer"}});
std::string generated = gen.generateFunction(func, instructions, false);
@@ -308,7 +308,7 @@ void register_code_generator_tests()
});
tc.Run("COP0 MFC0/MTC0 translate to COP0 register access", [](TestCase &t) {
CodeGenerator gen({});
CodeGenerator gen({}, {});
Instruction mfc0{};
mfc0.opcode = OPCODE_COP0;
@@ -318,7 +318,7 @@ void register_code_generator_tests()
std::string mfc0Code = gen.translateInstruction(mfc0);
printGeneratedCode("COP0 MFC0/MTC0 translate to COP0 register access (MFC0)", mfc0Code);
t.IsTrue(mfc0Code.find("SET_GPR_U32(ctx, 5") != std::string::npos, "MFC0 should write to rt");
t.IsTrue(mfc0Code.find("SET_GPR_S32(ctx, 5") != std::string::npos, "MFC0 should write to rt");
t.IsTrue(mfc0Code.find("ctx->cop0_status") != std::string::npos, "MFC0 STATUS should read cop0_status");
t.IsTrue(mfc0Code.find("Unimplemented COP0 register") == std::string::npos, "MFC0 should not hit unimplemented COP0 register path");
t.IsTrue(mfc0Code.find("Unhandled COP0") == std::string::npos, "MFC0 should not hit unhandled COP0 path");
@@ -338,7 +338,7 @@ void register_code_generator_tests()
});
tc.Run("FCR access uses CFC1/CTC1", [](TestCase &t) {
CodeGenerator gen({});
CodeGenerator gen({}, {});
Instruction cfc1{};
cfc1.opcode = OPCODE_COP1;
@@ -366,7 +366,7 @@ void register_code_generator_tests()
});
tc.Run("VU CReg access uses CFC2/CTC2", [](TestCase &t) {
CodeGenerator gen({});
CodeGenerator gen({}, {});
Instruction cfc2{};
cfc2.opcode = OPCODE_COP2;
@@ -408,7 +408,7 @@ void register_code_generator_tests()
t.IsTrue(!s1.empty(), "VU0_S1 enum list should not be empty");
t.IsTrue(!s2.empty(), "VU0_S2 enum list should not be empty");
CodeGenerator gen({});
CodeGenerator gen({}, {});
for (uint32_t value : s1)
{
@@ -457,7 +457,7 @@ void register_code_generator_tests()
inst.function = VU0_S1_VADD;
inst.vectorInfo.vectorField = 0xF;
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string out = gen.translateInstruction(inst);
t.IsTrue(out.find("ctx->vu0_vf[11]") != std::string::npos, "S1 fs should come from rd");
@@ -476,7 +476,7 @@ void register_code_generator_tests()
inst.function = VU0_S1_VADDq;
inst.vectorInfo.vectorField = 0x9;
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string out = gen.translateInstruction(inst);
t.IsTrue(out.find("_mm_blendv_ps") != std::string::npos, "S1 q/i form should honor destination mask");
@@ -498,7 +498,7 @@ void register_code_generator_tests()
uint32_t lower = VU0_S2_VABS & 0x3;
inst.raw = (upper << 6) | lower;
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string out = gen.translateInstruction(inst);
t.IsTrue(out.find("ctx->vu0_vf[12]") != std::string::npos, "S2 source VF should come from rd");
@@ -519,7 +519,7 @@ void register_code_generator_tests()
uint32_t lower = VU0_S2_VLQI & 0x3;
inst.raw = (upper << 6) | lower;
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string out = gen.translateInstruction(inst);
t.IsTrue(out.find("ctx->vi[14]") != std::string::npos, "S2 VLQI base VI should come from rd");
@@ -545,7 +545,7 @@ void register_code_generator_tests()
Instruction jal = makeJal(0xA000, 0xB000);
Instruction delay = makeNop(0xA004);
CodeGenerator gen({targetSym});
CodeGenerator gen({targetSym}, {});
std::string generated = gen.generateFunction(func, {jal, delay}, false);
printGeneratedCode("JAL to known function emits call and check", generated);
@@ -581,7 +581,7 @@ void register_code_generator_tests()
Instruction delay = makeNop(0xC004);
Instruction targetInst = makeNop(0xC010);
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string generated = gen.generateFunction(func, {jal, delay, targetInst}, false);
printGeneratedCode("JAL to internal target becomes goto", generated);
@@ -602,7 +602,7 @@ void register_code_generator_tests()
Instruction jalr = makeJalr(0xD000, 4, 31);
Instruction delay = makeNop(0xD004);
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string generated = gen.generateFunction(func, {jalr, delay}, false);
printGeneratedCode("JALR emits indirect call", generated);
@@ -638,7 +638,7 @@ void register_code_generator_tests()
instructions.push_back(makeNop(0x1108));
instructions.push_back(makeNop(0x110c));
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("backward BEQ emits label and goto (sign-extended offset)", generated);
@@ -674,7 +674,7 @@ void register_code_generator_tests()
Instruction target = makeNop(0x1208);
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string generated = gen.generateFunction(func, { br, delay, target }, false);
printGeneratedCode("branch-likely places delay slot only in taken path", generated);
@@ -700,7 +700,7 @@ void register_code_generator_tests()
Instruction jr = makeJr(0x1314, 31);
Instruction jrDelay = makeNop(0x1318);
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string generated = gen.generateFunction(func, { jal, jalDelay, atReturn, atTarget, jr, jrDelay }, false);
printGeneratedCode("JR $31 emits switch for internal return targets", generated);
@@ -725,7 +725,7 @@ void register_code_generator_tests()
Instruction delay = makeNop(0x1408);
Instruction i3 = makeNop(0x140c);
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string generated = gen.generateFunction(func, {i0, jr, delay, i3}, false);
printGeneratedCode("JR non-RA emits switch for in-function jump targets", generated);
@@ -753,7 +753,7 @@ void register_code_generator_tests()
Instruction jalr = makeJalr(0x1514, 4, 31);
Instruction jalrDelay = makeNop(0x1518);
CodeGenerator gen({});
CodeGenerator gen({}, {});
std::string generated = gen.generateFunction(func, {jal, jalDelay, atReturn, atTarget, jalr, jalrDelay}, false);
printGeneratedCode("JALR includes switch and fallback/guard pair", generated);