The rule that dropped the first join after the hello data changed came
from a race in pspautotests net/adhoc/pair/matching2, where the host
started looking for the REQUEST after it had come. A PSP reports the
join at once however soon after SetHelloOpt it arrives, also when the
host changes its hello data every second (net/adhoc/pair/matchingjoin).
Ridge Racer does that every 5 seconds, so its clients could never enter
a race.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The matching, matching2 and matching3 pair tests (recorded on two PSPs)
now pass on both sides:
- Event and input messages are queued in order. They were stacks, so a
burst came out reversed (PONG and PANG crossed), and two places spawned
their messages backwards to compensate.
- A peer stays busy until its DATA_ACK is delivered (DATA_BUSY), and
AbortSendData discards that DATA_ACK.
- A host's SelectTarget while its accept is in progress gives
REQUEST_IN_PROGRESS.
- A child that leaves, a cancelled request and a denied request (client
and P2P) go back to a known non-member peer: no TIMEOUT, hellos keep
coming, and it can ask again; a cancelled request gives
TARGET_NOT_READY. A client says BYE to a host it has left.
- After SetHelloOpt the next join from an unknown peer reaches the host
without a REQUEST.
- GetMembers lists the console itself first, also on a client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A stack size too small to be one (where a partition was passed) gives
STACKSIZE_TOO_SHORT, and a partition outside 1-6 then INVALID_ID
(pspautotests net/adhoc/matching). How small a stack still works isn't
known, so only sizes below 0x100 are refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fake matching thread was started through sceKernelStartThread,
which switched threads inside the call, and the game got the new
thread's ID back. On a PSP, Start returns 0 (pspautotests
net/adhoc/matching). Create and start it like adhocctl's thread instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A child the host accepts right after another one learns about that one
twice: from the sibling list in its accept packet, and from the BIRTH
packet the host sends about it. It added it both times, so with three or
four players the last one to join had an extra member (pspautotests
net/adhoc/group/matching, not recorded on PSPs yet).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Several comments called PSP behaviour a workaround for one game: PDP
send and receive returning 0 and writing the length back, Scan while
connected reporting ALREADY_CONNECTED, PtpOpen connecting by itself,
connecting a connected socket returning 0, PtpAccept's optional peer
address and port, the malloc stat's pool size, and matching dropping
hellos from a host mid-join. The pspautotests recorded on PSPs show the
PSP does all of these, so say that, and keep the games as examples.
Also bumps pspautotests for the new adhocctl Disconnect events and the
pair/matching3 test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CancelTarget on a MAC that isn't a peer, and Start on a context ID that
doesn't exist, faked success for every game. On the PSP they fail with
UNKNOWN_TARGET and INVALID_ID (pspautotests net/adhoc/matching). Only
the games they were added for keep them now:
MatchingCancelUnknownTargetSucceeds for Soulcalibur: Broken Destiny, and
MatchingStartUnknownIdSucceeds for GTA: Vice City Stories.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the net/adhoc/matching and limits pspautotests, recorded on PSPs:
- Create: a bad mode gives INVALID_MODE, rxbuflen has to be at least
6 * maxnum + 4, host and P2P contexts need nonzero hello and keep-alive
intervals, init_count can't be negative and rexmt_int can't be 0. At
most 15 contexts exist at once.
- Start on a running context gives IS_RUNNING. Start2 takes each
thread's partition after its stack size, not before.
- Stop and Delete on unknown IDs give INVALID_ID, and Delete on a running
context IS_RUNNING (internal cleanup still stops and deletes).
- GetHelloOpt fails before Start, copies at most the game's buffer and
leaves the length alone. It used to write the whole hello past the
buffer.
CancelTarget on an unknown peer still succeeds, as Soul Calibur needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HLEFunc and ReplaceFunc now take the MIPSState they run on. The dispatch
paths (both interpreters and every JIT backend) pass in the state they
belong to, and CallSyscall*, hleFinishSyscall and the HLE mips call
machinery use it instead of currentMIPS.
The wrappers in FunctionWrappers.h take the function as `auto` and call it
through HLECall, which also accepts an implementation with an extra leading
MIPSState * parameter and passes the state to it. Any other signature still
fails to compile, as before. PARAM/RETURN now refer to a local `mips`.
The HLE functions (and replacement functions) that used currentMIPS now take
the state that way. Callers outside a syscall's own frame - CoreTiming
events, the adhoc matching thread, dialogs, PPGe, the debugger UIs - pass
currentMIPS or &mipsr4k explicitly. The kernel's thread internals still use
currentMIPS.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
NetAdhocMatching_Delete unlinked the context before calling Stop, which then
couldn't find it, so the input and event threads were never joined and
deleting the context destroyed joinable std::threads (std::terminate). Seen
on Android when shutting down a game with matching running.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Loading a savestate kept the current session's netAdhocInited,
netAdhocctlInited and matching-inited flags rather than the saved ones.
Loaded into a fresh boot, a state saved with adhocctl initialized (but not
connected) then failed every adhocctl call with NOT_INITIALIZED. MGS Peace
Walker shuts ad hoc down before a cutscene and retried
sceNetAdhocctlDisconnect forever, leaving the screen black.
Take the flags from the state, and bring the host side in line: start the
friendFinder and log in again when the state has adhocctl up and we don't,
stop it in the opposite case. Also save the product code, which that login
sends.
Also add a game.state.save debugger command, used to reproduce this.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Matching packet handlers added untrusted payload lengths to fixed header
sizes before comparing them with the datagram length. Large values could
overflow and permit out-of-bounds reads. Compare against the remaining
packet size instead, and reject negative ACCEPT sibling counts.
With WLAN on and no server reachable, sceNetAdhocctlInit keeps its thread
waiting for the login. The load dropped the request, and the wait ended in
BUSY, which Init can't retry. Splinter Cell then ran its failure path with
a deleted event flag. Also stop freeing matching event buffers into the
restored allocator, and take the event lock when clearing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>