diff --git a/pcsx2/VU0.cpp b/pcsx2/VU0.cpp index 556a2ecd87..d616bdb854 100644 --- a/pcsx2/VU0.cpp +++ b/pcsx2/VU0.cpp @@ -122,7 +122,15 @@ void CFC2() { _vu0WaitMicro(); } if (_Rt_ == 0) return; - cpuRegs.GPR.r[_Rt_].UL[0] = VU0.VI[_Fs_].UL; + + if (_Fs_ == REG_TPC) { + // For explanation why this is needed here please refer to + // recCFC2() definded in microVU_Macro.inl + cpuRegs.GPR.r[_Rt_].UL[0] = VU0.VI[_Fs_].UL >> 3; + } else { + cpuRegs.GPR.r[_Rt_].UL[0] = VU0.VI[_Fs_].UL; + } + if(VU0.VI[_Fs_].UL & 0x80000000) cpuRegs.GPR.r[_Rt_].UL[1] = 0xffffffff; else diff --git a/pcsx2/x86/microVU_Macro.inl b/pcsx2/x86/microVU_Macro.inl index d0aa6a043e..4e136ccea1 100644 --- a/pcsx2/x86/microVU_Macro.inl +++ b/pcsx2/x86/microVU_Macro.inl @@ -276,6 +276,39 @@ static void recCFC2() { } else xMOV(eax, ptr32[&vu0Regs.VI[_Rd_].UL]); + if (_Rd_ == REG_TPC) { // Divide TPC register value by 8 during copying + // Ok, this deserves an explanation. + // Accoring to the official PS2 VU0 coding manual there are 3 ways to execute a micro subroutine on VU0 + // one of which is using the VCALLMSR intruction. + // The manual requires putting the address of the micro subroutine + // into the CMSAR0 register divided by 8 using the CTC2 command before executing VCALLMSR. + // Many games (for instance, 24: The Game, GTA LCS, GTA VCS and FFXII) do in fact use this way, + // they diligently put the address of the micro subroutine into a separate register (v0, v1 etc), divide it by 8 + // and move it to CMSAR0 by calling the CTC2 command. + + // However, there are also at least 2 confirmed games (R Racing Evolution, Street Fighter EX3) + // that execute a piece of code to run a micro subroutine on VU0 like this: + // + // ... + // cfc2 t4, TPC + // ctc2 t4, CMSAR0 + // callmsr + // ... + // + // Interestingly enough there is no division by 8 but it works fine in these 2 mentioned games. + // It means the division operation is implicit. + // Homebrew tests for the real PS2 have shown that in fact the instruction "cfc2 t4, TPC" ends up with values that are not always divisible by 8. + + // There are 2 possibilities: either the CFC2 instruction divides the value of the TPC (which is the Program Counter register + // for micro subroutines) by 8 itself during copying or the TPC register always works with addresses already divided by 8. + // The latter seems less possible because the Program Counter register by definition holds the memory address of the instruction. + // In addition, PCSX2 already implements TPC as an instruction pointer so we'll assume that division by 8 + // is done by CFC2 while working with the TPC register. + // (fixes R Racing Evolution and Street Fighter EX3) + + xSHR(eax, 3); + } + // FixMe: Should R-Reg have upper 9 bits 0? xMOV(ptr32[&cpuRegs.GPR.r[_Rt_].UL[0]], eax);