From 8584c0613ac0ab0b99e11d640f3f02f6d88ae960 Mon Sep 17 00:00:00 2001 From: Ranieri Date: Sun, 22 Feb 2026 02:20:42 -0300 Subject: [PATCH] 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 --- ps2xRecomp/include/ps2recomp/code_generator.h | 30 +- ps2xRecomp/include/ps2recomp/types.h | 1 + ps2xRecomp/src/lib/code_generator.cpp | 950 ++++++++++----- ps2xRecomp/src/lib/ps2_recompiler.cpp | 2 +- ps2xRecomp/src/lib/r5900_decoder.cpp | 8 + ps2xRuntime/CMakeLists.txt | 3 + ps2xRuntime/include/ps2_gs_gpu.h | 62 + ps2xRuntime/include/ps2_memory.h | 45 +- ps2xRuntime/include/ps2_runtime.h | 62 +- ps2xRuntime/include/ps2_runtime_macros.h | 145 ++- ps2xRuntime/include/ps2_syscalls.h | 4 +- ps2xRuntime/src/lib/ps2_gs_gpu.cpp | 141 +++ ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp | 1039 +++++++++++++++++ ps2xRuntime/src/lib/ps2_memory.cpp | 292 ++++- ps2xRuntime/src/lib/ps2_runtime.cpp | 58 +- ps2xRuntime/src/lib/ps2_stubs.cpp | 1 + ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp | 268 +++++ .../lib/stubs/helpers/ps2_stubs_helpers.inl | 2 +- ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl | 53 +- ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl | 737 ++++++++++-- .../lib/stubs/ps2_stubs_residentEvilCV.inl | 290 +++-- .../helpers/ps2_syscalls_helpers_runtime.inl | 7 + .../lib/syscalls/ps2_syscalls_interrupt.inl | 86 +- .../src/lib/syscalls/ps2_syscalls_rpc.inl | 156 ++- .../src/lib/syscalls/ps2_syscalls_thread.inl | 38 +- ps2xTest/src/code_generator_tests.cpp | 48 +- 26 files changed, 3849 insertions(+), 679 deletions(-) create mode 100644 ps2xRuntime/include/ps2_gs_gpu.h create mode 100644 ps2xRuntime/src/lib/ps2_gs_gpu.cpp create mode 100644 ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp create mode 100644 ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp diff --git a/ps2xRecomp/include/ps2recomp/code_generator.h b/ps2xRecomp/include/ps2recomp/code_generator.h index d56cdfd..9d4960a 100644 --- a/ps2xRecomp/include/ps2recomp/code_generator.h +++ b/ps2xRecomp/include/ps2recomp/code_generator.h @@ -14,13 +14,14 @@ namespace ps2recomp struct Instruction; struct Function; struct Symbol; + struct Section; extern const std::unordered_set kKeywords; class CodeGenerator { public: - explicit CodeGenerator(const std::vector &symbols); + explicit CodeGenerator(const std::vector &symbols, const std::vector
§ions); ~CodeGenerator(); struct BootstrapInfo @@ -33,21 +34,28 @@ namespace ps2recomp std::string entryName; }; + struct AnalysisResult { + std::unordered_set entryPoints; + std::unordered_map> jumpTableTargets; + }; + std::string generateFunction(const Function &function, const std::vector &instructions, const bool &useHeaders); std::string generateFunctionRegistration(const std::vector &functions, const std::map &stubs); std::string handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot, - const Function &function, const std::unordered_set &internalTargets); + const Function &function, const AnalysisResult &analysisResult); void setRenamedFunctions(const std::unordered_map &renames); void setBootstrapInfo(const BootstrapInfo &info); void setRelocationCallNames(const std::unordered_map &callNames); - std::unordered_set collectInternalBranchTargets(const Function &function, - const std::vector &instructions); + + AnalysisResult collectInternalBranchTargets(const Function &function, + const std::vector &instructions); public: std::unordered_map m_symbols; std::unordered_map m_renamedFunctions; std::unordered_map m_relocationCallNames; + const std::vector
& 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); diff --git a/ps2xRecomp/include/ps2recomp/types.h b/ps2xRecomp/include/ps2recomp/types.h index 62464f0..0e825f0 100644 --- a/ps2xRecomp/include/ps2recomp/types.h +++ b/ps2xRecomp/include/ps2recomp/types.h @@ -45,6 +45,7 @@ namespace ps2recomp bool isMmio = false; uint32_t mmioAddress = 0; + std::string disassembly; struct { diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index 7123977..34541e5 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -98,7 +99,8 @@ namespace ps2recomp return kKeywords.contains(name); } - CodeGenerator::CodeGenerator(const std::vector &symbols) + CodeGenerator::CodeGenerator(const std::vector &symbols, const std::vector
§ions) + : m_sections(sections) { for (auto &symbol : symbols) { @@ -164,8 +166,9 @@ namespace ps2recomp const Instruction &branchInst, const Instruction &delaySlot, const Function &function, - const std::unordered_set &internalTargets) + const AnalysisResult &analysisResult) { + const std::unordered_set &internalTargets = analysisResult.entryPoints; std::stringstream ss; const bool hasValidDelaySlot = !(delaySlot.opcode == OPCODE_SPECIAL && @@ -174,7 +177,18 @@ namespace ps2recomp delaySlot.rt == 0 && delaySlot.sa == 0); - const std::string delaySlotCode = hasValidDelaySlot ? translateInstruction(delaySlot) : ""; + std::string delaySlotCode = ""; + std::string delaySlotPrefix = ""; + std::string delaySlotSuffix = ""; + if (hasValidDelaySlot) { + delaySlotPrefix = "ctx->in_delay_slot = true; ctx->branch_pc = 0x" + fmt::format("{:X}", branchInst.address) + "u;\n "; + delaySlotCode = " // 0x" + fmt::format("{:x}", delaySlot.address) + ": 0x" + fmt::format("{:x}", delaySlot.raw); + if (!delaySlot.disassembly.empty()) { + delaySlotCode += " " + delaySlot.disassembly; + } + delaySlotCode += " (Delay Slot)\n " + translateInstruction(delaySlot); + delaySlotSuffix = "\n ctx->in_delay_slot = false;"; + } const uint8_t rs_reg = branchInst.rs; const uint8_t rt_reg = branchInst.rt; @@ -189,12 +203,18 @@ namespace ps2recomp (branchInst.function == SPECIAL_JR || branchInst.function == SPECIAL_JALR) && !internalTargets.empty()) { - sortedInternalTargets.reserve(internalTargets.size()); - for (uint32_t t : internalTargets) - { - sortedInternalTargets.push_back(t); + auto jtIt = analysisResult.jumpTableTargets.find(branchInst.address); + if (jtIt != analysisResult.jumpTableTargets.end()) { + sortedInternalTargets = jtIt->second; + std::sort(sortedInternalTargets.begin(), sortedInternalTargets.end()); + } else { + sortedInternalTargets.reserve(internalTargets.size()); + for (uint32_t t : internalTargets) + { + sortedInternalTargets.push_back(t); + } + std::sort(sortedInternalTargets.begin(), sortedInternalTargets.end()); } - std::sort(sortedInternalTargets.begin(), sortedInternalTargets.end()); } if (internalTargets.contains(delayPc)) @@ -236,7 +256,7 @@ namespace ps2recomp if (hasValidDelaySlot) { ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); - ss << " " << delaySlotCode << "\n"; + ss << " " << delaySlotPrefix << delaySlotCode << delaySlotSuffix << "\n"; } const uint32_t target = buildAbsoluteJumpTarget(branchInst.address, branchInst.target); @@ -253,19 +273,33 @@ namespace ps2recomp if (!funcName.empty()) { + ss << fmt::format(" if (runtime->hasFunction(0x{:X}u)) {{\n", target); + ss << fmt::format(" auto targetFn = runtime->lookupFunction(0x{:X}u);\n", target); if (branchInst.opcode == OPCODE_J) { - ss << " " << funcName << "(rdram, ctx, runtime); return;\n"; + ss << " targetFn(rdram, ctx, runtime); return;\n"; + } + else + { + ss << " const uint32_t __entryPc = ctx->pc;\n"; + ss << " targetFn(rdram, ctx, runtime);\n"; + ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); + } + ss << " } else {\n"; + + if (branchInst.opcode == OPCODE_J) + { + ss << " " << funcName << "(rdram, ctx, runtime); return;\n"; } else { - ss << " {\n"; ss << " const uint32_t __entryPc = ctx->pc;\n"; ss << " " << funcName << "(rdram, ctx, runtime);\n"; ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); - ss << " }\n"; - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); } + ss << " }\n"; } else { @@ -339,7 +373,7 @@ namespace ps2recomp if (hasValidDelaySlot) { ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); - ss << " " << delaySlotCode << "\n"; + ss << " " << delaySlotPrefix << delaySlotCode << delaySlotSuffix << "\n"; } ss << " ctx->pc = jumpTarget;\n"; @@ -378,15 +412,16 @@ namespace ps2recomp else if (branchInst.isBranch) { std::string conditionStr = "false"; - std::string linkCode; + std::string conditionalLinkCode; + std::string unconditionalLinkCode; switch (branchInst.opcode) { case OPCODE_BEQ: - conditionStr = fmt::format("GPR_U32(ctx, {}) == GPR_U32(ctx, {})", rs_reg, rt_reg); + conditionStr = fmt::format("GPR_U64(ctx, {}) == GPR_U64(ctx, {})", rs_reg, rt_reg); break; case OPCODE_BNE: - conditionStr = fmt::format("GPR_U32(ctx, {}) != GPR_U32(ctx, {})", rs_reg, rt_reg); + conditionStr = fmt::format("GPR_U64(ctx, {}) != GPR_U64(ctx, {})", rs_reg, rt_reg); break; case OPCODE_BLEZ: conditionStr = fmt::format("GPR_S32(ctx, {}) <= 0", rs_reg); @@ -395,10 +430,10 @@ namespace ps2recomp conditionStr = fmt::format("GPR_S32(ctx, {}) > 0", rs_reg); break; case OPCODE_BEQL: - conditionStr = fmt::format("GPR_U32(ctx, {}) == GPR_U32(ctx, {})", rs_reg, rt_reg); + conditionStr = fmt::format("GPR_U64(ctx, {}) == GPR_U64(ctx, {})", rs_reg, rt_reg); break; case OPCODE_BNEL: - conditionStr = fmt::format("GPR_U32(ctx, {}) != GPR_U32(ctx, {})", rs_reg, rt_reg); + conditionStr = fmt::format("GPR_U64(ctx, {}) != GPR_U64(ctx, {})", rs_reg, rt_reg); break; case OPCODE_BLEZL: conditionStr = fmt::format("GPR_S32(ctx, {}) <= 0", rs_reg); @@ -423,19 +458,19 @@ namespace ps2recomp break; case REGIMM_BLTZAL: conditionStr = fmt::format("GPR_S32(ctx, {}) < 0", rs_reg); - linkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); + unconditionalLinkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); break; case REGIMM_BGEZAL: conditionStr = fmt::format("GPR_S32(ctx, {}) >= 0", rs_reg); - linkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); + unconditionalLinkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); break; case REGIMM_BLTZALL: conditionStr = fmt::format("GPR_S32(ctx, {}) < 0", rs_reg); - linkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); + conditionalLinkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); break; case REGIMM_BGEZALL: conditionStr = fmt::format("GPR_S32(ctx, {}) >= 0", rs_reg); - linkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); + conditionalLinkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); break; default: break; @@ -482,17 +517,22 @@ namespace ps2recomp ss << " {\n"; ss << " const bool " << branchTakenVar << " = (" << conditionStr << ");\n"; + if (!unconditionalLinkCode.empty()) + { + ss << " " << unconditionalLinkCode << "\n"; + } + if (isLikely) { ss << " if (" << branchTakenVar << ") {\n"; - if (!linkCode.empty()) + if (!conditionalLinkCode.empty()) { - ss << " " << linkCode << "\n"; + ss << " " << conditionalLinkCode << "\n"; } if (hasValidDelaySlot) { ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); - ss << " " << delaySlotCode << "\n"; + ss << " " << delaySlotPrefix << delaySlotCode << delaySlotSuffix << "\n"; } if (internalTargets.contains(target)) @@ -510,15 +550,15 @@ namespace ps2recomp } else { - if (!linkCode.empty()) + if (!conditionalLinkCode.empty()) { - ss << " if (" << branchTakenVar << ") { " << linkCode << " }\n"; + ss << " if (" << branchTakenVar << ") { " << conditionalLinkCode << " }\n"; } if (hasValidDelaySlot) { ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); - ss << " " << delaySlotCode << "\n"; + ss << " " << delaySlotPrefix << delaySlotCode << delaySlotSuffix << "\n"; } ss << " if (" << branchTakenVar << ") {\n"; @@ -543,7 +583,7 @@ namespace ps2recomp if (hasValidDelaySlot) { ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); - ss << " " << delaySlotCode << "\n"; + ss << " " << delaySlotPrefix << delaySlotCode << delaySlotSuffix << "\n"; } } @@ -559,13 +599,14 @@ namespace ps2recomp CodeGenerator::~CodeGenerator() = default; - std::unordered_set CodeGenerator::collectInternalBranchTargets( + CodeGenerator::AnalysisResult CodeGenerator::collectInternalBranchTargets( const Function &function, const std::vector &instructions) { - std::unordered_set targets; + AnalysisResult result; std::unordered_set instructionAddresses; instructionAddresses.reserve(instructions.size()); bool hasIndirectRegisterJump = false; + std::vector indirectJumps; for (const auto &inst : instructions) { @@ -575,6 +616,7 @@ namespace ps2recomp inst.function == SPECIAL_JALR)) { hasIndirectRegisterJump = true; + indirectJumps.push_back(&inst); } } @@ -590,7 +632,7 @@ namespace ps2recomp if (target >= function.start && target < function.end && instructionAddresses.contains(target)) { - targets.insert(target); + result.entryPoints.insert(target); } } else if (isStaticJump) @@ -599,7 +641,7 @@ namespace ps2recomp if (target >= function.start && target < function.end && instructionAddresses.contains(target)) { - targets.insert(target); + result.entryPoints.insert(target); if (inst.opcode == OPCODE_JAL) { @@ -607,7 +649,7 @@ namespace ps2recomp if (returnAddr >= function.start && returnAddr < function.end && instructionAddresses.contains(returnAddr)) { - targets.insert(returnAddr); + result.entryPoints.insert(returnAddr); } } } @@ -616,16 +658,152 @@ namespace ps2recomp if (hasIndirectRegisterJump) { - for (uint32_t addr : instructionAddresses) - { - if (addr >= function.start && addr < function.end) + bool hasFallback = false; + for (const Instruction* jrInst : indirectJumps) { + bool foundTable = false; + + uint32_t jrReg = jrInst->rs; + + int lwIndex = -1; + uint32_t baseReg = 0; + int32_t lwOffset = 0; + + auto it = std::find_if(instructions.begin(), instructions.end(), [&](const Instruction& inst) { return inst.address == jrInst->address; }); + if (it != instructions.end()) { + int jrIndex = std::distance(instructions.begin(), it); + for (int i = jrIndex - 1; i >= 0 && i >= jrIndex - 20; --i) { + const auto& inst = instructions[i]; + if ((inst.opcode == OPCODE_LW || inst.opcode == OPCODE_LWU) && inst.rt == jrReg) { + lwIndex = i; + baseReg = inst.rs; + lwOffset = inst.simmediate; + break; + } + } + + if (lwIndex != -1) { + int adduIndex = -1; + uint32_t tableBaseReg = 0; + uint32_t indexReg = 0; + for (int i = lwIndex - 1; i >= 0 && i >= lwIndex - 10; --i) { + const auto& inst = instructions[i]; + if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_ADDU && inst.rd == baseReg) { + adduIndex = i; + tableBaseReg = inst.rs; + indexReg = inst.rt; + break; + } + } + + uint32_t tableAddress = 0; + bool foundTableAddress = false; + + if (adduIndex != -1) { + for (int i = adduIndex - 1; i >= 0 && i >= adduIndex - 20; --i) { + const auto& inst = instructions[i]; + if (inst.opcode == OPCODE_LUI) { + if (inst.rt == tableBaseReg || inst.rt == indexReg) { + uint32_t high = inst.immediate << 16; + uint32_t low = 0; + for (int j = i + 1; j < adduIndex; ++j) { + const auto& lowInst = instructions[j]; + if (lowInst.rs == inst.rt && lowInst.rt == inst.rt) { + if (lowInst.opcode == OPCODE_ADDIU) { + low = (uint32_t)lowInst.simmediate; + } else if (lowInst.opcode == OPCODE_ORI) { + low = lowInst.immediate; + } + } + } + tableAddress = high + low; + foundTableAddress = true; + break; + } + } + } + } + + if (foundTableAddress) { + tableAddress += lwOffset; + + uint32_t unshiftedIndexReg = 0; + for (int i = adduIndex - 1; i >= 0 && i >= adduIndex - 10; --i) { + const auto& inst = instructions[i]; + if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_SLL && (inst.rd == tableBaseReg || inst.rd == indexReg)) { + unshiftedIndexReg = inst.rt; + break; + } + } + + uint32_t numCases = 0; + if (unshiftedIndexReg != 0) { + for (int i = adduIndex - 1; i >= 0 && i >= adduIndex - 30; --i) { + const auto& inst = instructions[i]; + if ((inst.opcode == OPCODE_SLTIU || inst.opcode == OPCODE_SLTI) && inst.rs == unshiftedIndexReg) { + numCases = inst.immediate; + break; + } + } + } + + if (numCases > 0 && numCases <= 1000) { + const Section* rodata = nullptr; + for (const auto& sec : m_sections) { + if (tableAddress >= sec.address && tableAddress < sec.address + sec.size) { + rodata = &sec; + break; + } + } + + if (rodata && rodata->data) { + std::vector jrTargets; + bool validJumpTable = true; + std::unordered_set uniqueTargets; + for (uint32_t i = 0; i < numCases; ++i) { + uint32_t addr = tableAddress + i * 4; + if (addr >= rodata->address && addr + 4 <= rodata->address + rodata->size) { + uint32_t target = 0; + std::memcpy(&target, rodata->data + (addr - rodata->address), 4); + if (target >= function.start && target < function.end && instructionAddresses.contains(target)) { + if (!uniqueTargets.contains(target)) { + jrTargets.push_back(target); + uniqueTargets.insert(target); + } + } + } else { + validJumpTable = false; + break; + } + } + if (validJumpTable && !jrTargets.empty()) { + result.jumpTableTargets[jrInst->address] = jrTargets; + for (uint32_t t : jrTargets) { + result.entryPoints.insert(t); + } + foundTable = true; + } + } + } + } + } + } + if (!foundTable) { + hasFallback = true; + } + } + + if (hasFallback) { + for (uint32_t addr : instructionAddresses) { - targets.insert(addr); + if (addr >= function.start && addr < function.end) + { + result.entryPoints.insert(addr); + } } } } - return targets; + return result; } std::string ps2recomp::CodeGenerator::generateFunction( @@ -645,7 +823,8 @@ namespace ps2recomp ss << "#include \"ps2_stubs.h\"\n\n"; } - std::unordered_set internalTargets = collectInternalBranchTargets(function, instructions); + AnalysisResult analysisResult = collectInternalBranchTargets(function, instructions); + const std::unordered_set& internalTargets = analysisResult.entryPoints; ss << "// Function: " << function.name << "\n"; ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n"; @@ -671,7 +850,11 @@ namespace ps2recomp ss << "label_" << std::hex << inst.address << std::dec << ":\n"; } - ss << " // 0x" << std::hex << inst.address << ": 0x" << inst.raw << std::dec << "\n"; + ss << " // 0x" << std::hex << inst.address << ": 0x" << inst.raw << std::dec; + if (!inst.disassembly.empty()) { + ss << " " << inst.disassembly; + } + ss << "\n"; try { @@ -684,7 +867,7 @@ namespace ps2recomp ss << "label_" << std::hex << delaySlot.address << std::dec << ":\n"; } - ss << handleBranchDelaySlots(inst, delaySlot, function, internalTargets); + ss << handleBranchDelaySlots(inst, delaySlot, function, analysisResult); ++i; // Skip delay slot instruction (handled inside branch logic) } @@ -768,25 +951,25 @@ namespace ps2recomp case OPCODE_ADDIU: if (inst.rt == 0) return "// NOP (addiu $zero, ...)"; - return fmt::format("SET_GPR_S32(ctx, {}, ADD32(GPR_U32(ctx, {}), {}));", inst.rt, inst.rs, inst.simmediate); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ADD32(GPR_U32(ctx, {}), {}));", inst.rt, inst.rs, inst.simmediate); case OPCODE_SLTI: - return fmt::format("SET_GPR_U32(ctx, {}, SLT32(GPR_S32(ctx, {}), {}));", inst.rt, inst.rs, inst.simmediate); + return fmt::format("SET_GPR_U64(ctx, {}, ((int64_t)GPR_S64(ctx, {}) < (int64_t)(int32_t){}) ? 1 : 0);", inst.rt, inst.rs, inst.simmediate); case OPCODE_SLTIU: - return fmt::format("SET_GPR_U32(ctx, {}, SLTU32(GPR_U32(ctx, {}), {}));", inst.rt, inst.rs, inst.immediate); + return fmt::format("SET_GPR_U64(ctx, {}, ((uint64_t)GPR_U64(ctx, {}) < (uint64_t)(int64_t)(int32_t){}) ? 1 : 0);", inst.rt, inst.rs, inst.simmediate); case OPCODE_ANDI: - return fmt::format("SET_GPR_U32(ctx, {}, AND32(GPR_U32(ctx, {}), {}));", inst.rt, inst.rs, inst.immediate); + return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PAND(GPR_VEC(ctx, {}), _mm_cvtsi32_si128((int){}{})));", inst.rt, inst.rs, inst.immediate, "u"); case OPCODE_ORI: - return fmt::format("SET_GPR_U32(ctx, {}, OR32(GPR_U32(ctx, {}), {}));", inst.rt, inst.rs, inst.immediate); + return fmt::format("SET_GPR_VEC(ctx, {}, PS2_POR(GPR_VEC(ctx, {}), _mm_cvtsi32_si128((int){}{})));", inst.rt, inst.rs, inst.immediate, "u"); case OPCODE_XORI: - return fmt::format("SET_GPR_U32(ctx, {}, XOR32(GPR_U32(ctx, {}), {}));", inst.rt, inst.rs, inst.immediate); + return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PXOR(GPR_VEC(ctx, {}), _mm_cvtsi32_si128((int){}{})));", inst.rt, inst.rs, inst.immediate, "u"); case OPCODE_LUI: - return fmt::format("SET_GPR_U32(ctx, {}, ((uint32_t){} << 16));", inst.rt, inst.immediate); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)((uint32_t){} << 16));", inst.rt, inst.immediate); case OPCODE_LB: return fmt::format("SET_GPR_S32(ctx, {}, (int8_t){});", inst.rt, genRead(8, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_LH: return fmt::format("SET_GPR_S32(ctx, {}, (int16_t){});", inst.rt, genRead(16, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_LW: - return fmt::format("SET_GPR_U32(ctx, {}, {});", inst.rt, genRead(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t){});", inst.rt, genRead(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_LBU: return fmt::format("SET_GPR_U32(ctx, {}, (uint8_t){});", inst.rt, genRead(8, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_LHU: @@ -814,14 +997,14 @@ namespace ps2recomp "{{ float f = ctx->f[{}]; uint32_t bits; std::memcpy(&bits, &f, sizeof(bits)); {}; }}", inst.rt, genWrite(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), "bits")); - case OPCODE_LDC2: // was OPCODE_LQC2 need to check + case OPCODE_LDC2: return fmt::format("ctx->vu0_vf[{}] = _mm_castsi128_ps({});", inst.rt, genRead(128, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); - case OPCODE_SDC2: // was OPCODE_SQC2 need to check + case OPCODE_SDC2: return genWrite(128, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("_mm_castps_si128(ctx->vu0_vf[{}])", inst.rt)) + ";"; case OPCODE_DADDI: return fmt::format( "{{ int64_t src = (int64_t)GPR_S64(ctx, {}); " - "int64_t imm = (int64_t){}; " + "int64_t imm = (int64_t)(int32_t){}; " "int64_t res = src + imm; " "if (((src ^ imm) >= 0) && ((src ^ res) < 0)) " " runtime->SignalException(ctx, EXCEPTION_INTEGER_OVERFLOW); " @@ -829,7 +1012,7 @@ namespace ps2recomp inst.rs, inst.simmediate, inst.rt); case OPCODE_DADDIU: return fmt::format( - "SET_GPR_S64(ctx, {}, (int64_t)GPR_S64(ctx, {}) + (int64_t){});", + "SET_GPR_S64(ctx, {}, (int64_t)GPR_S64(ctx, {}) + (int64_t)(int32_t){});", inst.rt, inst.rs, inst.simmediate); case OPCODE_J: return fmt::format("// J 0x{:X} - Handled by branch logic", buildAbsoluteJumpTarget(inst.address, inst.target)); @@ -847,79 +1030,114 @@ namespace ps2recomp case OPCODE_LDL: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = (7 - (addr & 7)) << 3; " - "uint64_t mask = 0xFFFFFFFFFFFFFFFFULL << shift; " - "uint64_t aligned_data = {}; " - "SET_GPR_U64(ctx, {}, (GPR_U64(ctx, {}) & ~mask) | ((aligned_data << shift) & mask)); }}", - inst.rs, inst.simmediate, genRead(64, "addr & ~7ULL"), inst.rt, inst.rt); + "uint32_t aligned_addr = addr & ~7u; " + "uint32_t offset = addr & 7u; " + "uint64_t mem = {}; " + "uint32_t shift = (7u - offset) << 3; " + "uint64_t keepMask = (shift == 0) ? 0ull : ((1ull << shift) - 1ull); " + "SET_GPR_U64(ctx, {}, (GPR_U64(ctx, {}) & keepMask) | (mem << shift)); }}", + inst.rs, inst.simmediate, genRead(64, "aligned_addr"), inst.rt, inst.rt); case OPCODE_LDR: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = (addr & 7) << 3; " - "uint64_t mask = 0xFFFFFFFFFFFFFFFFULL >> shift; " - "uint64_t aligned_data = {}; " - "SET_GPR_U64(ctx, {}, (GPR_U64(ctx, {}) & ~mask) | ((aligned_data >> shift) & mask)); }}", - inst.rs, inst.simmediate, genRead(64, "addr & ~7ULL"), inst.rt, inst.rt); + "uint32_t aligned_addr = addr & ~7u; " + "uint32_t offset = addr & 7u; " + "uint64_t mem = {}; " + "uint32_t shift = offset << 3; " + "uint64_t keepMask = (offset == 0) ? 0ull : (0xFFFFFFFFFFFFFFFFull << ((8u - offset) << 3)); " + "SET_GPR_U64(ctx, {}, (GPR_U64(ctx, {}) & keepMask) | (mem >> shift)); }}", + inst.rs, inst.simmediate, genRead(64, "aligned_addr"), inst.rt, inst.rt); case OPCODE_LWL: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = (3 - (addr & 3)) << 3; " - "uint32_t mask = 0xFFFFFFFF << shift; " - "uint32_t aligned_word = {}; " - "SET_GPR_U32(ctx, {}, (GPR_U32(ctx, {}) & ~mask) | ((aligned_word << shift) & mask)); }}", - inst.rs, inst.simmediate, genRead(32, "addr & ~3"), inst.rt, inst.rt); + "uint32_t aligned_addr = addr & ~3u; " + "uint32_t offset = addr & 3u; " + "uint32_t mem = {}; " + "uint32_t shift = (3u - offset) << 3; " + "uint32_t keepMask = (shift == 0) ? 0u : ((1u << shift) - 1u); " + "uint32_t merged = (GPR_U32(ctx, {}) & keepMask) | (mem << shift); " + "SET_GPR_S32(ctx, {}, (int32_t)merged); }}", + inst.rs, inst.simmediate, genRead(32, "aligned_addr"), inst.rt, inst.rt); case OPCODE_LWR: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = (addr & 3) << 3; " - "uint32_t mask = 0xFFFFFFFF >> shift; " - "uint32_t aligned_word = {}; " - "SET_GPR_U32(ctx, {}, (GPR_U32(ctx, {}) & ~mask) | ((aligned_word >> shift) & mask)); }}", - inst.rs, inst.simmediate, genRead(32, "addr & ~3"), inst.rt, inst.rt); + "uint32_t aligned_addr = addr & ~3u; " + "uint32_t offset = addr & 3u; " + "uint32_t mem = {}; " + "uint32_t shift = offset << 3; " + "uint32_t keepMask = (offset == 0) ? 0u : (0xFFFFFFFFu << ((4u - offset) << 3)); " + "uint32_t merged32 = (GPR_U32(ctx, {}) & keepMask) | (mem >> shift); " + "uint64_t merged64 = (GPR_U64(ctx, {}) & 0xFFFFFFFF00000000ull) | (uint64_t)merged32; " + "if (offset == 0) merged64 = (uint64_t)(int64_t)(int32_t)merged32; " + "SET_GPR_U64(ctx, {}, merged64); }}", + inst.rs, inst.simmediate, genRead(32, "aligned_addr"), + inst.rt, inst.rt, inst.rt); case OPCODE_SWL: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = (3 - (addr & 3)) << 3; " - "uint32_t mask = 0xFFFFFFFF >> shift; " - "uint32_t aligned_addr = addr & ~3; " + "uint32_t aligned_addr = addr & ~3u; " + "uint32_t offset = addr & 3u; " + "uint32_t shift = (3u - offset) << 3; " + "uint32_t mask = 0xFFFFFFFFu >> shift; " "uint32_t old_data = {}; " - "uint32_t new_data = (old_data & ~mask) | ((GPR_U32(ctx, {}) >> shift) & mask); " + "uint32_t val = GPR_U32(ctx, {}); " + "uint32_t new_data = (old_data & ~mask) | ((val >> shift) & mask); " "{}; }}", inst.rs, inst.simmediate, genRead(32, "aligned_addr"), inst.rt, genWrite(32, "aligned_addr", "new_data")); case OPCODE_SWR: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = (addr & 3) << 3; " - "uint32_t mask = 0xFFFFFFFF << shift; " - "uint32_t aligned_addr = addr & ~3; " + "uint32_t aligned_addr = addr & ~3u; " + "uint32_t offset = addr & 3u; " + "uint32_t shift = offset << 3; " + "uint32_t mask = 0xFFFFFFFFu << shift; " "uint32_t old_data = {}; " - "uint32_t new_data = (old_data & ~mask) | ((GPR_U32(ctx, {}) << shift) & mask); " + "uint32_t val = GPR_U32(ctx, {}); " + "uint32_t new_data = (old_data & ~mask) | ((val << shift) & mask); " "{}; }}", inst.rs, inst.simmediate, genRead(32, "aligned_addr"), inst.rt, genWrite(32, "aligned_addr", "new_data")); case OPCODE_SDL: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = (7 - (addr & 7)) << 3; " - "uint64_t mask = 0xFFFFFFFFFFFFFFFFULL >> shift; " - "uint64_t aligned_addr = addr & ~7ULL; " + "uint32_t aligned_addr = addr & ~7u; " + "uint32_t offset = addr & 7u; " + "uint32_t shift = (7u - offset) << 3; " + "uint64_t mask = 0xFFFFFFFFFFFFFFFFull >> shift; " "uint64_t old_data = {}; " - "uint64_t new_data = (old_data & ~mask) | ((GPR_U64(ctx, {}) >> shift) & mask); " + "uint64_t val = GPR_U64(ctx, {}); " + "uint64_t new_data = (old_data & ~mask) | ((val >> shift) & mask); " "{}; }}", inst.rs, inst.simmediate, genRead(64, "aligned_addr"), inst.rt, genWrite(64, "aligned_addr", "new_data")); case OPCODE_SDR: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = ((addr & 7)) << 3; " - "uint64_t mask = 0xFFFFFFFFFFFFFFFFULL << shift; " - "uint64_t aligned_addr = addr & ~7ULL; " + "uint32_t aligned_addr = addr & ~7u; " + "uint32_t offset = addr & 7u; " + "uint32_t shift = offset << 3; " + "uint64_t mask = 0xFFFFFFFFFFFFFFFFull << shift; " "uint64_t old_data = {}; " - "uint64_t new_data = (old_data & ~mask) | ((GPR_U64(ctx, {}) << shift) & mask); " + "uint64_t val = GPR_U64(ctx, {}); " + "uint64_t new_data = (old_data & ~mask) | ((val << shift) & mask); " "{}; }}", inst.rs, inst.simmediate, genRead(64, "aligned_addr"), inst.rt, genWrite(64, "aligned_addr", "new_data")); case OPCODE_CACHE: return "// CACHE instruction (ignored)"; case OPCODE_PREF: return "// PREF instruction (ignored)"; + case OPCODE_LL: + return fmt::format( + "{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " + "SET_GPR_S32(ctx, {}, (int32_t)READ32(addr)); " + "ctx->llbit = 1; ctx->lladdr = addr; }}", + inst.rs, inst.simmediate, inst.rt); + case OPCODE_SC: + return fmt::format( + "{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " + "if (ctx->llbit) {{ WRITE32(addr, GPR_U32(ctx, {})); " + "SET_GPR_S32(ctx, {}, 1); }} " + "else {{ SET_GPR_S32(ctx, {}, 0); }} " + "ctx->llbit = 0; }}", + inst.rs, inst.simmediate, inst.rt, inst.rt, inst.rt); default: return fmt::format("// Unhandled opcode: 0x{:X}", inst.opcode); } @@ -934,15 +1152,15 @@ namespace ps2recomp return "// NOP"; if (inst.rd == 0) return ""; - return fmt::format("SET_GPR_U32(ctx, {}, SLL32(GPR_U32(ctx, {}), {}));", inst.rd, inst.rt, inst.sa); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)SLL32(GPR_U32(ctx, {}), {}));", inst.rd, inst.rt, inst.sa); case SPECIAL_SRL: - return fmt::format("SET_GPR_U32(ctx, {}, SRL32(GPR_U32(ctx, {}), {}));", inst.rd, inst.rt, inst.sa); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)SRL32(GPR_U32(ctx, {}), {}));", inst.rd, inst.rt, inst.sa); case SPECIAL_SRA: return fmt::format("SET_GPR_S32(ctx, {}, SRA32(GPR_S32(ctx, {}), {}));", inst.rd, inst.rt, inst.sa); case SPECIAL_SLLV: - return fmt::format("SET_GPR_U32(ctx, {}, SLL32(GPR_U32(ctx, {}), GPR_U32(ctx, {}) & 0x1F));", inst.rd, inst.rt, inst.rs); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)SLL32(GPR_U32(ctx, {}), GPR_U32(ctx, {}) & 0x1F));", inst.rd, inst.rt, inst.rs); case SPECIAL_SRLV: - return fmt::format("SET_GPR_U32(ctx, {}, SRL32(GPR_U32(ctx, {}), GPR_U32(ctx, {}) & 0x1F));", inst.rd, inst.rt, inst.rs); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)SRL32(GPR_U32(ctx, {}), GPR_U32(ctx, {}) & 0x1F));", inst.rd, inst.rt, inst.rs); case SPECIAL_SRAV: return fmt::format("SET_GPR_S32(ctx, {}, SRA32(GPR_S32(ctx, {}), GPR_U32(ctx, {}) & 0x1F));", inst.rd, inst.rt, inst.rs); case SPECIAL_JR: @@ -956,33 +1174,33 @@ namespace ps2recomp case SPECIAL_SYNC: return "// SYNC instruction - memory barrier\n// In recompiled code, we don't need explicit memory barriers"; case SPECIAL_MFHI: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->hi);", inst.rd); + return fmt::format("SET_GPR_U64(ctx, {}, ctx->hi);", inst.rd); case SPECIAL_MTHI: - return fmt::format("ctx->hi = GPR_U32(ctx, {});", inst.rs); + return fmt::format("ctx->hi = GPR_U64(ctx, {});", inst.rs); case SPECIAL_MFLO: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->lo);", inst.rd); + return fmt::format("SET_GPR_U64(ctx, {}, ctx->lo);", inst.rd); case SPECIAL_MTLO: - return fmt::format("ctx->lo = GPR_U32(ctx, {});", inst.rs); + return fmt::format("ctx->lo = GPR_U64(ctx, {});", inst.rs); case SPECIAL_MULT: - return fmt::format("{{ int64_t result = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); SET_GPR_S32(ctx, {}, (int32_t)(uint32_t)result); }}", inst.rs, inst.rt, inst.rd); + return fmt::format("{{ int64_t result = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); ctx->lo = (uint64_t)(int64_t)(int32_t)result; ctx->hi = (uint64_t)(int64_t)(int32_t)(result >> 32); }}", inst.rs, inst.rt); case SPECIAL_MULTU: - return fmt::format("{{ uint64_t result = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); SET_GPR_U32(ctx, {}, (uint32_t)result); }}", inst.rs, inst.rt, inst.rd); + return fmt::format("{{ uint64_t result = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); ctx->lo = (uint64_t)(int64_t)(int32_t)result; ctx->hi = (uint64_t)(int64_t)(int32_t)(result >> 32); }}", inst.rs, inst.rt); case SPECIAL_DIV: return fmt::format("{{ int32_t divisor = GPR_S32(ctx, {}); " " int32_t dividend = GPR_S32(ctx, {}); " " if (divisor != 0) {{ " " if (divisor == -1 && dividend == INT32_MIN) {{ " - " ctx->lo = INT32_MIN; ctx->hi = 0; " + " ctx->lo = (uint64_t)(int64_t)INT32_MIN; ctx->hi = 0; " " }} else {{ " - " ctx->lo = (uint32_t)(dividend / divisor); " - " ctx->hi = (uint32_t)(dividend % divisor); " + " ctx->lo = (uint64_t)(int64_t)(dividend / divisor); " + " ctx->hi = (uint64_t)(int64_t)(dividend % divisor); " " }} " " }} else {{ " - " ctx->lo = (dividend < 0) ? 1 : -1; ctx->hi = dividend; " + " ctx->lo = (dividend < 0) ? 1ull : 0xFFFFFFFFFFFFFFFFull; ctx->hi = (uint64_t)(int64_t)dividend; " " }} }}", inst.rt, inst.rs); case SPECIAL_DIVU: - return fmt::format("{{ uint32_t divisor = GPR_U32(ctx, {}); if (divisor != 0) {{ ctx->lo = GPR_U32(ctx, {}) / divisor; ctx->hi = GPR_U32(ctx, {}) % divisor; }} else {{ ctx->lo = 0xFFFFFFFF; ctx->hi = GPR_U32(ctx,{}); }} }}", inst.rt, inst.rs, inst.rt, inst.rs, inst.rt); + return fmt::format("{{ uint32_t divisor = GPR_U32(ctx, {}); if (divisor != 0) {{ ctx->lo = (uint64_t)(int64_t)(int32_t)(GPR_U32(ctx, {}) / divisor); ctx->hi = (uint64_t)(int64_t)(int32_t)(GPR_U32(ctx, {}) % divisor); }} else {{ ctx->lo = 0xFFFFFFFFFFFFFFFFull; ctx->hi = (uint64_t)(int64_t)(int32_t)GPR_U32(ctx,{}); }} }}", inst.rt, inst.rs, inst.rs, inst.rs); case SPECIAL_ADD: return fmt::format( "{{ " @@ -997,7 +1215,7 @@ namespace ps2recomp "}}", inst.rs, inst.rt, inst.rd); case SPECIAL_ADDU: - return fmt::format("SET_GPR_U32(ctx, {}, ADD32(GPR_U32(ctx, {}), GPR_U32(ctx, {})));", inst.rd, inst.rs, inst.rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ADD32(GPR_U32(ctx, {}), GPR_U32(ctx, {})));", inst.rd, inst.rs, inst.rt); case SPECIAL_SUB: return fmt::format( "{{ uint32_t tmp; bool ov; " @@ -1006,27 +1224,27 @@ namespace ps2recomp "else SET_GPR_S32(ctx, {}, (int32_t)tmp); }}", inst.rs, inst.rt, inst.rd); case SPECIAL_SUBU: - return fmt::format("SET_GPR_U32(ctx, {}, SUB32(GPR_U32(ctx, {}), GPR_U32(ctx, {})));", inst.rd, inst.rs, inst.rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)SUB32(GPR_U32(ctx, {}), GPR_U32(ctx, {})));", inst.rd, inst.rs, inst.rt); case SPECIAL_AND: - return fmt::format("SET_GPR_U32(ctx, {}, AND32(GPR_U32(ctx, {}), GPR_U32(ctx, {})));", inst.rd, inst.rs, inst.rt); + return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PAND(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", inst.rd, inst.rs, inst.rt); case SPECIAL_OR: - return fmt::format("SET_GPR_U32(ctx, {}, OR32(GPR_U32(ctx, {}), GPR_U32(ctx, {})));", inst.rd, inst.rs, inst.rt); + return fmt::format("SET_GPR_VEC(ctx, {}, PS2_POR(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", inst.rd, inst.rs, inst.rt); case SPECIAL_XOR: - return fmt::format("SET_GPR_U32(ctx, {}, XOR32(GPR_U32(ctx, {}), GPR_U32(ctx, {})));", inst.rd, inst.rs, inst.rt); + return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PXOR(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", inst.rd, inst.rs, inst.rt); case SPECIAL_NOR: - return fmt::format("SET_GPR_U32(ctx, {}, NOR32(GPR_U32(ctx, {}), GPR_U32(ctx, {})));", inst.rd, inst.rs, inst.rt); + return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PNOR(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", inst.rd, inst.rs, inst.rt); case SPECIAL_SLT: - return fmt::format("SET_GPR_U32(ctx, {}, SLT32(GPR_S32(ctx, {}), GPR_S32(ctx, {})));", inst.rd, inst.rs, inst.rt); + return fmt::format("SET_GPR_U64(ctx, {}, ((int64_t)GPR_S64(ctx, {}) < (int64_t)GPR_S64(ctx, {})) ? 1 : 0);", inst.rd, inst.rs, inst.rt); case SPECIAL_SLTU: - return fmt::format("SET_GPR_U32(ctx, {}, SLTU32(GPR_U32(ctx, {}), GPR_U32(ctx, {})));", inst.rd, inst.rs, inst.rt); + return fmt::format("SET_GPR_U64(ctx, {}, ((uint64_t)GPR_U64(ctx, {}) < (uint64_t)GPR_U64(ctx, {})) ? 1 : 0);", inst.rd, inst.rs, inst.rt); case SPECIAL_MOVZ: - return fmt::format("if (GPR_U32(ctx, {}) == 0) SET_GPR_U32(ctx, {}, GPR_U32(ctx, {}));", inst.rt, inst.rd, inst.rs); + return fmt::format("if (GPR_U64(ctx, {}) == 0) SET_GPR_VEC(ctx, {}, GPR_VEC(ctx, {}));", inst.rt, inst.rd, inst.rs); case SPECIAL_MOVN: - return fmt::format("if (GPR_U32(ctx, {}) != 0) SET_GPR_U32(ctx, {}, GPR_U32(ctx, {}));", inst.rt, inst.rd, inst.rs); + return fmt::format("if (GPR_U64(ctx, {}) != 0) SET_GPR_VEC(ctx, {}, GPR_VEC(ctx, {}));", inst.rt, inst.rd, inst.rs); case SPECIAL_MFSA: return fmt::format("SET_GPR_U32(ctx, {}, ctx->sa);", inst.rd); case SPECIAL_MTSA: - return fmt::format("ctx->sa = GPR_U32(ctx, {}) & 0x1F;", inst.rs); + return fmt::format("ctx->sa = GPR_U32(ctx, {}) & 0x7F;", inst.rs); case SPECIAL_DADD: return fmt::format( "{{ int64_t a = (int64_t)GPR_S64(ctx, {}); " @@ -1068,17 +1286,17 @@ namespace ps2recomp case SPECIAL_DSRA32: return fmt::format("SET_GPR_S64(ctx, {}, GPR_S64(ctx, {}) >> (32 + {}));", inst.rd, inst.rt, inst.sa); case SPECIAL_TGE: - return fmt::format("if (GPR_S32(ctx, {}) >= GPR_S32(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt); + return fmt::format("if (GPR_S64(ctx, {}) >= GPR_S64(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt); case SPECIAL_TGEU: - return fmt::format("if (GPR_U32(ctx, {}) >= GPR_U32(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt); + return fmt::format("if (GPR_U64(ctx, {}) >= GPR_U64(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt); case SPECIAL_TLT: - return fmt::format("if (GPR_S32(ctx, {}) < GPR_S32(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt); + return fmt::format("if (GPR_S64(ctx, {}) < GPR_S64(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt); case SPECIAL_TLTU: - return fmt::format("if (GPR_U32(ctx, {}) < GPR_U32(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt); + return fmt::format("if (GPR_U64(ctx, {}) < GPR_U64(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt); case SPECIAL_TEQ: - return fmt::format("if (GPR_U32(ctx, {}) == GPR_U32(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt); + return fmt::format("if (GPR_U64(ctx, {}) == GPR_U64(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt); case SPECIAL_TNE: - return fmt::format("if (GPR_U32(ctx, {}) != GPR_U32(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt); + return fmt::format("if (GPR_U64(ctx, {}) != GPR_U64(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt); default: return fmt::format("// Unhandled SPECIAL instruction: 0x{:X}", inst.function); } @@ -1102,21 +1320,21 @@ namespace ps2recomp return fmt::format("// REGIMM branch instruction to 0x{:X} - Handled by branch logic", target); } case REGIMM_MTSAB: - return fmt::format("ctx->sa = (GPR_U32(ctx, {}) + {}) & 0xF;", inst.rs, inst.simmediate); + return fmt::format("ctx->sa = ((GPR_U32(ctx, {}) ^ (uint32_t){}) & 0xF) << 3;", inst.rs, inst.simmediate); case REGIMM_MTSAH: - return fmt::format("ctx->sa = ((GPR_U32(ctx, {}) + {}) & 0x7) << 1;", inst.rs, inst.simmediate); + return fmt::format("ctx->sa = ((GPR_U32(ctx, {}) ^ (uint32_t){}) & 0x7) << 4;", inst.rs, inst.simmediate); case REGIMM_TGEI: - return fmt::format("if (GPR_S32(ctx, {}) >= {}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate); + return fmt::format("if (GPR_S64(ctx, {}) >= (int64_t)(int32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate); case REGIMM_TGEIU: - return fmt::format("if (GPR_U32(ctx, {}) >= (uint32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate); + return fmt::format("if (GPR_U64(ctx, {}) >= (uint64_t)(int64_t)(int32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate); case REGIMM_TLTI: - return fmt::format("if (GPR_S32(ctx, {}) < {}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate); + return fmt::format("if (GPR_S64(ctx, {}) < (int64_t)(int32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate); case REGIMM_TLTIU: - return fmt::format("if (GPR_U32(ctx, {}) < (uint32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate); + return fmt::format("if (GPR_U64(ctx, {}) < (uint64_t)(int64_t)(int32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate); case REGIMM_TEQI: - return fmt::format("if (GPR_S32(ctx, {}) == {}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate); + return fmt::format("if (GPR_S64(ctx, {}) == (int64_t)(int32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate); case REGIMM_TNEI: - return fmt::format("if (GPR_S32(ctx, {}) != {}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate); + return fmt::format("if (GPR_S64(ctx, {}) != (int64_t)(int32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate); default: return fmt::format("// Unhandled REGIMM instruction: 0x{:X}", inst.rt); } @@ -1134,51 +1352,51 @@ namespace ps2recomp switch (rd) { case COP0_REG_INDEX: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_index);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_index);", rt); case COP0_REG_RANDOM: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_random);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_random);", rt); case COP0_REG_ENTRYLO0: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_entrylo0);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_entrylo0);", rt); case COP0_REG_ENTRYLO1: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_entrylo1);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_entrylo1);", rt); case COP0_REG_CONTEXT: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_context);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_context);", rt); case COP0_REG_PAGEMASK: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_pagemask);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_pagemask);", rt); case COP0_REG_WIRED: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_wired);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_wired);", rt); case COP0_REG_BADVADDR: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_badvaddr);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_badvaddr);", rt); case COP0_REG_COUNT: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_count);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_count);", rt); case COP0_REG_ENTRYHI: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_entryhi);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_entryhi);", rt); case COP0_REG_COMPARE: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_compare);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_compare);", rt); case COP0_REG_STATUS: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_status);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_status);", rt); case COP0_REG_CAUSE: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_cause);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_cause);", rt); case COP0_REG_EPC: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_epc);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_epc);", rt); case COP0_REG_PRID: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_prid);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_prid);", rt); case COP0_REG_CONFIG: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_config);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_config);", rt); case COP0_REG_BADPADDR: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_badpaddr);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_badpaddr);", rt); case COP0_REG_DEBUG: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_debug);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_debug);", rt); case COP0_REG_PERF: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_perf);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_perf);", rt); case COP0_REG_TAGLO: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_taglo);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_taglo);", rt); case COP0_REG_TAGHI: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_taghi);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_taghi);", rt); case COP0_REG_ERROREPC: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->cop0_errorepc);", rt); + return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_errorepc);", rt); default: - return fmt::format("SET_GPR_U32(ctx, {}, 0); // Unimplemented COP0 register {}", rt, rd); + return fmt::format("SET_GPR_S32(ctx, {}, 0); // Unimplemented COP0 register {}", rt, rd); } case COP0_MT: switch (rd) @@ -1281,9 +1499,9 @@ namespace ps2recomp switch (format) { case COP1_MF: - return fmt::format("SET_GPR_U32(ctx, {}, *(uint32_t*)&ctx->f[{}]);", ft, fs); + return fmt::format("{{ uint32_t bits; std::memcpy(&bits, &ctx->f[{}], sizeof(bits)); SET_GPR_U32(ctx, {}, bits); }}", fs, ft); case COP1_MT: - return fmt::format("*(uint32_t*)&ctx->f[{}] = GPR_U32(ctx, {});", fs, ft); + return fmt::format("{{ uint32_t bits = GPR_U32(ctx, {}); std::memcpy(&ctx->f[{}], &bits, sizeof(bits)); }}", ft, fs); case COP1_CF: if (fs == 31) return fmt::format("SET_GPR_U32(ctx, {}, ctx->fcr31);", ft); // FCR31 contains status/control @@ -1321,15 +1539,15 @@ namespace ps2recomp case COP1_S_NEG: return fmt::format("ctx->f[{}] = FPU_NEG_S(ctx->f[{}]);", fd, fs); case COP1_S_ROUND_W: - return fmt::format("*(int32_t*)&ctx->f[{}] = FPU_ROUND_W_S(ctx->f[{}]);", fd, fs); + return fmt::format("{{ int32_t tmp = FPU_ROUND_W_S(ctx->f[{}]); std::memcpy(&ctx->f[{}], &tmp, sizeof(tmp)); }}", fs, fd); case COP1_S_TRUNC_W: - return fmt::format("*(int32_t*)&ctx->f[{}] = FPU_TRUNC_W_S(ctx->f[{}]);", fd, fs); + return fmt::format("{{ int32_t tmp = FPU_TRUNC_W_S(ctx->f[{}]); std::memcpy(&ctx->f[{}], &tmp, sizeof(tmp)); }}", fs, fd); case COP1_S_CEIL_W: - return fmt::format("*(int32_t*)&ctx->f[{}] = FPU_CEIL_W_S(ctx->f[{}]);", fd, fs); + return fmt::format("{{ int32_t tmp = FPU_CEIL_W_S(ctx->f[{}]); std::memcpy(&ctx->f[{}], &tmp, sizeof(tmp)); }}", fs, fd); case COP1_S_FLOOR_W: - return fmt::format("*(int32_t*)&ctx->f[{}] = FPU_FLOOR_W_S(ctx->f[{}]);", fd, fs); + return fmt::format("{{ int32_t tmp = FPU_FLOOR_W_S(ctx->f[{}]); std::memcpy(&ctx->f[{}], &tmp, sizeof(tmp)); }}", fs, fd); case COP1_S_CVT_W: - return fmt::format("*(int32_t*)&ctx->f[{}] = FPU_CVT_W_S(ctx->f[{}]);", fd, fs); + return fmt::format("{{ int32_t tmp = FPU_CVT_W_S(ctx->f[{}]); std::memcpy(&ctx->f[{}], &tmp, sizeof(tmp)); }}", fs, fd); case COP1_S_RSQRT: return fmt::format("ctx->f[{}] = 1.0f / sqrtf(ctx->f[{}]);", fd, fs); case COP1_S_ADDA: @@ -1389,7 +1607,7 @@ namespace ps2recomp switch (function) { case COP1_W_CVT_S: - return fmt::format("ctx->f[{}] = FPU_CVT_S_W(*(int32_t*)&ctx->f[{}]);", fd, fs); + return fmt::format("{{ int32_t tmp; std::memcpy(&tmp, &ctx->f[{}], sizeof(tmp)); ctx->f[{}] = FPU_CVT_S_W(tmp); }}", fs, fd); default: return fmt::format("// Unhandled FPU.W instruction: function 0x{:X}", function); } @@ -1408,40 +1626,52 @@ namespace ps2recomp switch (function) { case MMI_MFHI1: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->hi1);", rd); + return fmt::format("SET_GPR_U64(ctx, {}, ctx->hi1);", rd); case MMI_MTHI1: - return fmt::format("ctx->hi1 = GPR_U32(ctx, {});", rs); + return fmt::format("ctx->hi1 = GPR_U64(ctx, {});", rs); case MMI_MFLO1: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->lo1);", rd); + return fmt::format("SET_GPR_U64(ctx, {}, ctx->lo1);", rd); case MMI_MTLO1: - return fmt::format("ctx->lo1 = GPR_U32(ctx, {});", rs); + return fmt::format("ctx->lo1 = GPR_U64(ctx, {});", rs); case MMI_MULT1: - return fmt::format("{{ int64_t result = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); ctx->lo1 = (uint32_t)result; ctx->hi1 = (uint32_t)(result >> 32); }}", rs, rt); + return fmt::format("{{ int64_t result = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); ctx->lo1 = (uint64_t)(int64_t)(int32_t)result; ctx->hi1 = (uint64_t)(int64_t)(int32_t)(result >> 32); }}", rs, rt); case MMI_MULTU1: - return fmt::format("{{ uint64_t result = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); ctx->lo1 = (uint32_t)result; ctx->hi1 = (uint32_t)(result >> 32); }}", rs, rt); + return fmt::format("{{ uint64_t result = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); ctx->lo1 = (uint64_t)(int64_t)(int32_t)result; ctx->hi1 = (uint64_t)(int64_t)(int32_t)(result >> 32); }}", rs, rt); case MMI_DIV1: - return fmt::format("{{ int32_t divisor = GPR_S32(ctx, {}); if (divisor != 0) {{ ctx->lo1 = (uint32_t)(GPR_S32(ctx, {}) / divisor); ctx->hi1 = (uint32_t)(GPR_S32(ctx, {}) % divisor); }} else {{ ctx->lo1= (GPR_S32(ctx,{}) < 0) ? 1 : -1; ctx->hi1=GPR_S32(ctx,{}); }} }}", rt, rs, rt, rs, rt); + return fmt::format("{{ int32_t divisor = GPR_S32(ctx, {}); " + "int32_t dividend = GPR_S32(ctx, {}); " + "if (divisor != 0) {{ " + " if (divisor == -1 && dividend == INT32_MIN) {{ " + " ctx->lo1 = (uint64_t)(int64_t)INT32_MIN; ctx->hi1 = 0; " + " }} else {{ " + " ctx->lo1 = (uint64_t)(int64_t)(dividend / divisor); " + " ctx->hi1 = (uint64_t)(int64_t)(dividend % divisor); " + " }} " + "}} else {{ " + " ctx->lo1 = (dividend < 0) ? 1ull : 0xFFFFFFFFFFFFFFFFull; ctx->hi1 = (uint64_t)(int64_t)dividend; " + "}} }}", + inst.rt, inst.rs); case MMI_DIVU1: - return fmt::format("{{ uint32_t divisor = GPR_U32(ctx, {}); if (divisor != 0) {{ ctx->lo1 = GPR_U32(ctx, {}) / divisor; ctx->hi1 = GPR_U32(ctx, {}) % divisor; }} else {{ ctx->lo1=0xFFFFFFFF; ctx->hi1=GPR_U32(ctx,{}); }} }}", rt, rs, rt, rs, rt); + return fmt::format("{{ uint32_t divisor = GPR_U32(ctx, {}); if (divisor != 0) {{ ctx->lo1 = (uint64_t)(int64_t)(int32_t)(GPR_U32(ctx, {}) / divisor); ctx->hi1 = (uint64_t)(int64_t)(int32_t)(GPR_U32(ctx, {}) % divisor); }} else {{ ctx->lo1=0xFFFFFFFFFFFFFFFFull; ctx->hi1=(uint64_t)(int64_t)(int32_t)GPR_U32(ctx,{}); }} }}", rt, rs, rs, rs); case MMI_MADD: - return fmt::format("{{ int64_t acc = ((int64_t)ctx->hi << 32) | ctx->lo; int64_t prod = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); int64_t result = acc + prod; ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", rs, rt); + return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); int64_t prod = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); int64_t result = acc + prod; ctx->lo = Ps2SignExt32ToU64((uint32_t)result); ctx->hi = Ps2SignExt32ToU64((uint32_t)(result >> 32)); }}", rs, rt); case MMI_MADDU: - return fmt::format("{{ uint64_t acc = ((uint64_t)ctx->hi << 32) | ctx->lo; uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc + prod; ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", rs, rt); + return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc + prod; ctx->lo = Ps2SignExt32ToU64((uint32_t)result); ctx->hi = Ps2SignExt32ToU64((uint32_t)(result >> 32)); }}", rs, rt); case MMI_MSUB: - return fmt::format("{{ int64_t acc = ((int64_t)ctx->hi << 32) | ctx->lo; int64_t prod = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); int64_t result = acc - prod; ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", rs, rt); + return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); int64_t prod = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); int64_t result = acc - prod; ctx->lo = Ps2SignExt32ToU64((uint32_t)result); ctx->hi = Ps2SignExt32ToU64((uint32_t)(result >> 32)); }}", rs, rt); case MMI_MSUBU: - return fmt::format("{{ uint64_t acc = ((uint64_t)ctx->hi << 32) | ctx->lo; uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc - prod; ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", rs, rt); + return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc - prod; ctx->lo = Ps2SignExt32ToU64((uint32_t)result); ctx->hi = Ps2SignExt32ToU64((uint32_t)(result >> 32)); }}", rs, rt); case MMI_MADD1: - return fmt::format("{{ int64_t acc = ((int64_t)ctx->hi1 << 32) | ctx->lo1; int64_t prod = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); int64_t result = acc + prod; ctx->lo1 = (uint32_t)result; ctx->hi1 = (uint32_t)(result >> 32); }}", rs, rt); + return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi1, ctx->lo1); int64_t prod = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); int64_t result = acc + prod; ctx->lo1 = Ps2SignExt32ToU64((uint32_t)result); ctx->hi1 = Ps2SignExt32ToU64((uint32_t)(result >> 32)); }}", rs, rt); case MMI_MADDU1: - return fmt::format("{{ uint64_t acc = ((uint64_t)ctx->hi1 << 32) | ctx->lo1; uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc + prod; ctx->lo1 = (uint32_t)result; ctx->hi1 = (uint32_t)(result >> 32); }}", rs, rt); + return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi1, ctx->lo1); uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc + prod; ctx->lo1 = Ps2SignExt32ToU64((uint32_t)result); ctx->hi1 = Ps2SignExt32ToU64((uint32_t)(result >> 32)); }}", rs, rt); case MMI_PLZCW: return fmt::format( "{{ " "uint64_t v = GPR_U64(ctx, {}); " "uint32_t lo = (uint32_t)(v & 0xFFFFFFFFu); " "uint32_t hi = (uint32_t)(v >> 32); " - "uint64_t out = ((uint64_t)ps2_clz32(hi) << 32) | (uint64_t)ps2_clz32(lo); " + "uint64_t out = ((uint64_t)ps2_plzcw32(hi) << 32) | (uint64_t)ps2_plzcw32(lo); " "SET_GPR_U64(ctx, {}, out); " "}}", rs, rd); @@ -1534,9 +1764,9 @@ namespace ps2recomp case MMI0_PPACB: return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PPACB(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt); case MMI0_PEXT5: - return fmt::format("// Unhandled PEXT5 instruction: function 0x{:X}", subfunc); + return translatePEXT5(inst); case MMI0_PPAC5: - return fmt::format("// Unhandled PPAC5 instruction: function 0x{:X}", subfunc); + return translatePPAC5(inst); default: return fmt::format("// Unhandled MMI0 instruction: function 0x{:X}", subfunc); } @@ -1557,7 +1787,7 @@ namespace ps2recomp case MMI1_PMINW: return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PMINW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt); case MMI1_PADSBH: - return fmt::format("// Unhandled PADSBH instruction: function 0x{:X}", subfunc); + return translatePADSBH(inst); case MMI1_PABSH: return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PABSH(GPR_VEC(ctx, {})));", rd, rs); case MMI1_PCEQH: @@ -1567,9 +1797,11 @@ namespace ps2recomp case MMI1_PCEQB: return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PCEQB(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt); case MMI1_PADDUW: - return fmt::format("SET_GPR_VEC(ctx, {}, _mm_add_epi32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt); + return fmt::format( + "SET_GPR_VEC(ctx, {}, ps2_paddu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt); case MMI1_PSUBUW: - return fmt::format("SET_GPR_VEC(ctx, {}, _mm_sub_epi32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt); + return fmt::format( + "SET_GPR_VEC(ctx, {}, ps2_psubu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt); case MMI1_PEXTUW: return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PEXTUW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt); case MMI1_PADDUH: @@ -1606,15 +1838,15 @@ namespace ps2recomp case MMI2_PSRLVW: return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PSRLVW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt); case MMI2_PMSUBW: - return fmt::format("// Unhandled PMSUBW instruction: function 0x{:X}", subfunc); + return translatePMSUBW(inst); case MMI2_PMFHI: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->hi);", rd); + return fmt::format("SET_GPR_U64(ctx, {}, ctx->hi);", rd); case MMI2_PMFLO: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->lo);", rd); + return fmt::format("SET_GPR_U64(ctx, {}, ctx->lo);", rd); case MMI2_PINTH: return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PINTH(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt); case MMI2_PMULTW: - return fmt::format("// Unhandled PMULTW instruction: function 0x{:X}", subfunc); + return translatePMULTW(inst); case MMI2_PDIVW: return translatePDIVW(inst); case MMI2_PCPYLD: @@ -1628,9 +1860,9 @@ namespace ps2recomp case MMI2_PHMADH: return translatePHMADH(inst); case MMI2_PMSUBH: - return fmt::format("// Unhandled PMSUBH instruction: function 0x{:X}", subfunc); + return translatePMSUBH(inst); case MMI2_PHMSBH: - return fmt::format("// Unhandled PHMSBH instruction: function 0x{:X}", subfunc); + return translatePHMSBH(inst); case MMI2_PEXEH: return translatePEXEH(inst); case MMI2_PREVH: @@ -1657,7 +1889,7 @@ namespace ps2recomp switch (subfunc) { case MMI3_PMADDUW: - return fmt::format("// Unhandled PMADDUW instruction: function 0x{:X}", subfunc); + return translatePMADDUW(inst); case MMI3_PSRAVW: return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PSRAVW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt); case MMI3_PMTHI: @@ -1743,7 +1975,7 @@ namespace ps2recomp case VU0_CR_R: return fmt::format("SET_GPR_VEC(ctx, {}, _mm_castps_si128(ctx->vu0_r));", rt); case VU0_CR_I: - return fmt::format("SET_GPR_U32(ctx, {}, *(uint32_t*)&ctx->vu0_i);", rt); + return fmt::format("{{ uint32_t bits; std::memcpy(&bits, &ctx->vu0_i, sizeof(bits)); SET_GPR_U32(ctx, {}, bits); }}", rt); case VU0_CR_CLIP: return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_clip_flags);", rt); case VU0_CR_TPC: @@ -1779,13 +2011,13 @@ namespace ps2recomp case VU0_CR_CLIP2: return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_clip_flags2);", rt); case VU0_CR_P: - return fmt::format("SET_GPR_U32(ctx, {}, *(uint32_t*)&ctx->vu0_p);", rt); + return fmt::format("{{ uint32_t bits; std::memcpy(&bits, &ctx->vu0_p, sizeof(bits)); SET_GPR_U32(ctx, {}, bits); }}", rt); case VU0_CR_XITOP: // Maybe this does not exist, maybe we handle to vu0_itop return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_xitop);", rt); case VU0_CR_ITOP: return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_itop);", rt); case VU0_CR_TOP: - return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_vpu_stat);", rt); + return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_top);", rt); // TODO: verify vu0_top field exists in R5900Context default: return fmt::format("// Unimplemented CFC2 VU CReg: {}", rt); } @@ -1807,7 +2039,7 @@ namespace ps2recomp case VU0_CR_R: return fmt::format("ctx->vu0_r = _mm_castsi128_ps(GPR_VEC(ctx, {}));", rt); case VU0_CR_I: - return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); ctx->vu0_i = *reinterpret_cast(&tmp); }}", rt); + return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); std::memcpy(&ctx->vu0_i, &tmp, sizeof(tmp)); }}", rt); case VU0_CR_TPC: return fmt::format("ctx->vu0_tpc = GPR_U32(ctx, {});", rt); case VU0_CR_CMSAR0: @@ -1841,13 +2073,13 @@ namespace ps2recomp case VU0_CR_CLIP2: return fmt::format("ctx->vu0_clip_flags2 = GPR_U32(ctx, {});", rt); case VU0_CR_P: - return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); ctx->vu0_p = *reinterpret_cast(&tmp); }}", rt); + return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); std::memcpy(&ctx->vu0_p, &tmp, sizeof(tmp)); }}", rt); case VU0_CR_XITOP: return fmt::format("ctx->vu0_xitop = GPR_U32(ctx, {}) & 0x3FF;", rt); case VU0_CR_ITOP: return fmt::format("ctx->vu0_itop = GPR_U32(ctx, {}) & 0x3FF;", rt); case VU0_CR_TOP: - return fmt::format("ctx->vu0_vpu_stat = GPR_U32(ctx, {}) & 0x3FF;", rt); + return fmt::format("ctx->vu0_top = GPR_U32(ctx, {}) & 0x3FF;", rt); default: return fmt::format("// Unimplemented CTC2 VU CReg: {}", rd); } @@ -2062,71 +2294,17 @@ namespace ps2recomp case VU0_S1_VCALLMSR: return translateVU_VCALLMSR(inst); case VU0_S1_VADDq: - { - uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); " - "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " - "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", - inst.rd, - (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, - (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, - inst.sa, inst.sa); - } + return translateVU_VADDq(inst); case VU0_S1_VSUBq: - { - uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); " - "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " - "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", - inst.rd, - (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, - (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, - inst.sa, inst.sa); - } + return translateVU_VSUBq(inst); case VU0_S1_VMULq: - { - uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); " - "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " - "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", - inst.rd, - (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, - (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, - inst.sa, inst.sa); - } + return translateVU_VMULq(inst); case VU0_S1_VADDi: - { - uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); " - "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " - "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", - inst.rd, - (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, - (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, - inst.sa, inst.sa); - } + return translateVU_VADDi(inst); case VU0_S1_VSUBi: - { - uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); " - "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " - "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", - inst.rd, - (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, - (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, - inst.sa, inst.sa); - } + return translateVU_VSUBi(inst); case VU0_S1_VMULi: - { - uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); " - "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " - "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", - inst.rd, - (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, - (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, - inst.sa, inst.sa); - } + return translateVU_VMULi(inst); case VU0_S1_VMADDx: case VU0_S1_VMADDy: case VU0_S1_VMADDz: @@ -2238,17 +2416,101 @@ namespace ps2recomp return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = PS2_VBLEND(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", vfs, vft, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, vfd, vfd); } + std::string CodeGenerator::translatePEXT5(const Instruction &inst) + { + return fmt::format( + "{{ __m128i rt = GPR_VEC(ctx, {}); \n" + " __m128i m1 = _mm_set1_epi32(0x0000001F); \n" + " __m128i m2 = _mm_set1_epi32(0x000003E0); \n" + " __m128i m3 = _mm_set1_epi32(0x00007C00); \n" + " __m128i m4 = _mm_set1_epi32(0x00008000); \n" + " __m128i a1 = _mm_slli_epi32(_mm_and_si128(rt, m1), 3); \n" + " __m128i a2 = _mm_slli_epi32(_mm_and_si128(rt, m2), 6); \n" + " __m128i a3 = _mm_slli_epi32(_mm_and_si128(rt, m3), 9); \n" + " __m128i a4 = _mm_slli_epi32(_mm_and_si128(rt, m4), 16); \n" + " SET_GPR_VEC(ctx, {}, _mm_or_si128(_mm_or_si128(a1, a2), _mm_or_si128(a3, a4))); }}", + inst.rt, inst.rd); + } + + std::string CodeGenerator::translatePPAC5(const Instruction &inst) + { + return fmt::format( + "{{ __m128i rt = GPR_VEC(ctx, {}); \n" + " __m128i m1 = _mm_set1_epi32(0x0000001F); \n" + " __m128i m2 = _mm_set1_epi32(0x000003E0); \n" + " __m128i m3 = _mm_set1_epi32(0x00007C00); \n" + " __m128i m4 = _mm_set1_epi32(0x00008000); \n" + " __m128i a1 = _mm_and_si128(_mm_srli_epi32(rt, 3), m1); \n" + " __m128i a2 = _mm_and_si128(_mm_srli_epi32(rt, 6), m2); \n" + " __m128i a3 = _mm_and_si128(_mm_srli_epi32(rt, 9), m3); \n" + " __m128i a4 = _mm_and_si128(_mm_srli_epi32(rt, 16), m4); \n" + " SET_GPR_VEC(ctx, {}, _mm_or_si128(_mm_or_si128(a1, a2), _mm_or_si128(a3, a4))); }}", + inst.rt, inst.rd); + } + + std::string CodeGenerator::translatePADSBH(const Instruction &inst) + { + return fmt::format( + "{{ __m128i rs = GPR_VEC(ctx, {}); __m128i rt = GPR_VEC(ctx, {}); \n" + " __m128i sub = _mm_sub_epi16(rs, rt); \n" + " __m128i add = _mm_add_epi16(rs, rt); \n" + " SET_GPR_VEC(ctx, {}, _mm_unpacklo_epi64(sub, _mm_unpackhi_epi64(add, add))); }}", + inst.rs, inst.rt, inst.rd); + } + std::string CodeGenerator::translatePMADDW(const Instruction &inst) { - return fmt::format("{{ __m128i p01 = _mm_mul_epu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n" // [p1, p0] 64b each - " __m128i p23 = _mm_mul_epu32(_mm_srli_si128(GPR_VEC(ctx, {}), 8), _mm_srli_si128(GPR_VEC(ctx, {}), 8)); \n" // [p3, p2] 64b each - " uint64_t acc = ((uint64_t)ctx->hi << 32) | ctx->lo; \n" - " acc += _mm_cvtsi128_si64(p01); \n" // Add product 0 - " acc += _mm_cvtsi128_si64(_mm_srli_si128(p01, 8)); \n" // Add product 1 - " acc += _mm_cvtsi128_si64(p23); \n" // Add product 2 - " acc += _mm_cvtsi128_si64(_mm_srli_si128(p23, 8)); \n" // Add product 3 + return fmt::format("{{ __m128i p01 = _mm_mul_epu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n" + " __m128i p23 = _mm_mul_epu32(_mm_srli_si128(GPR_VEC(ctx, {}), 8), _mm_srli_si128(GPR_VEC(ctx, {}), 8)); \n" + " uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n" + " acc += _mm_cvtsi128_si64(p01); \n" + " acc += _mm_cvtsi128_si64(_mm_srli_si128(p01, 8)); \n" + " acc += _mm_cvtsi128_si64(p23); \n" + " acc += _mm_cvtsi128_si64(_mm_srli_si128(p23, 8)); \n" " ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n" - " SET_GPR_U64(ctx, {}, acc); }}", // Store 64-bit acc result in rd + " SET_GPR_U64(ctx, {}, acc); }}", + inst.rs, inst.rt, inst.rs, inst.rt, inst.rd); + } + + std::string CodeGenerator::translatePMSUBW(const Instruction &inst) + { + return fmt::format("{{ __m128i p01 = _mm_mul_epu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n" + " __m128i p23 = _mm_mul_epu32(_mm_srli_si128(GPR_VEC(ctx, {}), 8), _mm_srli_si128(GPR_VEC(ctx, {}), 8)); \n" + " uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n" + " acc -= _mm_cvtsi128_si64(p01); \n" + " acc -= _mm_cvtsi128_si64(_mm_srli_si128(p01, 8)); \n" + " acc -= _mm_cvtsi128_si64(p23); \n" + " acc -= _mm_cvtsi128_si64(_mm_srli_si128(p23, 8)); \n" + " ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n" + " SET_GPR_U64(ctx, {}, acc); }}", + inst.rs, inst.rt, inst.rs, inst.rt, inst.rd); + } + + std::string CodeGenerator::translatePMULTW(const Instruction &inst) + { + return fmt::format("{{ __m128i p01 = _mm_mul_epu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n" + " __m128i p23 = _mm_mul_epu32(_mm_srli_si128(GPR_VEC(ctx, {}), 8), _mm_srli_si128(GPR_VEC(ctx, {}), 8)); \n" + " uint64_t acc = 0; \n" + " acc += _mm_cvtsi128_si64(p01); \n" + " acc += _mm_cvtsi128_si64(_mm_srli_si128(p01, 8)); \n" + " acc += _mm_cvtsi128_si64(p23); \n" + " acc += _mm_cvtsi128_si64(_mm_srli_si128(p23, 8)); \n" + " ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n" + " SET_GPR_U64(ctx, {}, acc); }}", + inst.rs, inst.rt, inst.rs, inst.rt, inst.rd); + } + + std::string CodeGenerator::translatePMADDUW(const Instruction &inst) + { + return fmt::format("{{ __m128i p01 = _mm_mul_epu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n" + " __m128i p23 = _mm_mul_epu32(_mm_srli_si128(GPR_VEC(ctx, {}), 8), _mm_srli_si128(GPR_VEC(ctx, {}), 8)); \n" + " uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n" + " acc += _mm_cvtsi128_si64(p01); \n" + " acc += _mm_cvtsi128_si64(_mm_srli_si128(p01, 8)); \n" + " acc += _mm_cvtsi128_si64(p23); \n" + " acc += _mm_cvtsi128_si64(_mm_srli_si128(p23, 8)); \n" + " ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n" + " SET_GPR_U64(ctx, {}, acc); }}", inst.rs, inst.rt, inst.rs, inst.rt, inst.rd); } @@ -2256,9 +2518,11 @@ namespace ps2recomp { // Only divides the first word element rs[0] / rt[0] return fmt::format("{{ int32_t rs0 = GPR_S32(ctx, {}); int32_t rt0 = GPR_S32(ctx, {}); \n" - " if (rt0 != 0) {{ ctx->lo = (uint32_t)(rs0 / rt0); ctx->hi = (uint32_t)(rs0 % rt0); }} \n" - " else {{ ctx->lo = (rs0 < 0) ? 1 : -1; ctx->hi = rs0; }} \n" // Div by zero behavior - " SET_GPR_U32(ctx, {}, ctx->lo); }}", // Store quotient in rd[0] + " if (rt0 != 0) {{ \n" + " if (rt0 == -1 && rs0 == INT32_MIN) {{ ctx->lo = (uint32_t)INT32_MIN; ctx->hi = 0; }} \n" + " else {{ ctx->lo = (uint32_t)(rs0 / rt0); ctx->hi = (uint32_t)(rs0 % rt0); }} \n" + " }} else {{ ctx->lo = (rs0 < 0) ? 1 : -1; ctx->hi = (uint32_t)rs0; }} \n" + " SET_GPR_U32(ctx, {}, ctx->lo); }}", inst.rs, inst.rt, inst.rd); } @@ -2277,7 +2541,7 @@ namespace ps2recomp " int32_t p1 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 4)); \n" " int32_t p2 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 8)); \n" " int32_t p3 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 12)); \n" - " int64_t acc = ((int64_t)ctx->hi << 32) | ctx->lo; \n" + " int64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n" " acc += (int64_t)p0 + (int64_t)p1 + (int64_t)p2 + (int64_t)p3; \n" " ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n" " SET_GPR_U64(ctx, {}, acc); }}", @@ -2296,7 +2560,39 @@ namespace ps2recomp " int32_t h1 = _mm_extract_epi16(sum_pairs, 2) + _mm_extract_epi16(sum_pairs, 3); \n" // Horizontal add within next 32b " int32_t h2 = _mm_extract_epi16(sum_pairs, 4) + _mm_extract_epi16(sum_pairs, 5); \n" " int32_t h3 = _mm_extract_epi16(sum_pairs, 6) + _mm_extract_epi16(sum_pairs, 7); \n" - " int64_t acc = ((int64_t)ctx->hi << 32) | ctx->lo; \n" + " int64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n" + " acc += (int64_t)h0 + (int64_t)h1 + (int64_t)h2 + (int64_t)h3; \n" + " ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n" + " SET_GPR_U64(ctx, {}, acc); }}", + inst.rs, inst.rt, inst.rs, inst.rt, inst.rd); + } + + std::string CodeGenerator::translatePMSUBH(const Instruction &inst) + { + return fmt::format("{{ __m128i prod = _mm_madd_epi16(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n" + " int32_t p0 = _mm_cvtsi128_si32(prod); \n" + " int32_t p1 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 4)); \n" + " int32_t p2 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 8)); \n" + " int32_t p3 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 12)); \n" + " int64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n" + " acc -= (int64_t)p0 + (int64_t)p1 + (int64_t)p2 + (int64_t)p3; \n" + " ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n" + " SET_GPR_U64(ctx, {}, acc); }}", + inst.rs, inst.rt, inst.rd); + } + + std::string CodeGenerator::translatePHMSBH(const Instruction &inst) + { + return fmt::format("{{ __m128i evens = _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(2,0,2,0)); \n" + " __m128i odds = _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(3,1,3,1)); \n" + " __m128i prod_ev = _mm_mullo_epi16(evens, _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(2,0,2,0))); \n" + " __m128i prod_od = _mm_mullo_epi16(odds, _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(3,1,3,1))); \n" + " __m128i sub_pairs = _mm_sub_epi16(prod_od, prod_ev); \n" + " int32_t h0 = _mm_extract_epi16(sub_pairs, 0) + _mm_extract_epi16(sub_pairs, 1); \n" + " int32_t h1 = _mm_extract_epi16(sub_pairs, 2) + _mm_extract_epi16(sub_pairs, 3); \n" + " int32_t h2 = _mm_extract_epi16(sub_pairs, 4) + _mm_extract_epi16(sub_pairs, 5); \n" + " int32_t h3 = _mm_extract_epi16(sub_pairs, 6) + _mm_extract_epi16(sub_pairs, 7); \n" + " int64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n" " acc += (int64_t)h0 + (int64_t)h1 + (int64_t)h2 + (int64_t)h3; \n" " ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n" " SET_GPR_U64(ctx, {}, acc); }}", @@ -2460,13 +2756,13 @@ namespace ps2recomp std::string CodeGenerator::translateVU_VMTIR(const Instruction &inst) { uint8_t fsf = inst.vectorInfo.fsf; - return fmt::format("{{ float src = _mm_cvtss_f32(_mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,0,0,{}))); ctx->vi[{}] = static_cast(static_cast(src)); }}", inst.rd, inst.rd, fsf, inst.rt); + return fmt::format("{{ uint32_t bits; float src = _mm_cvtss_f32(_mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,0,0,{}))); std::memcpy(&bits, &src, sizeof(bits)); ctx->vi[{}] = (uint16_t)(bits & 0xFFFF); }}", inst.rd, inst.rd, fsf, inst.rt); } std::string CodeGenerator::translateVU_VMFIR(const Instruction &inst) { uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ uint32_t tmp = ctx->vi[{}]; float val = *(float*)&tmp; " + return fmt::format("{{ uint32_t tmp = (uint32_t)(int32_t)(int16_t)ctx->vi[{}]; float val; std::memcpy(&val, &tmp, sizeof(val)); " "__m128 res = _mm_set1_ps(val); " "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", @@ -2719,6 +3015,49 @@ namespace ps2recomp vfd, vfd); } + std::string CodeGenerator::translateVU_VMINIi(const Instruction &inst) + { + uint8_t vfd = inst.sa; + uint8_t vfs = inst.rd; + uint8_t dest_mask = inst.vectorInfo.vectorField; + return fmt::format("{{ __m128 res = _mm_min_ps(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); " + "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " + "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", + vfs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + vfd, vfd); + } + + std::string CodeGenerator::translateVU_VMULi(const Instruction &inst) + { + uint8_t vfd = inst.sa; + uint8_t vfs = inst.rd; + uint8_t dest_mask = inst.vectorInfo.vectorField; + return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); " + "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " + "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", + vfs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + vfd, vfd); + } + + std::string CodeGenerator::translateVU_VMULq(const Instruction &inst) + { + uint8_t vfd = inst.sa; + uint8_t vfs = inst.rd; + uint8_t dest_mask = inst.vectorInfo.vectorField; + return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); " + "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " + "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", + vfs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + vfd, vfd); + } + + std::string CodeGenerator::translateVU_VOPMSUB(const Instruction &inst) { uint8_t vfd = inst.sa; @@ -2736,6 +3075,55 @@ namespace ps2recomp vfd, vfd); } + + + std::string CodeGenerator::translateVU_VADDq(const Instruction &inst) + { + uint8_t vfd = inst.sa; + uint8_t vfs = inst.rd; + uint8_t dest_mask = inst.vectorInfo.vectorField; + return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); " + "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " + "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", + vfs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + vfd, vfd); + } + + std::string CodeGenerator::translateVU_VADDi(const Instruction &inst) + { + uint8_t vfd = inst.sa; + uint8_t vfs = inst.rd; + uint8_t dest_mask = inst.vectorInfo.vectorField; + return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); " + "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " + "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", + vfs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + vfd, vfd); + } + + + + std::string CodeGenerator::translateVU_VMSUB(const Instruction &inst) + { + uint8_t vfd = inst.sa; + uint8_t vfs = inst.rd; + uint8_t vft = inst.rt; + uint8_t dest_mask = inst.vectorInfo.vectorField; + return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); " + "__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); " + "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " + "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); " + "ctx->vu0_acc = res; }}", + vfs, vft, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + vfd, vfd); + } + std::string CodeGenerator::translateVU_VMINI(const Instruction &inst) { uint8_t vfd = inst.sa; @@ -2751,12 +3139,14 @@ namespace ps2recomp vfd, vfd); } - std::string CodeGenerator::translateVU_VMINIi(const Instruction &inst) + + + std::string CodeGenerator::translateVU_VSUBi(const Instruction &inst) { uint8_t vfd = inst.sa; uint8_t vfs = inst.rd; uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = _mm_min_ps(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); " + return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); " "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", vfs, @@ -2765,23 +3155,21 @@ namespace ps2recomp vfd, vfd); } - std::string CodeGenerator::translateVU_VMSUB(const Instruction &inst) + std::string CodeGenerator::translateVU_VSUBq(const Instruction &inst) { uint8_t vfd = inst.sa; uint8_t vfs = inst.rd; - uint8_t vft = inst.rt; uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); " - "__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); " + return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); " "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " - "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); " - "ctx->vu0_acc = res; }}", - vfs, vft, + "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", + vfs, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, vfd, vfd); } + std::string CodeGenerator::translateVU_VMSUBq(const Instruction &inst) { uint8_t vfd = inst.sa; @@ -3148,7 +3536,7 @@ namespace ps2recomp " __m128i mixed = _mm_xor_si128(xored, _mm_slli_epi32(xored, 7)); " " mixed = _mm_xor_si128(mixed, _mm_srli_epi32(mixed, 9)); " " " - " ctx->vu0_r = (__m128)mixed; " + " ctx->vu0_r = _mm_castsi128_ps(mixed);" "}}", fs_reg, fs_reg, fsf); } @@ -3312,7 +3700,7 @@ namespace ps2recomp uint32_t indexReg = inst.rs; - ss << "switch (ctx->r[" << indexReg << "]) {\n"; + ss << "switch (GPR_U32(ctx, " << indexReg << ")) {\n"; for (const auto &[index, target] : entries) { diff --git a/ps2xRecomp/src/lib/ps2_recompiler.cpp b/ps2xRecomp/src/lib/ps2_recompiler.cpp index f22da25..a172236 100644 --- a/ps2xRecomp/src/lib/ps2_recompiler.cpp +++ b/ps2xRecomp/src/lib/ps2_recompiler.cpp @@ -816,7 +816,7 @@ namespace ps2recomp << m_relocations.size() << " relocations." << std::endl; m_decoder = std::make_unique(); - m_codeGenerator = std::make_unique(m_symbols); + m_codeGenerator = std::make_unique(m_symbols, m_sections); std::unordered_map relocationCallNames; relocationCallNames.reserve(m_relocations.size()); for (const auto &reloc : m_relocations) diff --git a/ps2xRecomp/src/lib/r5900_decoder.cpp b/ps2xRecomp/src/lib/r5900_decoder.cpp index 7a0cbf3..99e1b50 100644 --- a/ps2xRecomp/src/lib/r5900_decoder.cpp +++ b/ps2xRecomp/src/lib/r5900_decoder.cpp @@ -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 buffer(bufferSize + 1, '\0'); + RabbitizerInstruction_disassemble(&rabbitizerInst, buffer.data(), nullptr, 0, 0); + inst.disassembly = buffer.data(); + } + RabbitizerInstructionR5900_destroy(&rabbitizerInst); return inst; diff --git a/ps2xRuntime/CMakeLists.txt b/ps2xRuntime/CMakeLists.txt index 8fca766..24a682c 100644 --- a/ps2xRuntime/CMakeLists.txt +++ b/ps2xRuntime/CMakeLists.txt @@ -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 diff --git a/ps2xRuntime/include/ps2_gs_gpu.h b/ps2xRuntime/include/ps2_gs_gpu.h new file mode 100644 index 0000000..a1455ed --- /dev/null +++ b/ps2xRuntime/include/ps2_gs_gpu.h @@ -0,0 +1,62 @@ +#ifndef PS2_GS_GPU_H +#define PS2_GS_GPU_H + +#include +#include +#include +#include + +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 &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 m_buffers[2]; + int m_backIdx = 0; // index into m_buffers for the current write target + mutable std::mutex m_mutex; + std::atomic m_hasData{false}; + uint32_t m_screenW = 640; + uint32_t m_screenH = 448; +}; + +GsGpuFrameData &gsGpuGetFrameData(); +bool gsGpuRenderFrame(); + +#endif // PS2_GS_GPU_H diff --git a/ps2xRuntime/include/ps2_memory.h b/ps2xRuntime/include/ps2_memory.h index db6768e..d349320 100644 --- a/ps2xRuntime/include/ps2_memory.h +++ b/ps2xRuntime/include/ps2_memory.h @@ -6,13 +6,14 @@ #include #include #include +#include #if defined(_MSC_VER) - #include +#include #elif defined(USE_SSE2NEON) - #include "sse2neon.h" +#include "sse2neon.h" #else - #include // For SSE/AVX instructions - #include // For SSE4.1 instructions +#include // For SSE/AVX instructions +#include // 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; diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index 658dae5..d124cf7 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -8,12 +8,12 @@ #include #include #if defined(_MSC_VER) - #include +#include #elif defined(USE_SSE2NEON) - #include "sse2neon.h" +#include "sse2neon.h" #else - #include // For SSE/AVX instructions - #include // For SSE4.1 instructions +#include // For SSE/AVX instructions +#include // For SSE4.1 instructions #endif #include #include @@ -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(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(static_cast(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 - diff --git a/ps2xRuntime/include/ps2_runtime_macros.h b/ps2xRuntime/include/ps2_runtime_macros.h index a51e9e4..5f24d10 100644 --- a/ps2xRuntime/include/ps2_runtime_macros.h +++ b/ps2xRuntime/include/ps2_runtime_macros.h @@ -1,6 +1,8 @@ #ifndef PS2_RUNTIME_MACROS_H #define PS2_RUNTIME_MACROS_H #include +#include +#include #include #if defined(_MSC_VER) #include @@ -44,6 +46,29 @@ static inline uint32_t ps2_clz32(uint32_t x) return static_cast(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(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(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); \ } \ diff --git a/ps2xRuntime/include/ps2_syscalls.h b/ps2xRuntime/include/ps2_syscalls.h index 58d2094..4a7bea3 100644 --- a/ps2xRuntime/include/ps2_syscalls.h +++ b/ps2xRuntime/include/ps2_syscalls.h @@ -5,8 +5,9 @@ #include "ps2_call_list.h" #include #include +#include +#include -// Number of active host threads spawned for PS2 thread emulation extern std::atomic 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 diff --git a/ps2xRuntime/src/lib/ps2_gs_gpu.cpp b/ps2xRuntime/src/lib/ps2_gs_gpu.cpp new file mode 100644 index 0000000..faf9a9d --- /dev/null +++ b/ps2xRuntime/src/lib/ps2_gs_gpu.cpp @@ -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 lock(m_mutex); + m_buffers[m_backIdx].push_back(prim); + m_hasData.store(true, std::memory_order_relaxed); +} + +const std::vector &GsGpuFrameData::swapAndGetFront() +{ + std::lock_guard 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 &prims = fd.swapAndGetFront(); + + if (prims.empty()) + { + return false; + } + + const float screenW = static_cast(fd.screenWidth()); + const float screenH = static_cast(fd.screenHeight()); + + // Set up 2D orthographic projection matching PS2 screen coords + rlMatrixMode(RL_PROJECTION); + rlPushMatrix(); + rlLoadIdentity(); + rlOrtho(0.0, static_cast(screenW), + static_cast(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; +} diff --git a/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp b/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp new file mode 100644 index 0000000..4f1b59a --- /dev/null +++ b/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp @@ -0,0 +1,1039 @@ +// Based on Blackline Interactive implementation +#include "ps2_memory.h" +#include "ps2_gs_gpu.h" +#include +#include +#include +#include +#include + +enum GSReg : uint8_t +{ + GS_PRIM = 0x00, + GS_RGBAQ = 0x01, + GS_ST = 0x02, + GS_UV = 0x03, + GS_XYZF2 = 0x04, + GS_XYZ2 = 0x05, + GS_TEX0_1 = 0x06, + GS_TEX0_2 = 0x07, + GS_CLAMP_1 = 0x08, + GS_CLAMP_2 = 0x09, + GS_FOG = 0x0A, + GS_XYZF3 = 0x0C, + GS_XYZ3 = 0x0D, + GS_TEX1_1 = 0x14, + GS_TEX1_2 = 0x15, + GS_TEX2_1 = 0x16, + GS_TEX2_2 = 0x17, + GS_XYOFFSET_1 = 0x18, + GS_XYOFFSET_2 = 0x19, + GS_PRMODECONT = 0x1A, + GS_PRMODE = 0x1B, + GS_TEXCLUT = 0x1C, + GS_SCANMSK = 0x22, + GS_MIPTBP1_1 = 0x34, + GS_MIPTBP1_2 = 0x35, + GS_MIPTBP2_1 = 0x36, + GS_MIPTBP2_2 = 0x37, + GS_TEXA = 0x3B, + GS_FOGCOL = 0x3D, + GS_TEXFLUSH = 0x3F, + GS_SCISSOR_1 = 0x40, + GS_SCISSOR_2 = 0x41, + GS_ALPHA_1 = 0x42, + GS_ALPHA_2 = 0x43, + GS_DIMX = 0x44, + GS_DTHE = 0x45, + GS_COLCLAMP = 0x46, + GS_TEST_1 = 0x47, + GS_TEST_2 = 0x48, + GS_PABE = 0x49, + GS_FBA_1 = 0x4A, + GS_FBA_2 = 0x4B, + GS_FRAME_1 = 0x4C, + GS_FRAME_2 = 0x4D, + GS_ZBUF_1 = 0x4E, + GS_ZBUF_2 = 0x4F, + GS_BITBLTBUF = 0x50, + GS_TRXPOS = 0x51, + GS_TRXREG = 0x52, + GS_TRXDIR = 0x53, + GS_HWREG = 0x54, + GS_SIGNAL = 0x60, + GS_FINISH = 0x61, + GS_LABEL = 0x62, +}; + +namespace +{ + + struct GSVertex + { + float x, y, z; // screen coords (after 12.4 fixed -> float) + uint8_t r, g, b, a; // vertex color + float s, t, q; // texture coords + uint16_t u, v; // UV coords (14.0 fixed) + }; + + struct GSInternalRegs + { + // Current primitive + uint32_t prim = 0; + // Color + uint8_t r = 128, g = 128, b = 128, a = 128; + float q = 1.0f; + // Texture coords + float s = 0, t = 0; + uint16_t u = 0, v = 0; + // Frame buffer + uint32_t fbp = 0; // frame buffer base pointer (in pages) + uint32_t fbw = 10; // frame buffer width (64-pixel units) + uint32_t psm = 0; // pixel storage mode + uint32_t fbmsk = 0; // frame buffer write mask + // Scissor + uint32_t scax0 = 0, scax1 = 639, scay0 = 0, scay1 = 447; + // XY offset (12.4 fixed point) + uint32_t ofx = 0, ofy = 0; + // Texture + uint64_t tex0 = 0; + uint32_t tbp0 = 0; // texture base pointer + uint32_t tbw = 0; // texture buffer width + uint32_t tpsm = 0; // texture pixel storage mode + uint32_t tw = 0, th = 0; // texture width/height (log2) + // Alpha + uint64_t alpha = 0; + // Test + uint64_t test = 0; + // BITBLTBUF / TRXPOS / TRXREG / TRXDIR for image transfers + uint64_t bitbltbuf = 0; + uint64_t trxpos = 0; + uint64_t trxreg = 0; + uint64_t trxdir = 0; + // Image transfer state + bool xferActive = false; + uint32_t xferX = 0, xferY = 0; + uint32_t xferW = 0, xferH = 0; + uint32_t xferDBP = 0, xferDBW = 0, xferDPSM = 0; + uint32_t xferDstX = 0, xferDstY = 0; + uint32_t xferPixelsWritten = 0; + // Vertex queue for primitive assembly + GSVertex vtxQueue[3]; + int vtxCount = 0; + int vtxKick = 0; // vertices needed for current prim + // Stats + uint32_t gifTagsProcessed = 0; + uint32_t adWrites = 0; + uint32_t imageQWs = 0; + uint32_t primsDrawn = 0; + // PRMODECONT / PRMODE + uint32_t prmodecont = 1; // 1 = use PRIM bits, 0 = use PRMODE bits + uint32_t prmode = 0; + }; + + static GSInternalRegs g_gsRegs; + static std::mutex g_gsRegsMutex; // protects g_gsRegs from game/render thread races + static int g_gsLogCount = 0; + + // PSMCT24 bit accumulator for HWREG transfers. + // Each 64-bit write delivers 64 bits; each pixel is 24 bits. + // We accumulate leftover bits across writes. + static uint64_t g_psmct24_accBits = 0; + static int g_psmct24_accCount = 0; // bits currently in accumulator + + // PSMCT32: 4 bytes per pixel, standard layout per block + // For FRAME register: FBP is in words/2048 => base = fbp * 2048 * 4 bytes + inline uint32_t gsVramOffset32_Frame(uint32_t fbp, uint32_t fbw, uint32_t x, uint32_t y) + { + const uint32_t baseBytes = fbp * 2048u * 4u; + const uint32_t stride = (fbw ? fbw : 10u) * 64u * 4u; // bytes per row + return baseBytes + y * stride + x * 4u; + } + + // For BITBLTBUF/TEX0: BP is in words/64 => base = bp * 64 * 4 bytes + inline uint32_t gsVramOffset32_BP64(uint32_t bp, uint32_t bw, uint32_t x, uint32_t y) + { + const uint32_t baseBytes = bp * 64u * 4u; + const uint32_t stride = (bw ? bw : 1u) * 64u * 4u; + return baseBytes + y * stride + x * 4u; + } + + inline void writePixel32(uint8_t *vram, uint32_t bp, uint32_t bw, + uint32_t x, uint32_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t a, + uint32_t vramSize, bool isBP64 = false) + { + uint32_t off = isBP64 ? gsVramOffset32_BP64(bp, bw, x, y) + : gsVramOffset32_Frame(bp, bw, x, y); + if (off + 3 < vramSize) + { + vram[off + 0] = r; + vram[off + 1] = g; + vram[off + 2] = b; + vram[off + 3] = a; + } + } + + inline void readPixel32(const uint8_t *vram, uint32_t bp, uint32_t bw, + uint32_t x, uint32_t y, uint8_t &r, uint8_t &g, uint8_t &b, uint8_t &a, + uint32_t vramSize, bool isBP64 = false) + { + uint32_t off = isBP64 ? gsVramOffset32_BP64(bp, bw, x, y) + : gsVramOffset32_Frame(bp, bw, x, y); + if (off + 3 < vramSize) + { + r = vram[off + 0]; + g = vram[off + 1]; + b = vram[off + 2]; + a = vram[off + 3]; + } + else + { + r = g = b = a = 0; + } + } + + inline GsGpuVertex toGpuVertex(const GSVertex &v) + { + GsGpuVertex gv{}; + gv.x = v.x; + gv.y = v.y; + gv.z = v.z; + gv.r = v.r; + gv.g = v.g; + gv.b = v.b; + gv.a = v.a; + gv.u = v.s; // TODO: proper tex coord mapping + gv.v = v.t; + return gv; + } + + // Emit a QUAD (axis-aligned rectangle from SPRITE primitive) + void drawSprite(uint8_t * /*vram*/, uint32_t /*vramSize*/) + { + auto &gs = g_gsRegs; + if (gs.vtxCount < 2) + return; + + GSVertex &v0 = gs.vtxQueue[0]; + GSVertex &v1 = gs.vtxQueue[1]; + + float x0 = std::max(v0.x, (float)gs.scax0); + float y0 = std::max(v0.y, (float)gs.scay0); + float x1 = std::min(v1.x, (float)(gs.scax1 + 1)); + float y1 = std::min(v1.y, (float)(gs.scay1 + 1)); + + if (x0 >= x1 || y0 >= y1) + return; + + // Use the second vertex color (PS2 SPRITE convention) + uint8_t r = v1.r, g = v1.g, b = v1.b, a = v1.a; + float z = v1.z; + + GsGpuPrimitive prim{}; + prim.type = GS_GPU_QUAD; + prim.vertexCount = 4; + // v0=Top-left, v1=Top-right, v2=Bottom-right, v3=Bottom-left + prim.verts[0] = {x0, y0, z, r, g, b, a, 0.0f, 0.0f}; + prim.verts[1] = {x1, y0, z, r, g, b, a, 1.0f, 0.0f}; + prim.verts[2] = {x1, y1, z, r, g, b, a, 1.0f, 1.0f}; + prim.verts[3] = {x0, y1, z, r, g, b, a, 0.0f, 1.0f}; + + gsGpuGetFrameData().pushPrimitive(prim); + gs.primsDrawn++; + } + + void drawTriangle(uint8_t * /*vram*/, uint32_t /*vramSize*/) + { + auto &gs = g_gsRegs; + if (gs.vtxCount < 3) + return; + + bool gouraud; + { + // Determine effective primitive attributes (PRMODECONT) + uint32_t effectivePrim = (gs.prmodecont == 1) ? gs.prim : ((gs.prim & 0x7) | (gs.prmode & ~0x7)); + gouraud = (effectivePrim >> 3) & 1; // IIP bit + } + + GsGpuPrimitive prim{}; + prim.type = GS_GPU_TRIANGLE; + prim.vertexCount = 3; + + if (gouraud) + { + // Per-vertex colors + prim.verts[0] = toGpuVertex(gs.vtxQueue[0]); + prim.verts[1] = toGpuVertex(gs.vtxQueue[1]); + prim.verts[2] = toGpuVertex(gs.vtxQueue[2]); + } + else + { + // Flat shading: use last vertex color for all + prim.verts[0] = toGpuVertex(gs.vtxQueue[0]); + prim.verts[1] = toGpuVertex(gs.vtxQueue[1]); + prim.verts[2] = toGpuVertex(gs.vtxQueue[2]); + // Override colors to match PS2 flat shading (last vertex) + uint8_t r = gs.vtxQueue[2].r, g = gs.vtxQueue[2].g; + uint8_t b = gs.vtxQueue[2].b, a = gs.vtxQueue[2].a; + prim.verts[0].r = r; + prim.verts[0].g = g; + prim.verts[0].b = b; + prim.verts[0].a = a; + prim.verts[1].r = r; + prim.verts[1].g = g; + prim.verts[1].b = b; + prim.verts[1].a = a; + } + + gsGpuGetFrameData().pushPrimitive(prim); + gs.primsDrawn++; + } + + void drawLine(uint8_t * /*vram*/, uint32_t /*vramSize*/) + { + auto &gs = g_gsRegs; + if (gs.vtxCount < 2) + return; + + // Determine effective primitive attributes (PRMODECONT) + uint32_t effectivePrim = (gs.prmodecont == 1) ? gs.prim : ((gs.prim & 0x7) | (gs.prmode & ~0x7)); + bool gouraud = (effectivePrim >> 3) & 1; // IIP bit + + GsGpuPrimitive prim{}; + prim.type = GS_GPU_LINE; + prim.vertexCount = 2; + prim.verts[0] = toGpuVertex(gs.vtxQueue[0]); + prim.verts[1] = toGpuVertex(gs.vtxQueue[1]); + + if (!gouraud) + { + // Flat shading: use last vertex color + prim.verts[0].r = prim.verts[1].r; + prim.verts[0].g = prim.verts[1].g; + prim.verts[0].b = prim.verts[1].b; + prim.verts[0].a = prim.verts[1].a; + } + + gsGpuGetFrameData().pushPrimitive(prim); + gs.primsDrawn++; + } + + // Submit vertex (XYZ2/XYZ3/XYZF2) + void submitVertex(uint8_t *vram, uint32_t vramSize, bool drawing) + { + auto &gs = g_gsRegs; + int primType = gs.prim & 0x7; + + switch (primType) + { + case 0: // POINT + if (gs.vtxCount >= 1 && drawing) + { + GsGpuPrimitive prim{}; + prim.type = GS_GPU_POINT; + prim.vertexCount = 1; + prim.verts[0] = toGpuVertex(gs.vtxQueue[0]); + gsGpuGetFrameData().pushPrimitive(prim); + gs.primsDrawn++; + gs.vtxCount = 0; + } + break; + case 1: // LINE + if (gs.vtxCount >= 2 && drawing) + { + drawLine(vram, vramSize); + gs.vtxCount = 0; + } + break; + case 2: // LINE_STRIP + if (gs.vtxCount >= 2 && drawing) + { + drawLine(vram, vramSize); + gs.vtxQueue[0] = gs.vtxQueue[1]; + gs.vtxCount = 1; + } + break; + case 3: // TRIANGLE + if (gs.vtxCount >= 3 && drawing) + { + drawTriangle(vram, vramSize); + gs.vtxCount = 0; + } + break; + case 4: // TRIANGLE_STRIP + if (gs.vtxCount >= 3 && drawing) + { + drawTriangle(vram, vramSize); + gs.vtxQueue[0] = gs.vtxQueue[1]; + gs.vtxQueue[1] = gs.vtxQueue[2]; + gs.vtxCount = 2; + } + break; + case 5: // TRIANGLE_FAN + if (gs.vtxCount >= 3 && drawing) + { + drawTriangle(vram, vramSize); + gs.vtxQueue[1] = gs.vtxQueue[2]; + gs.vtxCount = 2; + } + break; + case 6: // SPRITE + if (gs.vtxCount >= 2 && drawing) + { + drawSprite(vram, vramSize); + gs.vtxCount = 0; + } + break; + default: + gs.vtxCount = 0; + break; + } + } + + void handleADWrite(uint64_t data, uint8_t reg, uint8_t *vram, uint32_t vramSize, + PS2Memory::GSDrawContext &drawCtx) + { + auto &gs = g_gsRegs; + gs.adWrites++; + + switch (reg) + { + case GS_PRIM: + gs.prim = (uint32_t)(data & 0x7FF); + gs.vtxCount = 0; // reset vertex queue on new prim + break; + + case GS_RGBAQ: + gs.r = data & 0xFF; + gs.g = (data >> 8) & 0xFF; + gs.b = (data >> 16) & 0xFF; + gs.a = (data >> 24) & 0xFF; + // Q is in bits 32-63 as float + { + uint32_t qBits = (uint32_t)(data >> 32); + memcpy(&gs.q, &qBits, 4); + if (!std::isfinite(gs.q) || gs.q == 0.0f) + gs.q = 1.0f; + } + break; + + case GS_ST: + { + uint32_t sBits = (uint32_t)(data & 0xFFFFFFFF); + uint32_t tBits = (uint32_t)(data >> 32); + memcpy(&gs.s, &sBits, 4); + memcpy(&gs.t, &tBits, 4); + } + break; + + case GS_UV: + gs.u = (uint16_t)(data & 0x3FFF); + gs.v = (uint16_t)((data >> 16) & 0x3FFF); + break; + + case GS_XYZ2: + case GS_XYZ3: + { + // XYZ2 triggers drawing kick, XYZ3 does not + bool drawing = (reg == GS_XYZ2); + + // X,Y are 16-bit 12.4 fixed point + uint32_t xFix = (uint32_t)(data & 0xFFFF); + uint32_t yFix = (uint32_t)((data >> 16) & 0xFFFF); + uint32_t z = (uint32_t)(data >> 32); + + float x = (float)((int32_t)xFix - (drawing ? (int32_t)(gs.ofx & 0xFFFF) : 0)) / 16.0f; + float y = (float)((int32_t)yFix - (drawing ? (int32_t)(gs.ofy & 0xFFFF) : 0)) / 16.0f; + float zf = (float)z / 4294967295.0f; // normalize 32-bit Z to [0,1] + + if (gs.vtxCount < 3) + { + GSVertex &v = gs.vtxQueue[gs.vtxCount]; + v.x = x; + v.y = y; + v.z = zf; + v.r = gs.r; + v.g = gs.g; + v.b = gs.b; + v.a = gs.a; + v.s = gs.s; + v.t = gs.t; + v.q = gs.q; + v.u = gs.u; + v.v = gs.v; + gs.vtxCount++; + } + + submitVertex(vram, vramSize, drawing); + break; + } + + case GS_XYZF2: + case GS_XYZF3: + { + bool drawing = (reg == GS_XYZF2); + uint32_t xFix = (uint32_t)(data & 0xFFFF); + uint32_t yFix = (uint32_t)((data >> 16) & 0xFFFF); + // Z is bits 32-55, F is bits 56-63 + uint32_t z = (uint32_t)((data >> 32) & 0xFFFFFF); + + float x = (float)((int32_t)xFix - (drawing ? (int32_t)(gs.ofx & 0xFFFF) : 0)) / 16.0f; + float y = (float)((int32_t)yFix - (drawing ? (int32_t)(gs.ofy & 0xFFFF) : 0)) / 16.0f; + float zf = (float)z / 16777215.0f; // normalize 24-bit Z to [0,1] + + if (gs.vtxCount < 3) + { + GSVertex &v = gs.vtxQueue[gs.vtxCount]; + v.x = x; + v.y = y; + v.z = zf; + v.r = gs.r; + v.g = gs.g; + v.b = gs.b; + v.a = gs.a; + v.s = gs.s; + v.t = gs.t; + v.q = gs.q; + v.u = gs.u; + v.v = gs.v; + gs.vtxCount++; + } + + submitVertex(vram, vramSize, drawing); + break; + } + + case GS_FRAME_1: + case GS_FRAME_2: // Context 2 — alias to Context 1 + gs.fbp = (uint32_t)(data & 0x1FF); + gs.fbw = (uint32_t)((data >> 16) & 0x3F); + gs.psm = (uint32_t)((data >> 24) & 0x3F); + gs.fbmsk = (uint32_t)(data >> 32); + break; + + case GS_SCISSOR_1: + case GS_SCISSOR_2: // Context 2 — alias to Context 1 + gs.scax0 = (uint32_t)(data & 0x7FF); + gs.scax1 = (uint32_t)((data >> 16) & 0x7FF); + gs.scay0 = (uint32_t)((data >> 32) & 0x7FF); + gs.scay1 = (uint32_t)((data >> 48) & 0x7FF); + break; + + case GS_XYOFFSET_1: + case GS_XYOFFSET_2: // Context 2 — alias to Context 1 + gs.ofx = (uint32_t)(data & 0xFFFF); + gs.ofy = (uint32_t)((data >> 32) & 0xFFFF); + break; + + case GS_TEX0_1: + case GS_TEX0_2: // Context 2 — alias to Context 1 + gs.tex0 = data; + gs.tbp0 = (uint32_t)(data & 0x3FFF); + gs.tbw = (uint32_t)((data >> 14) & 0x3F); + gs.tpsm = (uint32_t)((data >> 20) & 0x3F); + gs.tw = (uint32_t)((data >> 26) & 0xF); + gs.th = (uint32_t)((data >> 30) & 0xF); + break; + + case GS_ALPHA_1: + case GS_ALPHA_2: + gs.alpha = data; + break; + + case GS_TEST_1: + case GS_TEST_2: + gs.test = data; + break; + + case GS_PRMODECONT: + gs.prmodecont = (uint32_t)(data & 1); + break; + + case GS_PRMODE: + gs.prmode = (uint32_t)(data & 0x7F8); // bits 3-10 + break; + + case GS_BITBLTBUF: + gs.bitbltbuf = data; + drawCtx.bitbltbuf = data; + break; + + case GS_TRXPOS: + gs.trxpos = data; + drawCtx.trxpos = data; + break; + + case GS_TRXREG: + gs.trxreg = data; + drawCtx.trxreg = data; + break; + + case GS_TRXDIR: + { + gs.trxdir = data; + drawCtx.trxdir = data; + uint32_t dir = data & 3; + if (dir == 0) + { + // Host -> Local (texture upload to GS VRAM) + gs.xferActive = true; + gs.xferDBP = (uint32_t)((gs.bitbltbuf >> 32) & 0x3FFF); + gs.xferDBW = (uint32_t)((gs.bitbltbuf >> 46) & 0x3F); + gs.xferDPSM = (uint32_t)((gs.bitbltbuf >> 56) & 0x3F); + gs.xferDstX = (uint32_t)((gs.trxpos >> 32) & 0x7FF); + gs.xferDstY = (uint32_t)((gs.trxpos >> 48) & 0x7FF); + gs.xferW = (uint32_t)(gs.trxreg & 0xFFF); + gs.xferH = (uint32_t)((gs.trxreg >> 32) & 0xFFF); + gs.xferPixelsWritten = 0; + gs.xferX = 0; + gs.xferY = 0; + // Reset PSMCT24 bit accumulator for new transfer. + g_psmct24_accBits = 0; + g_psmct24_accCount = 0; + + drawCtx.xferActive = true; + drawCtx.xferDBP = gs.xferDBP; + drawCtx.xferDBW = gs.xferDBW; + drawCtx.xferDPSM = gs.xferDPSM; + drawCtx.xferDestX = gs.xferDstX; + drawCtx.xferDestY = gs.xferDstY; + drawCtx.xferWidth = gs.xferW; + drawCtx.xferHeight = gs.xferH; + + if (g_gsLogCount < 20) + { + std::cerr << "[GS] IMAGE xfer start: DBP=" << gs.xferDBP + << " DBW=" << gs.xferDBW << " DPSM=" << gs.xferDPSM + << " dst=(" << gs.xferDstX << "," << gs.xferDstY << ")" + << " size=" << gs.xferW << "x" << gs.xferH << std::endl; + g_gsLogCount++; + } + drawCtx.imageTransfers++; + } + else if (dir == 1) + { + // Local -> Host (readback) - not needed for rendering + gs.xferActive = false; + } + else if (dir == 2) + { + // Local -> Local (VRAM copy) + uint32_t sbp = (uint32_t)(gs.bitbltbuf & 0x3FFF); + uint32_t sbw = (uint32_t)((gs.bitbltbuf >> 14) & 0x3F); + uint32_t spsm = (uint32_t)((gs.bitbltbuf >> 24) & 0x3F); + uint32_t dbp = (uint32_t)((gs.bitbltbuf >> 32) & 0x3FFF); + uint32_t dbw = (uint32_t)((gs.bitbltbuf >> 46) & 0x3F); + uint32_t dpsm = (uint32_t)((gs.bitbltbuf >> 56) & 0x3F); + uint32_t sx = (uint32_t)(gs.trxpos & 0x7FF); + uint32_t sy = (uint32_t)((gs.trxpos >> 16) & 0x7FF); + uint32_t dx = (uint32_t)((gs.trxpos >> 32) & 0x7FF); + uint32_t dy = (uint32_t)((gs.trxpos >> 48) & 0x7FF); + uint32_t w = (uint32_t)(gs.trxreg & 0xFFF); + uint32_t h = (uint32_t)((gs.trxreg >> 32) & 0xFFF); + + if (spsm == 0 && dpsm == 0 && w > 0 && h > 0) + { + // PSMCT32 copy — sbp/dbp are BP64 units (from BITBLTBUF) + for (uint32_t row = 0; row < h; row++) + { + for (uint32_t col = 0; col < w; col++) + { + uint8_t r, g, b, a; + readPixel32(vram, sbp, sbw, sx + col, sy + row, r, g, b, a, vramSize, true); + writePixel32(vram, dbp, dbw, dx + col, dy + row, r, g, b, a, vramSize, true); + } + } + } + gs.xferActive = false; + } + break; + } + + case GS_HWREG: + { + // Image data for Host -> Local transfer + if (!gs.xferActive) + break; + + // Each HWREG write delivers 64 bits of pixel data + // For PSMCT32: 2 pixels per write + if (gs.xferDPSM == 0) + { + // PSMCT32: 2 pixels per 64-bit write + for (int p = 0; p < 2 && gs.xferY < gs.xferH; p++) + { + uint32_t pixel = (p == 0) ? (uint32_t)(data & 0xFFFFFFFF) : (uint32_t)(data >> 32); + uint32_t dx = gs.xferDstX + gs.xferX; + uint32_t dy = gs.xferDstY + gs.xferY; + writePixel32(vram, gs.xferDBP, gs.xferDBW, dx, dy, + pixel & 0xFF, (pixel >> 8) & 0xFF, + (pixel >> 16) & 0xFF, (pixel >> 24) & 0xFF, + vramSize, true); // BP64 units for BITBLTBUF + gs.xferX++; + if (gs.xferX >= gs.xferW) + { + gs.xferX = 0; + gs.xferY++; + } + gs.xferPixelsWritten++; + } + } + else if (gs.xferDPSM == 0x13) + { + // PSMT8: 8 pixels per 64-bit write + for (int p = 0; p < 8 && gs.xferY < gs.xferH; p++) + { + uint8_t idx = (uint8_t)(data >> (p * 8)); + // For indexed textures, store index as grayscale + uint32_t dx = gs.xferDstX + gs.xferX; + uint32_t dy = gs.xferDstY + gs.xferY; + writePixel32(vram, gs.xferDBP, gs.xferDBW, dx, dy, + idx, idx, idx, 255, vramSize, true); + gs.xferX++; + if (gs.xferX >= gs.xferW) + { + gs.xferX = 0; + gs.xferY++; + } + gs.xferPixelsWritten++; + } + } + else if (gs.xferDPSM == 0x14) + { + // PSMT4: 16 pixels per 64-bit write + for (int p = 0; p < 16 && gs.xferY < gs.xferH; p++) + { + uint8_t idx = (uint8_t)((data >> (p * 4)) & 0xF); + uint32_t dx = gs.xferDstX + gs.xferX; + uint32_t dy = gs.xferDstY + gs.xferY; + writePixel32(vram, gs.xferDBP, gs.xferDBW, dx, dy, + idx * 17, idx * 17, idx * 17, 255, vramSize, true); + gs.xferX++; + if (gs.xferX >= gs.xferW) + { + gs.xferX = 0; + gs.xferY++; + } + gs.xferPixelsWritten++; + } + } + else if (gs.xferDPSM == 0x01) + { + // PSMCT24: 24-bit pixels. Use a bit accumulator to handle + // the non-aligned boundary (64 bits / 24 bits = 2.67 pixels). + g_psmct24_accBits |= (data << g_psmct24_accCount); + g_psmct24_accCount += 64; + + while (g_psmct24_accCount >= 24 && gs.xferY < gs.xferH) + { + uint32_t pixel = (uint32_t)(g_psmct24_accBits & 0xFFFFFF); + g_psmct24_accBits >>= 24; + g_psmct24_accCount -= 24; + + uint32_t dx = gs.xferDstX + gs.xferX; + uint32_t dy = gs.xferDstY + gs.xferY; + writePixel32(vram, gs.xferDBP, gs.xferDBW, dx, dy, + pixel & 0xFF, (pixel >> 8) & 0xFF, + (pixel >> 16) & 0xFF, 0x80, + vramSize, true); + gs.xferX++; + if (gs.xferX >= gs.xferW) + { + gs.xferX = 0; + gs.xferY++; + } + gs.xferPixelsWritten++; + } + } + else if (gs.xferDPSM == 0x30 || gs.xferDPSM == 0x31) + { + // PSMZ32/PSMZ24: z-buffer - skip for now + } + else + { + // PSMCT16/PSMCT16S: 4 pixels per 64-bit write + for (int p = 0; p < 4 && gs.xferY < gs.xferH; p++) + { + uint16_t pixel16 = (uint16_t)((data >> (p * 16)) & 0xFFFF); + uint8_t r = (pixel16 & 0x1F) << 3; + uint8_t g = ((pixel16 >> 5) & 0x1F) << 3; + uint8_t b = ((pixel16 >> 10) & 0x1F) << 3; + uint8_t a = (pixel16 & 0x8000) ? 128 : 0; + uint32_t dx = gs.xferDstX + gs.xferX; + uint32_t dy = gs.xferDstY + gs.xferY; + writePixel32(vram, gs.xferDBP, gs.xferDBW, dx, dy, r, g, b, a, vramSize, true); + gs.xferX++; + if (gs.xferX >= gs.xferW) + { + gs.xferX = 0; + gs.xferY++; + } + gs.xferPixelsWritten++; + } + } + + if (gs.xferY >= gs.xferH) + { + gs.xferActive = false; + drawCtx.xferActive = false; + drawCtx.xferPixelsWritten = gs.xferPixelsWritten; + } + break; + } + + case GS_TEXFLUSH: + // Texture cache flush - no-op for software renderer + break; + + default: + // Ignore unknown registers, maybe we should log? + break; + } + } + +} + +void PS2Memory::processGIFPacket(uint32_t srcAddr, uint32_t qwCount) +{ + if (!m_rdram || !m_gsVRAM || qwCount == 0) + return; + + if (srcAddr >= PS2_RAM_SIZE) + return; + + uint32_t pos = srcAddr; + const uint64_t requestedEnd = static_cast(srcAddr) + static_cast(qwCount) * 16ull; + uint32_t endAddr = requestedEnd > static_cast(PS2_RAM_SIZE) + ? PS2_RAM_SIZE + : static_cast(requestedEnd); + + while (pos + 16 <= endAddr) + { + // Read GIF tag (128 bits = 16 bytes) + uint64_t lo, hi; + memcpy(&lo, m_rdram + pos, 8); + memcpy(&hi, m_rdram + pos + 8, 8); + pos += 16; + + uint32_t nloop = (uint32_t)(lo & 0x7FFF); + bool eop = (lo >> 15) & 1; + // bool pre = (lo >> 46) & 1; // not used currently + uint32_t prim = (uint32_t)((lo >> 47) & 0x7FF); + uint32_t flg = (uint32_t)((lo >> 58) & 0x3); + uint32_t nreg = (uint32_t)((lo >> 60) & 0xF); + if (nreg == 0) + nreg = 16; + + // PRE bit: if set, write PRIM register + bool pre = (lo >> 46) & 1; + if (pre && flg != 2) + { // not IMAGE mode + g_gsRegs.prim = prim; + g_gsRegs.vtxCount = 0; + } + + g_gsRegs.gifTagsProcessed++; + m_gsDrawCtx.gifTagsProcessed = g_gsRegs.gifTagsProcessed; + + // GS Q register resets to 1.0f when a GIFtag is read (ps2tek spec) + g_gsRegs.q = 1.0f; + + // If NLOOP==0, no processing — just check EOP + if (nloop == 0) + { + if (eop) + break; + continue; + } + + if (flg == 0) + { + // PACKED mode + uint64_t regs = hi; + for (uint32_t loop = 0; loop < nloop && pos + 16 <= endAddr; loop++) + { + for (uint32_t r = 0; r < nreg && pos + 16 <= endAddr; r++) + { + uint8_t regId = (uint8_t)((regs >> (r * 4)) & 0xF); + + uint64_t dataLo, dataHi; + memcpy(&dataLo, m_rdram + pos, 8); + memcpy(&dataHi, m_rdram + pos + 8, 8); + pos += 16; + + // In PACKED mode, most regs use dataLo, except A+D which uses both + if (regId == 0x0E) + { + // A+D: dataLo = value, dataHi low byte = register address + uint8_t gsReg = (uint8_t)(dataHi & 0xFF); + handleADWrite(dataLo, gsReg, m_gsVRAM, PS2_GS_VRAM_SIZE, m_gsDrawCtx); + } + else if (regId == 0x0F) + { + // NOP + } + else + { + // Direct register write in PACKED format + // Convert PACKED register data to A+D equivalent + switch (regId) + { + case 0x00: // PRIM + handleADWrite(dataLo & 0x7FF, GS_PRIM, m_gsVRAM, PS2_GS_VRAM_SIZE, m_gsDrawCtx); + break; + case 0x01: // RGBA (PACKED writes RGBAQ, Q unchanged) + { + // PACKED RGBA: R=lo[7:0], G=lo[39:32], B=hi[7:0], A=hi[39:32] + g_gsRegs.r = (uint8_t)(dataLo & 0xFF); + g_gsRegs.g = (uint8_t)((dataLo >> 32) & 0xFF); + g_gsRegs.b = (uint8_t)(dataHi & 0xFF); + g_gsRegs.a = (uint8_t)((dataHi >> 32) & 0xFF); + // Do NOT touch g_gsRegs.q — PACKED RGBA leaves Q unchanged + break; + } + case 0x02: // ST + { + // PACKED ST: lo[31:0]=S, lo[63:32]=T, hi[31:0]=Q + uint32_t sVal = (uint32_t)(dataLo & 0xFFFFFFFF); + uint32_t tVal = (uint32_t)(dataLo >> 32); + uint32_t qVal = (uint32_t)(dataHi & 0xFFFFFFFF); + memcpy(&g_gsRegs.s, &sVal, 4); + memcpy(&g_gsRegs.t, &tVal, 4); + memcpy(&g_gsRegs.q, &qVal, 4); + if (!std::isfinite(g_gsRegs.q) || g_gsRegs.q == 0.0f) + g_gsRegs.q = 1.0f; + break; + } + case 0x03: // UV (PACKED: U=lo[13:0], V=lo[45:32]) + { + uint16_t u = (uint16_t)(dataLo & 0x3FFF); + uint16_t v = (uint16_t)((dataLo >> 32) & 0x3FFF); + // Repack to A+D UV layout: U=bits[13:0], V=bits[29:16] + uint64_t uv = (uint64_t)u | ((uint64_t)v << 16); + handleADWrite(uv, GS_UV, m_gsVRAM, PS2_GS_VRAM_SIZE, m_gsDrawCtx); + break; + } + case 0x04: // XYZF2/XYZF3 + { + // PACKED XYZF: X=lo[15:0], Y=lo[47:32] + // Z=hi[27:4] (24 bits), F=hi[43:36] (8 bits) + // ADC=hi[47] (bit 111 of 128-bit QW) + uint32_t x = (uint32_t)(dataLo & 0xFFFF); + uint32_t y = (uint32_t)((dataLo >> 32) & 0xFFFF); + uint32_t z = (uint32_t)((dataHi >> 4) & 0x00FFFFFF); // 24-bit Z + uint32_t f = (uint32_t)((dataHi >> 36) & 0xFF); // fog coeff + bool adc = ((dataHi >> 47) & 1) != 0; // bit 111 + + uint8_t gsReg = adc ? GS_XYZF3 : GS_XYZF2; + uint64_t xyzf = (uint64_t)x | ((uint64_t)y << 16) | ((uint64_t)z << 32) | ((uint64_t)f << 56); + handleADWrite(xyzf, gsReg, m_gsVRAM, PS2_GS_VRAM_SIZE, m_gsDrawCtx); + break; + } + case 0x05: // XYZ2/XYZ3 + { + uint32_t x = (uint32_t)(dataLo & 0xFFFF); + uint32_t y = (uint32_t)((dataLo >> 32) & 0xFFFF); + uint32_t z = (uint32_t)(dataHi & 0xFFFFFFFF); + bool adc = ((dataHi >> 47) & 1) != 0; // bit 111 + uint8_t gsReg = adc ? GS_XYZ3 : GS_XYZ2; + uint64_t xyz = (uint64_t)x | ((uint64_t)y << 16) | ((uint64_t)z << 32); + handleADWrite(xyz, gsReg, m_gsVRAM, PS2_GS_VRAM_SIZE, m_gsDrawCtx); + break; + } + case 0x0A: // FOG + // Ignore fog for now + break; + default: + // Other packed regs - pass through as A+D + handleADWrite(dataLo, regId, m_gsVRAM, PS2_GS_VRAM_SIZE, m_gsDrawCtx); + break; + } + } + } + } + } + else if (flg == 1) + { + // REGLIST mode: stream is DWs; each QW contains 2 DWs + // A+D is NOT available in REGLIST (only regs 0x0..0xD) + uint64_t regs = hi; + + uint64_t totalDw = (uint64_t)nloop * (uint64_t)nreg; + uint64_t dwIndex = 0; + + while (dwIndex < totalDw && pos + 16 <= endAddr) + { + uint64_t qwLo, qwHi; + memcpy(&qwLo, m_rdram + pos, 8); + memcpy(&qwHi, m_rdram + pos + 8, 8); + pos += 16; + + // DW0 + if (dwIndex < totalDw) + { + uint32_t r = (uint32_t)(dwIndex % nreg); + uint8_t regId = (uint8_t)((regs >> (r * 4)) & 0xF); + if (regId != 0x0E && regId != 0x0F) + { + handleADWrite(qwLo, regId, m_gsVRAM, PS2_GS_VRAM_SIZE, m_gsDrawCtx); + } + dwIndex++; + } + + // DW1 + if (dwIndex < totalDw) + { + uint32_t r = (uint32_t)(dwIndex % nreg); + uint8_t regId = (uint8_t)((regs >> (r * 4)) & 0xF); + if (regId != 0x0E && regId != 0x0F) + { + handleADWrite(qwHi, regId, m_gsVRAM, PS2_GS_VRAM_SIZE, m_gsDrawCtx); + } + dwIndex++; + } + } + } + else if (flg == 2) + { + // IMAGE mode: raw pixel data for Host->Local transfer + uint32_t imageBytes = nloop * 16; + if (g_gsRegs.xferActive && m_gsVRAM) + { + for (uint32_t i = 0; i < nloop && pos + 16 <= endAddr; i++) + { + uint64_t lo2, hi2; + memcpy(&lo2, m_rdram + pos, 8); + memcpy(&hi2, m_rdram + pos + 8, 8); + + // Process as two HWREG writes (each 64 bits) + handleADWrite(lo2, GS_HWREG, m_gsVRAM, PS2_GS_VRAM_SIZE, m_gsDrawCtx); + handleADWrite(hi2, GS_HWREG, m_gsVRAM, PS2_GS_VRAM_SIZE, m_gsDrawCtx); + + pos += 16; + g_gsRegs.imageQWs++; + } + } + else + { + pos += imageBytes; + if (pos > endAddr) + pos = endAddr; + } + } + else + { + // flg == 3: disabled/reserved + break; + } + + if (eop) + break; + } + + m_gsDrawCtx.gifTagsProcessed = g_gsRegs.gifTagsProcessed; + m_gsDrawCtx.adWrites = g_gsRegs.adWrites; + m_gsDrawCtx.primitivesDrawn = g_gsRegs.primsDrawn; + + m_gsWriteCount.fetch_add(1, std::memory_order_relaxed); + m_seenGifCopy = true; +} diff --git a/ps2xRuntime/src/lib/ps2_memory.cpp b/ps2xRuntime/src/lib/ps2_memory.cpp index 25825ba..3e424ce 100644 --- a/ps2xRuntime/src/lib/ps2_memory.cpp +++ b/ps2xRuntime/src/lib/ps2_memory.cpp @@ -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(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(lo) | (static_cast(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(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(qwCount) * 16ull; - uint32_t bytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast(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(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(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(qwCount) * 16ull; + uint32_t bytes = bytes64 > static_cast(PS2_RAM_SIZE) + ? PS2_RAM_SIZE + : static_cast(bytes64); + if (srcPhys + bytes > PS2_RAM_SIZE) { - bytes = std::min(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(m_rdram, physTag, PS2_RAM_SIZE, "dma chain tag", curTadr); + const uint16_t tagQwc = static_cast(tag & 0xFFFFu); + const uint32_t id = static_cast((tag >> 28) & 0x7u); + const uint32_t addr = static_cast((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(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(tp, 0, 16, "dma chain tag", tadr); - uint16_t tagQwc = static_cast(tag & 0xFFFF); - uint32_t id = static_cast((tag >> 28) & 0x7); - uint32_t addr = static_cast((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 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); diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index 1a01c93..73f696e 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -14,6 +14,7 @@ #include #include #include "raylib.h" +#include "ps2_gs_gpu.h" #include #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((display64 >> 23) & 0x7); // magnification H (0-7) uint32_t dw = static_cast((display64 >> 32) & 0xFFF); uint32_t dh = static_cast((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 scratch(FB_WIDTH * FB_HEIGHT * 4, 0); // maybe we can do this static + static std::vector 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()) diff --git a/ps2xRuntime/src/lib/ps2_stubs.cpp b/ps2xRuntime/src/lib/ps2_stubs.cpp index 08d47e0..7b48003 100644 --- a/ps2xRuntime/src/lib/ps2_stubs.cpp +++ b/ps2xRuntime/src/lib/ps2_stubs.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "stubs/helpers/ps2_stubs_helpers.inl" diff --git a/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp b/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp new file mode 100644 index 0000000..9d8fe9b --- /dev/null +++ b/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp @@ -0,0 +1,268 @@ +// Based on Blackline Interactive implementation +#include "ps2_memory.h" +#include +#include + +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(srcPhys) + static_cast(sizeBytes); + if (requestedEnd > static_cast(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; + } +} diff --git a/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl b/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl index e9e373a..bc689ad 100644 --- a/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl +++ b/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl @@ -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) diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl index 7a3eaae..9b39d67 100644 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl +++ b/ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl @@ -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(2048 - (w / 2)); + env.offset_y = static_cast(2048 - (h / 2)); + env.clip_x = 0; + env.clip_y = 0; + env.clip_w = static_cast(w); + env.clip_h = static_cast(h); + env.vram_addr = static_cast(vramAddr & 0xFFFFu); + env.fbw = static_cast((w + 63u) / 64u); + env.psm = static_cast(psm & 0xFFu); + env.vram_x = static_cast(vramX & 0xFFFFu); + env.vram_y = static_cast(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)); } diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl index 71c14a7..f73eb74 100644 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl +++ b/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl @@ -47,7 +47,32 @@ void malloc_trim_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void mbtowc_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("mbtowc_r", rdram, ctx, runtime); + const uint32_t wcAddr = getRegU32(ctx, 5); // $a1 + const uint32_t strAddr = getRegU32(ctx, 6); // $a2 + const int32_t n = static_cast(getRegU32(ctx, 7)); // $a3 + if (n <= 0 || strAddr == 0u) + { + setReturnS32(ctx, 0); + return; + } + + const uint8_t *src = getConstMemPtr(rdram, strAddr); + if (!src) + { + setReturnS32(ctx, -1); + return; + } + + const uint8_t ch = *src; + if (wcAddr != 0u) + { + if (uint8_t *dst = getMemPtr(rdram, wcAddr)) + { + const uint32_t out = static_cast(ch); + std::memcpy(dst, &out, sizeof(out)); + } + } + setReturnS32(ctx, (ch == 0u) ? 0 : 1); } void printf_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -125,7 +150,7 @@ void sceFsSigSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceIDC(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceIDC", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceMpegFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -150,7 +175,7 @@ void sceRpcGetFPacket2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSDC(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSDC", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceSifCmdIntrHdlr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -160,12 +185,29 @@ void sceSifCmdIntrHdlr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifLoadModule", rdram, ctx, runtime); + ps2_syscalls::SifLoadModule(rdram, ctx, runtime); } void sceSifSendCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifSendCmd", rdram, ctx, runtime); + const uint32_t srcAddr = getRegU32(ctx, 7); // $a3 + const uint32_t dstAddr = readStackU32(rdram, ctx, 16); + const uint32_t size = readStackU32(rdram, ctx, 20); + if (size != 0u && srcAddr != 0u && dstAddr != 0u) + { + for (uint32_t i = 0; i < size; ++i) + { + const uint8_t *src = getConstMemPtr(rdram, srcAddr + i); + uint8_t *dst = getMemPtr(rdram, dstAddr + i); + if (!src || !dst) + { + break; + } + *dst = *src; + } + } + + setReturnS32(ctx, 1); } void sceVu0ecossin(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -175,12 +217,32 @@ void sceVu0ecossin(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void abs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("abs", rdram, ctx, runtime); + const int32_t value = static_cast(getRegU32(ctx, 4)); + if (value == std::numeric_limits::min()) + { + setReturnS32(ctx, std::numeric_limits::max()); + return; + } + setReturnS32(ctx, value < 0 ? -value : value); } void atan(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("atan", rdram, ctx, runtime); + float in = ctx ? ctx->f[12] : 0.0f; + if (in == 0.0f) + { + uint32_t raw = getRegU32(ctx, 4); + std::memcpy(&in, &raw, sizeof(in)); + } + const float out = std::atan(in); + if (ctx) + { + ctx->f[0] = out; + } + + uint32_t outRaw = 0u; + std::memcpy(&outRaw, &out, sizeof(outRaw)); + setReturnU32(ctx, outRaw); } void close(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -190,12 +252,16 @@ void close(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void DmaAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("DmaAddr", rdram, ctx, runtime); + setReturnU32(ctx, getRegU32(ctx, 4)); } void exit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("exit", rdram, ctx, runtime); + if (runtime) + { + runtime->requestStop(); + } + setReturnS32(ctx, 0); } void fstat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -212,12 +278,12 @@ void fstat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void getpid(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("getpid", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void iopGetArea(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("iopGetArea", rdram, ctx, runtime); + setReturnU32(ctx, kIopHeapBase); } void lseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -227,7 +293,25 @@ void lseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void memchr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("memchr", rdram, ctx, runtime); + const uint32_t srcAddr = getRegU32(ctx, 4); + const uint8_t needle = static_cast(getRegU32(ctx, 5) & 0xFFu); + const uint32_t size = getRegU32(ctx, 6); + + for (uint32_t i = 0; i < size; ++i) + { + const uint8_t *src = getConstMemPtr(rdram, srcAddr + i); + if (!src) + { + break; + } + if (*src == needle) + { + setReturnU32(ctx, srcAddr + i); + return; + } + } + + setReturnU32(ctx, 0u); } void open(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -242,12 +326,19 @@ void Pad_init(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void Pad_set(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("Pad_set", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void rand(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("rand", rdram, ctx, runtime); + setReturnS32(ctx, std::rand() & 0x7FFF); +} + +namespace +{ + std::mutex g_mcStateMutex; + int32_t g_mcNextFd = 1; + int32_t g_mcLastResult = 0; } void read(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -843,7 +934,7 @@ void sceDmaWatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceFsInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceFsInit", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceFsReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -853,27 +944,27 @@ void sceFsReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceIoctl(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceIoctl", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceIpuInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceIpuInit", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceIpuRestartDMA(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceIpuRestartDMA", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceIpuStopDMA(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceIpuStopDMA", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceIpuSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceIpuSync", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceLseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -883,52 +974,110 @@ void sceLseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceMcChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcChangeThreadPriority", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceMcChdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcChdir", rdram, ctx, runtime); + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = 0; + } + setReturnS32(ctx, 0); } void sceMcClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcClose", rdram, ctx, runtime); + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = 0; + } + setReturnS32(ctx, 0); } void sceMcDelete(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcDelete", rdram, ctx, runtime); + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = 0; + } + setReturnS32(ctx, 0); } void sceMcFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcFlush", rdram, ctx, runtime); + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = 0; + } + setReturnS32(ctx, 0); } void sceMcFormat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcFormat", rdram, ctx, runtime); + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = 0; + } + setReturnS32(ctx, 0); } void sceMcGetDir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcGetDir", rdram, ctx, runtime); + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = 0; + } + setReturnS32(ctx, 0); } void sceMcGetEntSpace(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcGetEntSpace", rdram, ctx, runtime); + setReturnS32(ctx, 1024); } void sceMcGetInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcGetInfo", rdram, ctx, runtime); + const uint32_t typePtr = getRegU32(ctx, 6); + const uint32_t freePtr = getRegU32(ctx, 7); + const uint32_t formatPtr = readStackU32(rdram, ctx, 16); + + const int32_t cardType = 2; // PS2 memory card. + const int32_t freeBlocks = 0x2000; + const int32_t format = 2; // formatted. + + if (typePtr != 0u) + { + if (uint8_t *out = getMemPtr(rdram, typePtr)) + { + std::memcpy(out, &cardType, sizeof(cardType)); + } + } + if (freePtr != 0u) + { + if (uint8_t *out = getMemPtr(rdram, freePtr)) + { + std::memcpy(out, &freeBlocks, sizeof(freeBlocks)); + } + } + if (formatPtr != 0u) + { + if (uint8_t *out = getMemPtr(rdram, formatPtr)) + { + std::memcpy(out, &format, sizeof(format)); + } + } + + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = 0; + } + setReturnS32(ctx, 0); } void sceMcGetSlotMax(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcGetSlotMax", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceMcInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -944,47 +1093,121 @@ void sceMcInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceMcMkdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcMkdir", rdram, ctx, runtime); + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = 0; + } + setReturnS32(ctx, 0); } void sceMcOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcOpen", rdram, ctx, runtime); + int32_t fd = 0; + { + std::lock_guard lock(g_mcStateMutex); + fd = g_mcNextFd++; + if (g_mcNextFd <= 0) + { + g_mcNextFd = 1; + } + g_mcLastResult = fd; + } + setReturnS32(ctx, 0); } void sceMcRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcRead", rdram, ctx, runtime); + const int32_t size = static_cast(getRegU32(ctx, 7)); + if (size > 0) + { + const uint32_t dstAddr = readStackU32(rdram, ctx, 16); + if (uint8_t *dst = getMemPtr(rdram, dstAddr)) + { + std::memset(dst, 0, static_cast(size)); + } + } + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = std::max(0, size); + } + setReturnS32(ctx, 0); } void sceMcRename(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcRename", rdram, ctx, runtime); + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = 0; + } + setReturnS32(ctx, 0); } void sceMcSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcSeek", rdram, ctx, runtime); + const int32_t offset = static_cast(getRegU32(ctx, 5)); + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = std::max(0, offset); + } + setReturnS32(ctx, 0); } void sceMcSetFileInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcSetFileInfo", rdram, ctx, runtime); + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = 0; + } + setReturnS32(ctx, 0); } void sceMcSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcSync", rdram, ctx, runtime); + const uint32_t cmdPtr = getRegU32(ctx, 5); + const uint32_t resultPtr = getRegU32(ctx, 6); + int32_t result = 0; + { + std::lock_guard lock(g_mcStateMutex); + result = g_mcLastResult; + } + + if (cmdPtr != 0u) + { + if (uint8_t *out = getMemPtr(rdram, cmdPtr)) + { + const int32_t cmd = 0; + std::memcpy(out, &cmd, sizeof(cmd)); + } + } + if (resultPtr != 0u) + { + if (uint8_t *out = getMemPtr(rdram, resultPtr)) + { + std::memcpy(out, &result, sizeof(result)); + } + } + + // 1 = command finished in this runtime's immediate model. + setReturnS32(ctx, 1); } void sceMcUnformat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcUnformat", rdram, ctx, runtime); + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = 0; + } + setReturnS32(ctx, 0); } void sceMcWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcWrite", rdram, ctx, runtime); + const int32_t size = static_cast(getRegU32(ctx, 7)); + { + std::lock_guard lock(g_mcStateMutex); + g_mcLastResult = std::max(0, size); + } + setReturnS32(ctx, 0); } void sceMpegAddBs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1114,32 +1337,33 @@ void sceOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void scePadEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadEnd", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void scePadEnterPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadEnterPressMode", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void scePadExitPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadExitPressMode", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void scePadGetButtonMask(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadGetButtonMask", rdram, ctx, runtime); + setReturnS32(ctx, static_cast(0xFFFFu)); } void scePadGetDmaStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadGetDmaStr", rdram, ctx, runtime); + setReturnU32(ctx, getRegU32(ctx, 6)); } void scePadGetFrameCount(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadGetFrameCount", rdram, ctx, runtime); + static uint32_t frameCount = 0u; + setReturnU32(ctx, ++frameCount); } void scePadGetModVersion(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1183,12 +1407,18 @@ void scePadGetState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void scePadInfoAct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadInfoAct", rdram, ctx, runtime); + const int32_t act = static_cast(getRegU32(ctx, 6)); + if (act < 0) + { + setReturnS32(ctx, 1); // one actuator descriptor + return; + } + setReturnS32(ctx, 0); } void scePadInfoComb(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadInfoComb", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void scePadInfoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1295,47 +1525,76 @@ void scePadRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void scePadReqIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadReqIntToStr", rdram, ctx, runtime); + const uint32_t outAddr = getRegU32(ctx, 5); + if (uint8_t *out = getMemPtr(rdram, outAddr)) + { + constexpr const char *kReq = "COMPLETE"; + std::memcpy(out, kReq, std::strlen(kReq) + 1u); + setReturnU32(ctx, outAddr); + return; + } + setReturnU32(ctx, 0u); } void scePadSetActAlign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetActAlign", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void scePadSetActDirect(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetActDirect", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void scePadSetButtonInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetButtonInfo", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void scePadSetMainMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetMainMode", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void scePadSetReqState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetReqState", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void scePadSetVrefParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetVrefParam", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void scePadSetWarningLevel(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadSetWarningLevel", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void scePadStateIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadStateIntToStr", rdram, ctx, runtime); + const int32_t state = static_cast(getRegU32(ctx, 4)); + const uint32_t outAddr = getRegU32(ctx, 5); + const char *label = "UNKNOWN"; + switch (state) + { + case 0: + label = "DISCONN"; + break; + case 6: + label = "STABLE"; + break; + default: + break; + } + + if (uint8_t *out = getMemPtr(rdram, outAddr)) + { + std::memcpy(out, label, std::strlen(label) + 1u); + setReturnU32(ctx, outAddr); + return; + } + setReturnU32(ctx, 0u); } void scePrintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1396,6 +1655,13 @@ namespace std::mutex g_sifDmaTransferMutex; uint32_t g_nextSifDmaTransferId = 1u; + std::mutex g_sifCmdStateMutex; + std::unordered_map g_sifRegs; + std::unordered_map g_sifSregs; + std::unordered_map g_sifCmdHandlers; + uint32_t g_sifCmdBuffer = 0u; + uint32_t g_sifSysCmdBuffer = 0u; + bool g_sifCmdInitialized = false; uint32_t allocateSifDmaTransferId() { @@ -1452,7 +1718,11 @@ namespace void sceSifAddCmdHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifAddCmdHandler", rdram, ctx, runtime); + const uint32_t cid = getRegU32(ctx, 4); + const uint32_t handler = getRegU32(ctx, 5); + std::lock_guard lock(g_sifCmdStateMutex); + g_sifCmdHandlers[cid] = handler; + setReturnS32(ctx, 0); } void sceSifAllocIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1497,7 +1767,10 @@ void sceSifExecRequest(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifExitCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifExitCmd", rdram, ctx, runtime); + std::lock_guard lock(g_sifCmdStateMutex); + g_sifCmdInitialized = false; + g_sifCmdHandlers.clear(); + setReturnS32(ctx, 0); } void sceSifExitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1512,12 +1785,13 @@ void sceSifFreeIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifGetDataTable(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifGetDataTable", rdram, ctx, runtime); + std::lock_guard lock(g_sifCmdStateMutex); + setReturnU32(ctx, g_sifCmdBuffer); } void sceSifGetIopAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifGetIopAddr", rdram, ctx, runtime); + setReturnU32(ctx, getRegU32(ctx, 4)); } void sceSifGetNextRequest(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1582,17 +1856,39 @@ void sceSifGetOtherData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifGetReg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifGetReg", rdram, ctx, runtime); + const uint32_t reg = getRegU32(ctx, 4); + uint32_t value = 0u; + { + std::lock_guard lock(g_sifCmdStateMutex); + auto it = g_sifRegs.find(reg); + if (it != g_sifRegs.end()) + { + value = it->second; + } + } + setReturnU32(ctx, value); } void sceSifGetSreg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifGetSreg", rdram, ctx, runtime); + const uint32_t reg = getRegU32(ctx, 4); + uint32_t value = 0u; + { + std::lock_guard lock(g_sifCmdStateMutex); + auto it = g_sifSregs.find(reg); + if (it != g_sifSregs.end()) + { + value = it->second; + } + } + setReturnU32(ctx, value); } void sceSifInitCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifInitCmd", rdram, ctx, runtime); + std::lock_guard lock(g_sifCmdStateMutex); + g_sifCmdInitialized = true; + setReturnS32(ctx, 0); } void sceSifInitIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1608,7 +1904,7 @@ void sceSifInitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifIsAliveIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifIsAliveIop", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceSifLoadElf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1623,7 +1919,7 @@ void sceSifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifLoadFileReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifLoadFileReset", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceSifLoadIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1648,7 +1944,10 @@ void sceSifRegisterRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifRemoveCmdHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifRemoveCmdHandler", rdram, ctx, runtime); + const uint32_t cid = getRegU32(ctx, 4); + std::lock_guard lock(g_sifCmdStateMutex); + g_sifCmdHandlers.erase(cid); + setReturnS32(ctx, 0); } void sceSifRemoveRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1663,7 +1962,7 @@ void sceSifRemoveRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime void sceSifResetIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifResetIop", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceSifRpcLoop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1673,7 +1972,14 @@ void sceSifRpcLoop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifSetCmdBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifSetCmdBuffer", rdram, ctx, runtime); + const uint32_t newBuffer = getRegU32(ctx, 4); + uint32_t prev = 0u; + { + std::lock_guard lock(g_sifCmdStateMutex); + prev = g_sifCmdBuffer; + g_sifCmdBuffer = newBuffer; + } + setReturnU32(ctx, prev); } void sceSifSetDChain(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1745,12 +2051,24 @@ void sceSifSetDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifSetIopAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifSetIopAddr", rdram, ctx, runtime); + setReturnU32(ctx, getRegU32(ctx, 5)); } void sceSifSetReg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifSetReg", rdram, ctx, runtime); + const uint32_t reg = getRegU32(ctx, 4); + const uint32_t value = getRegU32(ctx, 5); + uint32_t prev = 0u; + { + std::lock_guard lock(g_sifCmdStateMutex); + auto it = g_sifRegs.find(reg); + if (it != g_sifRegs.end()) + { + prev = it->second; + } + g_sifRegs[reg] = value; + } + setReturnU32(ctx, prev); } void sceSifSetRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1760,17 +2078,36 @@ void sceSifSetRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifSetSreg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifSetSreg", rdram, ctx, runtime); + const uint32_t reg = getRegU32(ctx, 4); + const uint32_t value = getRegU32(ctx, 5); + uint32_t prev = 0u; + { + std::lock_guard lock(g_sifCmdStateMutex); + auto it = g_sifSregs.find(reg); + if (it != g_sifSregs.end()) + { + prev = it->second; + } + g_sifSregs[reg] = value; + } + setReturnU32(ctx, prev); } void sceSifSetSysCmdBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifSetSysCmdBuffer", rdram, ctx, runtime); + const uint32_t newBuffer = getRegU32(ctx, 4); + uint32_t prev = 0u; + { + std::lock_guard lock(g_sifCmdStateMutex); + prev = g_sifSysCmdBuffer; + g_sifSysCmdBuffer = newBuffer; + } + setReturnU32(ctx, prev); } void sceSifStopDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifStopDma", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceSifSyncIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1780,7 +2117,7 @@ void sceSifSyncIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceSifWriteBackDCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifWriteBackDCache", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceSSyn_BreakAtick(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2248,6 +2585,53 @@ void sceTtyWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) TODO_NAMED("sceTtyWrite", rdram, ctx, runtime); } +namespace +{ + bool readVuVec4f(uint8_t *rdram, uint32_t addr, float (&out)[4]) + { + const uint8_t *ptr = getConstMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(out, ptr, sizeof(out)); + return true; + } + + bool writeVuVec4f(uint8_t *rdram, uint32_t addr, const float (&in)[4]) + { + uint8_t *ptr = getMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(ptr, in, sizeof(in)); + return true; + } + + bool readVuVec4i(uint8_t *rdram, uint32_t addr, int32_t (&out)[4]) + { + const uint8_t *ptr = getConstMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(out, ptr, sizeof(out)); + return true; + } + + bool writeVuVec4i(uint8_t *rdram, uint32_t addr, const int32_t (&in)[4]) + { + uint8_t *ptr = getMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(ptr, in, sizeof(in)); + return true; + } +} + void sceVpu0Reset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { setReturnS32(ctx, 0); @@ -2255,7 +2639,19 @@ void sceVpu0Reset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceVu0AddVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVu0AddVector", rdram, ctx, runtime); + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t lhsAddr = getRegU32(ctx, 5); + const uint32_t rhsAddr = getRegU32(ctx, 6); + float lhs[4]{}, rhs[4]{}, out[4]{}; + if (readVuVec4f(rdram, lhsAddr, lhs) && readVuVec4f(rdram, rhsAddr, rhs)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = lhs[i] + rhs[i]; + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); } void sceVu0ApplyMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2320,17 +2716,56 @@ void sceVu0DropShadowMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runti void sceVu0FTOI0Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVu0FTOI0Vector", rdram, ctx, runtime); + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + float src[4]{}; + int32_t out[4]{}; + if (readVuVec4f(rdram, srcAddr, src)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = static_cast(src[i]); + } + (void)writeVuVec4i(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); } void sceVu0FTOI4Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVu0FTOI4Vector", rdram, ctx, runtime); + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + float src[4]{}; + int32_t out[4]{}; + if (readVuVec4f(rdram, srcAddr, src)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = static_cast(src[i] * 16.0f); + } + (void)writeVuVec4i(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); } void sceVu0InnerProduct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVu0InnerProduct", rdram, ctx, runtime); + const uint32_t lhsAddr = getRegU32(ctx, 4); + const uint32_t rhsAddr = getRegU32(ctx, 5); + float lhs[4]{}, rhs[4]{}; + float dot = 0.0f; + if (readVuVec4f(rdram, lhsAddr, lhs) && readVuVec4f(rdram, rhsAddr, rhs)) + { + dot = (lhs[0] * rhs[0]) + (lhs[1] * rhs[1]) + (lhs[2] * rhs[2]) + (lhs[3] * rhs[3]); + } + + if (ctx) + { + ctx->f[0] = dot; + } + uint32_t raw = 0u; + std::memcpy(&raw, &dot, sizeof(raw)); + setReturnU32(ctx, raw); } void sceVu0InterVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2350,17 +2785,53 @@ void sceVu0InversMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceVu0ITOF0Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVu0ITOF0Vector", rdram, ctx, runtime); + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + int32_t src[4]{}; + float out[4]{}; + if (readVuVec4i(rdram, srcAddr, src)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = static_cast(src[i]); + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); } void sceVu0ITOF12Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVu0ITOF12Vector", rdram, ctx, runtime); + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + int32_t src[4]{}; + float out[4]{}; + if (readVuVec4i(rdram, srcAddr, src)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = static_cast(src[i]) / 4096.0f; + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); } void sceVu0ITOF4Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVu0ITOF4Vector", rdram, ctx, runtime); + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + int32_t src[4]{}; + float out[4]{}; + if (readVuVec4i(rdram, srcAddr, src)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = static_cast(src[i]) / 16.0f; + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); } void sceVu0LightColorMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2380,7 +2851,23 @@ void sceVu0MulVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceVu0Normalize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVu0Normalize", rdram, ctx, runtime); + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + float src[4]{}, out[4]{}; + if (readVuVec4f(rdram, srcAddr, src)) + { + const float len = std::sqrt((src[0] * src[0]) + (src[1] * src[1]) + (src[2] * src[2]) + (src[3] * src[3])); + if (len > 1.0e-6f) + { + const float invLen = 1.0f / len; + for (int i = 0; i < 4; ++i) + { + out[i] = src[i] * invLen; + } + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); } void sceVu0NormalLightMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2390,7 +2877,19 @@ void sceVu0NormalLightMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runt void sceVu0OuterProduct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVu0OuterProduct", rdram, ctx, runtime); + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t lhsAddr = getRegU32(ctx, 5); + const uint32_t rhsAddr = getRegU32(ctx, 6); + float lhs[4]{}, rhs[4]{}, out[4]{}; + if (readVuVec4f(rdram, lhsAddr, lhs) && readVuVec4f(rdram, rhsAddr, rhs)) + { + out[0] = (lhs[1] * rhs[2]) - (lhs[2] * rhs[1]); + out[1] = (lhs[2] * rhs[0]) - (lhs[0] * rhs[2]); + out[2] = (lhs[0] * rhs[1]) - (lhs[1] * rhs[0]); + out[3] = 0.0f; + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); } void sceVu0RotMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2425,7 +2924,29 @@ void sceVu0RotTransPersN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceVu0ScaleVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVu0ScaleVector", rdram, ctx, runtime); + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + float src[4]{}, out[4]{}; + float scale = ctx ? ctx->f[12] : 0.0f; + if (scale == 0.0f) + { + uint32_t raw = getRegU32(ctx, 6); + std::memcpy(&scale, &raw, sizeof(scale)); + if (scale == 0.0f) + { + scale = static_cast(getRegU32(ctx, 6)); + } + } + + if (readVuVec4f(rdram, srcAddr, src)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = src[i] * scale; + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); } void sceVu0ScaleVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2435,7 +2956,19 @@ void sceVu0ScaleVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime void sceVu0SubVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVu0SubVector", rdram, ctx, runtime); + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t lhsAddr = getRegU32(ctx, 5); + const uint32_t rhsAddr = getRegU32(ctx, 6); + float lhs[4]{}, rhs[4]{}, out[4]{}; + if (readVuVec4f(rdram, lhsAddr, lhs) && readVuVec4f(rdram, rhsAddr, rhs)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = lhs[i] - rhs[i]; + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); } void sceVu0TransMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2483,17 +3016,45 @@ void sceWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void srand(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("srand", rdram, ctx, runtime); + std::srand(getRegU32(ctx, 4)); + setReturnS32(ctx, 0); } void stat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("stat", rdram, ctx, runtime); + const uint32_t statAddr = getRegU32(ctx, 5); + uint8_t *statBuf = getMemPtr(rdram, statAddr); + if (!statBuf) + { + setReturnS32(ctx, -1); + return; + } + + // Minimal fake stat payload: zeroed structure indicates a valid, readable file. + std::memset(statBuf, 0, 128); + setReturnS32(ctx, 0); } void strcasecmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("strcasecmp", rdram, ctx, runtime); + const uint32_t lhsAddr = getRegU32(ctx, 4); + const uint32_t rhsAddr = getRegU32(ctx, 5); + const std::string lhs = readPs2CStringBounded(rdram, runtime, lhsAddr, 1024); + const std::string rhs = readPs2CStringBounded(rdram, runtime, rhsAddr, 1024); + + const size_t n = std::min(lhs.size(), rhs.size()); + for (size_t i = 0; i < n; ++i) + { + const int a = std::tolower(static_cast(lhs[i])); + const int b = std::tolower(static_cast(rhs[i])); + if (a != b) + { + setReturnS32(ctx, a - b); + return; + } + } + + setReturnS32(ctx, static_cast(lhs.size()) - static_cast(rhs.size())); } void vfprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl index 73676e3..83d4597 100644 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl +++ b/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl @@ -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(normalizedBase) + static_cast(heapSize); - heapLimit = static_cast(std::min(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(rdram + offset) = 0; + } + + offset = kSndBusyAddrLegacy & PS2_RAM_MASK; if (offset + sizeof(uint32_t) <= PS2_RAM_SIZE) { *reinterpret_cast(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(value.size())); } void mcMoveFileSelectWindowCursor(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("mcMoveFileSelectWindowCursor", rdram, ctx, runtime); + const int32_t delta = static_cast(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(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); } diff --git a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl b/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl index 03d4e80..834a0a5 100644 --- a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl +++ b/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl @@ -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 g_intcHandlers; @@ -328,6 +330,11 @@ static std::unordered_map 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) diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl index 4d72bf8..00fe7ce 100644 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl +++ b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl @@ -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 g_irq_worker_stop{false}; static std::atomic 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 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 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 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 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 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 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(getRegU32(ctx, 5)); if (handlerId > 0) { std::lock_guard 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 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(getRegU32(ctx, 5)); if (handlerId > 0) { std::lock_guard 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); } diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_rpc.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_rpc.inl index e0ef22e..fe085dc 100644 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_rpc.inl +++ b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_rpc.inl @@ -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 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 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(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(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(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(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(getMemPtr(rdram, qd)); - if (queue) + std::lock_guard lock(g_rpc_mutex); + + if (qd) { - if (!queue->link) + t_SifRpcDataQueue *queue = reinterpret_cast(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(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(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 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 lock(g_rpc_mutex); + if (qd->link == sdPtr) { t_SifRpcServerData *sd = reinterpret_cast(getMemPtr(rdram, sdPtr)); diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl index ac131ed..6efe05f 100644 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl +++ b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl @@ -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 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 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 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 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); } diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index 9fdf033..ee19eb2 100644 --- a/ps2xTest/src/code_generator_tests.cpp +++ b/ps2xTest/src/code_generator_tests.cpp @@ -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 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 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 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);