mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
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:
@@ -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> §ions);
|
||||
~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);
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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); \
|
||||
} \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user