Packet-builder stubs double the DMA tag QWC when the game's own close function runs

Open
#374 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 5 days

@Lautreck is already working on this.

Since Oct 22, 2026.

  • #378 by @Lautreck — open

Assessment

Difficulty
4/5
Estimated time
1-2 days
Newbie friendliness
48/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
cpp
Domain
backend

Research direction

Start in ps2xRuntime/src/lib/Kernel/Stubs/GS.cpp and inspect writePacketBuilderCurrent(), refreshPacketBuilderPendingCount(), and the stubbed close/terminate functions; the issue names the test VIF1 packet builders keep chain qwc live before terminate. Reproduce the Tekken 4 sequence described in the issue and check how the CNT tag QWC changes when the game's close function runs. Done means the fix avoids doubling QWC while preserving the tested packet builder behavior.

Written by the indexing model from the issue text.

Description

Summary

The packet-builder stubs in ps2xRuntime/src/lib/Kernel/Stubs/GS.cpp keep the open DMA tag's QWC up to date as they write: every stub that advances pk->cur goes through writePacketBuilderCurrent(), which calls refreshPacketBuilderPendingCount() and rewrites the QWC field of the tag at pk[2].

That's deliberate (the test VIF1 packet builders keep chain qwc live before terminate covers it), but it only works when the tag is also closed by a stub. When a game's own copy of the library's close function is not recognised (so it runs as recompiled game code), that function does what the real library does and adds (cur - tag) / 16 - 1 to the tag's first word. The QWC then comes out doubled.

Where it shows up

Tekken 4 (SCES-50878). Its image-upload helper does:

  1. its own sceVif1PkCnt-style open (unrecognised, game code at 0x292970);
  2. sceGifPkOpenGifTag-style open (game code);
  3. four sceGifPkAddGsAD calls (stub);
  4. sceGifPkCloseGifTag (stub);
  5. its own close (unrecognised, game code at 0x292900).

By step 5 the stubs have already set the CNT tag's QWC to 5, and the game's close adds another 5. VIF1 then reads 10 qwords for a 5-qword packet, lands mid-stream on garbage that decodes as a REFE tag and ends the transfer early. Logged around each call:

open CNT after:   [tag]=10000000
close GIF before: [tag]=10000005        <- set by the AddGsAD stubs
close tag:        word0 10000005 -> 1000000a

Possible fixes

  • Don't touch the DMA tag from the GIF/VIF data stubs, and compute the QWC only in the stubbed close/terminate functions, as the real library does. This is closest to hardware, but needs checking against every stubbed close.
  • Or make the pending-count refresh opt-in, for games whose close functions are all stubbed.

I worked around it in a game override by replacing the game's close function with one that sets the QWC instead of adding it (closeDmaTag in https://github.com/Lautreck/tekken4-recomp/blob/master/runtime/tekken4_overrides.cpp).

Made by Pablo and assisted with Claude Code.

🤖 Generated with Claude Code

Dominant language
C++
Stars
3.3k
Forks
149
Avg merge
8h 46m
Merged PRs (30d)
1

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from ran-j/PS2Recomp

All issues in ran-j/PS2Recomp

Similar issues

More C++ issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.