fix(runtime): stop double-counting nloop in sceGifPkRefLoadImage A+D header GIFtag (#149)

sceGifPkRefLoadImage seeded its A+D header GIFtag with nloop=4 via
makeGiftagAplusD(4u), then closePacketGifTag computed the appended
register-qword count (4) from the write-cursor delta and ADDED it to the
seed, finalizing nloop=8 for a tag with only 4 A+D register qwords
(BITBLTBUF/TRXPOS/TRXREG/TRXDIR). A GIF parser consuming that tag eats the
following IMAGE GIFtag and its first payload qwords as bogus A+D data,
starving the CLUT/texture upload that the setup packet precedes (e.g. DQ8
font-glyph CLUTs staying all-zero).

Seed an open A+D tag (nloop=0, nreg=1, EOP clear) instead and let
closePacketGifTag add the true count, finalizing 0x1000000000000004 -- the
value the regression test pins. The seed is written via a named
makeGiftagAplusDOpen(nloop) helper in Support.h (mirrors makeGiftagAplusD but
leaves EOP/bit 15 clear for a chained tag whose nloop is finalized at close),
rather than a bare (1ULL << 60) literal, to keep the file's
GIFtag-construction convention consistent. makeGiftagAplusD(0u) can't be used
because it always sets EOP.

Adds a byte-level regression test that drives sceGifPkRefLoadImage (setup
only, qwcRemaining=0) against a scratch packet-builder state and asserts the
finalized header tag lo/hi, the four A+D register descriptors (0x50..0x53),
and their four register payloads (BITBLTBUF dbp/dbw/psm, TRXPOS, TRXREG
width/height, TRXDIR) -- pinning the full setup packet.

Full build clean; ps2x_tests 297/297.
This commit is contained in:
Shane Michael Mathews (Personal Account)
2026-07-10 13:20:59 -04:00
committed by GitHub
parent c4355c8cda
commit 81f2a7f5e1
3 changed files with 93 additions and 1 deletions
+4 -1
View File
@@ -504,7 +504,10 @@ namespace ps2_stubs
sizeof(words));
writePacketBuilderCurrent(rdram, runtime, stateAddr, packetAddr + 16u);
const uint64_t giftag[2] = {makeGiftagAplusD(4u), 0xEULL};
// Seed an open A+D tag (nloop=0, EOP clear): closePacketGifTag adds the true
// appended qword count, so a pre-set nloop would double-count. Open variant
// (not makeGiftagAplusD) because that always sets EOP on this chained tag.
const uint64_t giftag[2] = {makeGiftagAplusDOpen(0u), 0xEULL};
uint32_t currentAddr = packetAddr + 16u;
writeGuestBytes(rdram, runtime, currentAddr, reinterpret_cast<const uint8_t *>(giftag), sizeof(giftag));
writePacketBuilderCurrent(rdram, runtime, stateAddr, currentAddr + 16u);
@@ -1673,6 +1673,14 @@ namespace
(static_cast<uint64_t>(1u) << 60);
}
// Like makeGiftagAplusD but leaves EOP (bit 15) clear, for a chained/open
// A+D tag whose nloop is finalized later by closePacketGifTag.
static uint64_t makeGiftagAplusDOpen(uint32_t nloop)
{
return (static_cast<uint64_t>(nloop & 0x7FFFu) << 0) |
(static_cast<uint64_t>(1u) << 60);
}
static uint32_t readStackU32(uint8_t *rdram, R5900Context *ctx, uint32_t offset)
{
uint32_t sp = getRegU32(ctx, 29);