Repository navigation
Conversation
The R5900 ignores the low four bits of the effective address for quadword loads and stores. The translator passed the raw address through, so code that relies on the implicit alignment read and wrote the wrong bytes. The common case is an unaligned memcpy: libc-style copies load aligned quadwords with LQ from an unaligned source pointer and splice them with QFSRV. In FIFA Street 2 (SLES-53797) the memcpy at 0x3cbdd8 copied a config struct from an 8-byte-aligned source this way, zeroed its size field, and left a later allocator callback NULL. Mask the address in both the expression path and the constant-address (memory hint) path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ISO9660 names are case-insensitive and games spell them freely; FIFA Street 2, for example, opens lowercase names that the disc stores in uppercase. The sceCd* layer already falls back to a case-insensitive lookup, but fio/VFS opens resolved cdrom0: paths verbatim, so they failed on case-sensitive host filesystems such as Linux. When the exact host path does not exist, resolve cdrom0: paths one component at a time, ignoring ASCII case. Other devices (host0:, mc0:) keep their exact-match behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IoPaths::cdImage exists and readCdSectors falls back to it for LBNs that are not backed by a sceCdSearchFile entry, but nothing on desktop ever set it. Games that parse ISO9660 themselves (EA's ASYNCFILE, used by FIFA Street 2, reads sector 16 and then raw file extents) could therefore never read anything. Set cdImage from the PS2X_CD_IMAGE environment variable when the ELF's I/O paths are configured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The VBlank period was fixed at 16.667 ms, so PAL games ran their VSync-paced logic 20% fast. SetGsCrt now picks the rate from the video mode argument (3 = PAL, 50 Hz; anything else keeps the previous 60 Hz), effective from the next scheduled VBlank. Also correct the mode comment: SetGsCrt uses 2 for NTSC and 3 for PAL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceSifLoadModuleBuffer takes an IOP address: the EE first copies the IRX into IOP memory (sceSifAllocIopHeap + sceSifSetDma) and then asks the IOP to load it from there. The emulator read the image from EE RAM at that address instead, found no ELF header, and failed the load silently. FIFA Street 2 loads SIO2MAN, MTAPMAN, PADMAN, LIBSD, SNDDRV, MCMAN and MCSERV this way from DATA/IOP.BIG, so none of them started and sceMtapInit spun forever trying to bind SID 0x80000900. Read the image from IOP RAM first and fall back to EE RAM, so callers that still hand over an EE address keep working. The ELF reader is shared between both sources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BLEZ, BGTZ, BLTZ and BGEZ (and their likely and linking forms) test bit 63 of the 64-bit GPR. The generated conditions only looked at the low 32 bits, so a value such as 0x0000000080000000 (from DSUBU, LD or a 64-bit shift) branched as if it were negative. In FIFA Street 2 this broke a comparison in EA's file system and its movies failed to open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SQRT.S fd, ft takes its source in the ft field and encodes fs as 0. The translator used fs, so every SQRT.S returned sqrt(f0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The EE's CVT.W.S always rounds toward zero, whatever the FCR31 rounding mode, and saturates to INT32_MAX/INT32_MIN from 2^31 up. FPU_CVT_W_S used nearbyintf (round to nearest). Games that compute log2 with (int)(log(w) / log(2)) got 7 instead of 6 for a width of 112, and FIFA Street 2 sized its text textures twice too wide, cutting its menu text in half. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Count increments every EE cycle, but MFC0 read a field nothing updated, so code that waits or measures with Count saw no time pass. Count is now derived from the scheduler's EE cycle counter; MTC0 rebases it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resume points are registered after the normal functions but before stubs and library functions. When a resume point fell on the first instruction of a stub (a final branch whose delay slot is the next function's first instruction), the table sent calls to that stub into the middle of the previous function. Resume points that are another function's start are now skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a checkpoint or jump unwinds to the scheduler, every enclosing dispatchGuestBranch call has to return as well. A call whose callee came back with ctx->pc equal to the callee's entry was instead treated as "the callee returned without setting pc" and continued at its fallthrough. That happens with recursion when the inner call unwinds at a loop on the function's first instruction: the outer frame then ran on with the inner frame's registers (FIFA Street 2 crashed with a jalr to 0). An unwind flag, set on every unwinding path and cleared by the scheduler before each dispatch, now keeps all frames returning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The second byte of the pad data is the terminal ID and the payload length in halfwords. In pressure mode a DualShock 2 sends 9 halfwords, so it reports 0x79, not the analog 0x73. EA's pad library checks this after entering pressure mode and restarts its setup forever while it still reads 0x73. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With auto-DMA (ADMAS) the SPU2 takes PCM data only as fast as it plays it, 48 kHz stereo 16-bit or 192 IOP cycles per byte. The IOP DMA completed such transfers at bus speed, so software mixers that stream through sceSdBlockTrans (EA's snddrv) refilled back-to-back and kept the IOP busy 100% of the time. Adds ps2_iop_memory_tests for the DMA timing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A VIFcode with the I bit sets STAT.INT, raises the INTC VIF1 interrupt and stops VIF1 once the command is done, until the CPU writes VIF1_FBRST.STC (unless ERR.MII masks it). VIF1 only set STAT.INT and went on. EA's renderer syncs PATH3 texture uploads with VIF1 this way: its handlers restart GIF and VIF1 when both have stopped, and without the stall and interrupt it waited forever on the first loading screen. The remaining DMA data waits until FBRST.STC (a further stall inside it only moves an offset), the VIF1 channel completes after that data has run, and INTC causes queued by PS2Memory reach the scheduler's INTC dispatch, also from the sceDmaSend path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PATH3 has the lowest GIF priority. Games that sync GIF and VIF1 with a VIF1 interrupt restart both from the handler, GIF first, and rely on the PATH2 draws behind the stall winning the arbitration: the PATH3 uploads are for the next batch and may reuse VRAM those draws still read. The runtime ran the whole GIF chain at the CHCR write, so in FIFA Street 2 a UI texture overwrote a flag's shading data before the flag was drawn. A GIF chain started while VIF1 is stalled with data left now waits for the next I/O register write. If that write is VIF1_FBRST.STC, the resumed VIF1 data goes first. Any other write, a read of GIF_STAT, GIF_CHCR or D_STAT, or VIF1 FLUSHA runs the chain right away, so code that waits for the GIF transfer cannot hang. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The libgraph sceGsResetGraph ends with SetGsCrt(interlace, omode, ffmode), so a PAL game that only calls sceGsResetGraph also runs at 50 Hz. The HLE version did not select the VBlank rate; it now does, like the SetGsCrt syscall. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some games build or load MIPS code in RAM at run time, which a static recompilation cannot see. FIFA Street 2's sound engine (EA AEMS) ships code modules inside its .ABK sound banks, links them into the heap and calls them every frame. Such calls stopped the runtime with a missing function. User RAM (1 MB and up) outside the recompiled code regions now runs in an EE interpreter that follows the recompiler's semantics. It hands control back as soon as it reaches recompiled code or leaves that RAM, calls recompiled functions through dispatchGuestBranch, and takes a scheduler checkpoint every 32 instructions. hasFunction() keeps meaning "has a recompiled function" (HLE code probes optional helpers with it); the new canRunGuestCode() also accepts interpretable addresses and gates dispatch. PS2X_NO_INTERPRETER=1 turns the interpreter off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
Independent fixes found while bringing up FIFA Street 2 (SLES-53797, PAL) on Linux, from boot to in-game matches. Each commit carries its own test, so they can be reviewed or cherry-picked one by one. Sections 6 to 17 were added after the first review round.
1.
fix(recomp): align LQ/SQ/LQC2/SQC2 addresses to 16 bytesThe R5900 ignores the low four bits of the effective address for quadword loads/stores, but the translator passed the raw address through. Unaligned
memcpyroutines depend on that implicit alignment: theyLQfrom an unaligned source pointer and splice the quadwords withQFSRV. In FIFA Street 2 the memcpy at0x3cbdd8copied a config struct from an 8-byte-aligned source, zeroed its size field, and left a later allocator callback NULL (JALRto0x0during boot).The mask is applied in both the expression path and the constant-address (memory hint) path. Rebuilding with it changed 16k generated functions in this game.
2.
fix(runtime): matchcdrom0:paths case-insensitivelyISO9660 names are case-insensitive and games spell them freely (this game opens
anims.big, the disc hasANIMS.BIG). ThesceCd*layer already falls back to a case-insensitive lookup, but fio/VFS opens resolvedcdrom0:paths verbatim, so they failed on case-sensitive hosts. When the exact path is missing,cdrom0:paths are now matched one component at a time, ignoring ASCII case.host0:andmc0:keep exact matching.3.
feat(runtime): take the CD image path fromPS2X_CD_IMAGEIoPaths::cdImageexists andreadCdSectorsfalls back to it, but nothing on desktop sets it. Games with their own ISO9660 reader (EA's ASYNCFILE reads sector 16, then raw file extents) could never read anything.configureIoPathsFromElfnow setscdImagefrom thePS2X_CD_IMAGEenvironment variable.4.
fix(kernel): use 50 Hz VBlanks whenSetGsCrtselects PALThe VBlank period was fixed at 16.667 ms, so PAL games ran their VSync-paced logic 20% fast.
SetGsCrtnow selects 20 ms for mode 3 (PAL), effective from the next scheduled VBlank. Every other mode keeps exactly the previous 16.667 ms, so NTSC titles are unaffected. A smallEeScheduler::vblankPeriod()getter was added for the test.5.
fix(iop): loadsceSifLoadModuleBufferimages from IOP RAMsceSifLoadModuleBuffertakes an IOP address. The EE first copies the IRX into IOP memory (sceSifAllocIopHeap+sceSifSetDma), then asks the IOP to load it from there. The emulator read the image from EE RAM at that address instead, found no ELF header, and failed silently. FIFA Street 2 loads SIO2MAN, MTAPMAN, PADMAN, LIBSD, SNDDRV, MCMAN and MCSERV this way fromDATA/IOP.BIG, so none of them started andsceMtapInitspun forever binding SID0x80000900. The loader now reads IOP RAM first and falls back to EE RAM, so callers that pass an EE address keep working.6.
fix(recomp): compare sign-testing branches on the full 64-bit registerBLEZ,BGTZ,BLTZandBGEZ(and their likely and linking forms) test bit 63, but the generated conditions usedGPR_S32. A value like0x0000000080000000(fromDSUBU,LDor a 64-bit shift) branched as negative. In FIFA Street 2 this broke the comparator of EA's file-system hash table and its movies failed to open.7.
fix(recomp): read theSQRT.Soperand fromftSQRT.S fd, ftencodes its source inft(fsis 0); the translator usedfs, so everySQRT.Sreturnedsqrt(f0).8.
fix(runtime): truncateCVT.W.Stoward zero and saturateThe EE's
CVT.W.Salways rounds toward zero and saturates from 2^31 up;FPU_CVT_W_Susednearbyintf.(int)(log(112) / log(2))came out as 7 instead of 6, so FIFA Street 2 sized its text textures twice too wide and its menu text was cut in half.9.
fix(runtime): advance COP0Countwith the EE cycle counterMFC0 Countread a field nothing updated.Countis now derived from the scheduler's EE cycle counter;MTC0rebases it. AddsEeScheduler::eeCycle().10.
fix(recomp): keep resume entries from taking other functions' addressesResume points are registered after the normal functions but before stubs and library functions. A resume point on a stub's first instruction (a final branch whose delay slot is the next function's entry) sent calls to that stub into the middle of the previous function. Such resume points are now skipped.
11.
fix(runtime): keep unwinding nested calls after a scheduler checkpointWhen a checkpoint or jump unwinds to the scheduler, a
dispatchGuestBranchcall whose callee came back withctx->pcequal to the callee's entry took that for "returned without setting pc" and continued at its fallthrough. With recursion and an inner loop on the function's first instruction, the outer frame ran on with the inner frame's registers (FIFA Street 2 crashed with ajalrto 0). An unwind flag, set on every unwinding path and cleared by the scheduler before each dispatch, keeps all frames returning.12.
fix(pad): report the DualShock 2 pressure mode ID0x79In pressure mode the pad data's second byte is
0x79(9 halfwords), not0x73. EA's pad library re-runs its setup forever while it still sees0x73.13.
fix(iop): complete SPU2 auto-DMA transfers at playback speedWith
ADMASset the SPU2 takes PCM only as fast as it plays it (192 IOP cycles per byte at 48 kHz stereo 16-bit). Completing those transfers at bus speed madesceSdBlockTransmixers such as EA'ssnddrvrefill back-to-back and kept the IOP 100% busy. Adds aps2_iop_memory_testsexecutable.14.
fix(vif): stall VIF1 after a VIFcode with the interrupt bitAn I-bit VIFcode raises the INTC VIF1 interrupt and stops VIF1 until
VIF1_FBRST.STC(unlessERR.MIImasks it); VIF1 only setSTAT.INTand went on. EA's renderer syncs PATH3 uploads with VIF1 this way and waited forever on its first loading screen. The remaining DMA data waits forSTC, the VIF1 channel completes after it, and INTC causes queued byPS2Memoryreach the scheduler's INTC dispatch (also from thesceDmaSendHLE path, through the newPS2Runtime::dispatchCompletedDmacCause).15.
fix(gif): let PATH2 data resumed after a VIF1 stall beat PATH3The interrupt handlers restart GIF before VIF1, and the game relies on the PATH2 draws behind the stall winning the arbitration: the PATH3 uploads are for the next batch and may reuse VRAM those draws still read. The runtime ran the whole GIF chain at the
CHCRwrite, so a UI texture overwrote a flag's shading data before the flag was drawn. A GIF chain started while VIF1 is stalled with data left now waits for the next I/O write. If that write isFBRST.STC, the resumed VIF1 data goes first. Any other write, a read ofGIF_STAT/GIF_CHCR/D_STAT, orFLUSHAruns it at once, so code waiting for the transfer cannot hang.16.
fix(kernel): set the VBlank rate insceGsResetGraphtooThe libgraph
sceGsResetGraphends withSetGsCrt, so it now selects the PAL/NTSC VBlank rate like the syscall in section 4.17.
feat(runtime): interpret EE code that has no recompiled functionFIFA Street 2's sound engine ships MIPS code modules inside its
.ABKbanks, links them into the heap and calls them every frame, which a static recompilation cannot see. User RAM (1 MB and up) outside the recompiled code regions now runs in an EE interpreter that follows the recompiler's semantics. It hands control back as soon as it reaches recompiled code or leaves that RAM, and takes a scheduler checkpoint every 32 instructions.hasFunction()keeps meaning "has a recompiled function" (HLE code probes optional helpers with it); the newcanRunGuestCode()gates dispatch.PS2X_NO_INTERPRETER=1turns it off.Related
This game also needs #354 (
sceCdCallback). That PR found the same callback dependency in the NTSC release, so it isn't duplicated here.Verification
-msse4.1as in CI. Configured with-DPS2X_ENABLE_FFMPEG=OFF -DPLATFORM=SDLbecause those development packages aren't installed locally.ps2x_tests: 459/459 pass (446 existing + 13 new; sections 9 and 12 extend existing tests). ps2xIOP tests: all five executables pass, including the newps2_iop_memory_tests.🤖 Generated with Claude Code