rand/srand stubs use the host std::rand: the guest seed is lost and RAND_MAX changes (Tekken 4 CPU opponents never vary)
Maintainers usually reply within 5 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 48/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- cpp
- Domain
- backend, operating-systems
Research direction
Start with ps2xRuntime/include/ps2_call_list.h and the rand/srand stubs in ps2xRuntime/src/lib/Kernel/Stubs/LibC.cpp; trace how resolveStubTarget selects them and how unmatched guest functions run. Check the guest reent state and EE newlib behavior described in the issue. Done when guest seeding, range, and sequence are preserved, including when only one symbol matches.
Written by the indexing model from the issue text.
Description
Summary
ps2_stubs::rand and ps2_stubs::srand (ps2xRuntime/src/lib/Kernel/Stubs/LibC.cpp) replace the game's libc rand/srand with the host's std::rand/std::srand:
void rand(...) { setReturnS32(ctx, std::rand() & 0x7FFF); }
void srand(...) { std::srand(getRegU32(ctx, 4)); setReturnS32(ctx, 0); }
This changes guest behaviour in three ways:
- The guest seed is lost when only one of the pair is matched by name. The stub is chosen per function name (
resolveStubTarget). In Tekken 4 (PAL, SCES-50878) the symbol match findsrand(0x2A3178) but notsrand(0x2A3168, left asFUN_002a3168). So the guestsrandruns as recompiled code and stores the seed in the libc reent struct. The stubbedrandthen reads the hoststd::randstate, which nothing ever seeds, and gives the same sequence on every boot. - Different range. The stub returns 0..0x7FFF, but the EE newlib
RAND_MAXthat games are compiled against is 0x7FFFFFFF. Code that scales byRAND_MAX(e.g.rand() / (float)RAND_MAX) gets values 65536 times too small, andrand() % ngets a different distribution. - Different sequence. Even when both functions are stubbed, the host generator isn't newlib's. Seeded sequences (demos, replays, AI) then differ from PCSX2 and real hardware. The host
std::randstate is also shared with anything else in the process that calls it.
Symptom
In Tekken 4 the CPU opponent was always the same character (Law in Arcade, Steve in Story mode), whatever the input timing. PCSX2 picks a different opponent each time. The CPU fighters' AI also draws from rand.
Workaround in our game override
We register an override on the game's rand address that runs the EE libc routine on the guest state:
// EE newlib rand: next = next * 1103515245 + 12345 (32-bit); return next & 0x7FFFFFFF.
// In this game the state is the 32-bit word at _impure_ptr + 88 (_impure_ptr at 0x3059D4).
void guestRand(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
ctx->pc = getRegU32(ctx, 31);
uint32_t reent = 0, next = 0;
std::memcpy(&reent, rdram + 0x003059D4u, 4);
uint8_t *state = getMemPtr(rdram, reent + 88u);
if (!state) { setReturnS32(ctx, 0); return; }
std::memcpy(&next, state, 4);
next = next * 1103515245u + 12345u;
std::memcpy(state, &next, 4);
setReturnS32(ctx, static_cast<int32_t>(next & 0x7FFFFFFFu));
}
With it, the opponents vary as they do in PCSX2.
Suggested fix
rand and srand are pure integer code on guest memory with no host dependency. The simplest fix is to stop stubbing them: drop X(rand) and X(srand) from ps2xRuntime/include/ps2_call_list.h and let the recompiled guest code run. That keeps the game's own state, seed, range and sequence, whichever of the two the symbol match finds.
If a stub is preferred, it should at least keep its own state (not the host std::rand) and use newlib's LCG and RAND_MAX of 0x7FFFFFFF. It would still lose the seed whenever only one of the pair is matched by name, though, so not stubbing looks safer.
Checked against main at e563698.
- 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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from ran-j/PS2Recomp
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ran-j/PS2Recomp#393 · 1 comment ·
Maintainers usually reply within 5 days
-
EE sceCdStatus stub always reports READ (6), hanging games that wait for PAUSEPossibly taken @ahmedgamalalzatary claimed this 12 days ago. Open
Difficulty 1/5 Under an hour Newbie friendliness 95/100
Maintainers usually reply within 5 days
-
VU0 macro VSQI / VSQD: address and source registers swapped in generated codePossibly taken @yanm1103 claimed this 20 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 5 days
-
Runtime: SampleTexture casts unbounded or NaN texture coordinates to int, which is undefined behaviourMay be free again A pull request for this issue was closed without being merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
ran-j/PS2Recomp#273 · 2 comments ·
Maintainers usually reply within 5 days
-
Build: /Qspectre- is MSVC-only and breaks every Release target under clang-clPossibly taken @ahmedgamalalzatary claimed this 12 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Maintainers usually reply within 5 days
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 3 days
-
Bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
SCIInstitute/SCIRun#2909 ·
Maintainers usually reply within 2 days
-
bug libcudf
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 2 days
-
Triage: Needed
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 2 days