Commit Graph
16477 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 acb2acf0b4 sceNetAdhocMatching: Run the input and event loops from CoreTiming instead of host threads
Each started context had two host threads, polling every 10 ms of wall
time: one sent queued packets, received, and ran hellos, keepalives and
timeouts, and the other handed events to the callbacks. A single
CoreTiming event now does both for every running context, every 1 ms of
emulated time, and Stop does what the threads did on their way out.

Matching no longer depends on host scheduling or wall-clock delays, so an
accept's ESTABLISHED comes 1-10 ms after SelectTarget at any emulation
speed, as on a PSP (pspautotests net/adhoc/pair/matchingaccept).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 00:44:36 +01:00
Henrik RydgårdandClaude Opus 5.5 752ecccf43 sceNetAdhoc: Don't treat a relay PTP listener's handle as a PDP session when polling
Polling an unconnected or listening PTP socket for SEND took the PDP
path in postoffice_can_send. For an opened connection that created a
PDP session and stored it as the socket's handle, so its sends and
receives failed afterwards (pspautotests net/adhoc/pair/ptppoll). A PTP
socket that isn't established now just isn't writable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 00:44:36 +01:00
Henrik RydgårdandClaude Opus 5.5 1437c15f52 sceNetApctl: Fail GetState before Init, let GetLocalEtherAddr work in infrastructure mode
- Fixes #22857

PES 2009 calls sceNetApctlGetState first and only calls sceNetApctlInit
when it fails with 0x80410a0d. We returned success, so Init was never
called, apctl's state never advanced and the Netconf dialog waited on
"Connecting to the access point" forever. A PSP returns 0x80410a0d before
Init and after Term (pspautotests net/adhoc/apctlinit).

After that the game polls sceNetGetLocalEtherAddr until it succeeds, which
we only allowed in ad hoc mode. Also allow it once apctl has connected. It
still fails with apctl initialized but not connected, as the test shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 23:50:28 +01:00
Henrik RydgårdandClaude Opus 5.5 969bc4a1be sceNetAdhoc: Drop PtpOpen's unused isClient
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 20:22:08 +01:00
Henrik RydgårdandClaude Opus 5.5 958d76ea77 sceNetAdhoc: Move the two transports into their own files
RelayTransport, with the aemu_postoffice helpers only it uses, goes to
AdhocTransportRelay.cpp, and P2PTransport to AdhocTransportP2P.cpp. The
helpers they share with sceNetAdhoc.cpp are declared in
AdhocTransport.h. Moved as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:09:35 +01:00
Henrik RydgårdandClaude Opus 5.5 b5db43ca9d sceNetAdhoc: Put PTP, polling and socket cleanup behind AdhocTransport
The relay and P2P halves of PtpOpen, PtpListen, PtpClose, PtpSend,
PtpRecv, PtpAccept and their blocking retries, GetPtpStat, PtpFlush,
PollSocket and deleting all sockets move into RelayTransport and
P2PTransport. Connecting stays shared, with the transports answering
the steps that differ (start, check on, confirm, still pending).

No change in behaviour, except that a relay poll asks the postoffice
whether a socket is readable once instead of twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:09:35 +01:00
Henrik RydgårdandClaude Opus 5.5 7202562695 sceNetAdhoc: Put PDP behind an AdhocTransport interface
The relay and P2P halves of PdpCreate, PdpDelete, PdpSend, PdpRecv, the
blocking PDP receive, GetPdpStat and game mode's readiness checks move into
RelayTransport and P2PTransport, which Transport() picks between by
serverHasRelay. No change in behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:09:35 +01:00
Henrik Rydgård 36b6db5458 Merge pull request #22851 from hrydgard/adhoc-9-fixes
Ad hoc 9/9: Fixes from playing games, and 8x faster ad hoc tests
2026-10-30 13:09:06 +01:00
Henrik RydgårdandClaude Opus 5.5 c1058fd589 sceNetAdhoc: Disconnect is BUSY until a Create or Connect has connected
As on a PSP (pspautotests net/adhoc/abandon): the Disconnect fails and
the join goes on to CONNECT. Before, it was accepted, and the join's late
answer from the server could still connect us afterwards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:04:33 +01:00
Henrik RydgårdandClaude Opus 5.5 f6f9a00eb7 UI: Move file I/O off the render thread in a few screens
On Android the render thread runs the UI, and surfaceDestroyed waits for
it, so slow storage (SAF especially) there stutters and can become an ANR.

- Store: only check whether the game is installed when an install or
  uninstall finishes, not every frame.
- Savedata: read the modification time with SIZE on the GameInfo worker.
  The popup asked for it every frame, and each button on first draw.
- FirmwareScreen: scan the NAND, and erase it, on a worker thread. It
  rescanned on every RecreateViews, which is after every popup.
- GameScreen: look up the installed update, plugins and what the firmware
  can play on a worker thread. They ran on every RecreateViews, which
  happens as each piece of game info arrives.
- LaunchFile: wait only for the file type, and for PARAM.SFO just in the
  cases that use it.

Also add --start-screen=firmware.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-30 12:01:36 +01:00
Henrik RydgårdandClaude Opus 5.5 a0669ffbe4 sceNetAdhoc: Don't let an abandoned group join put us in the group
When the game gives up on a join (game mode timed out, or it
disconnected), a join still queued isn't sent anymore, and the rest of
the server's answer to one already sent is dropped: its players, and the
BSSID that came last and connected us. Players joining a group we've
just left (before the server knew) are dropped too, instead of staying
in our peer list.

Replaces the previous fix, which only dropped a late BSSID: on Windows
the join itself was still queued when game mode timed out in
net/adhoc/gamemode, and went out after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:53:44 +01:00
Henrik Rydgård 56c84bb5c0 Merge pull request #22852 from hrydgard/ci-armhf-qemu
CI: Test ARM32 under qemu, and fix the MD5/SHA-1 bus error it found
2026-10-30 11:25:15 +01:00
Henrik RydgårdandClaude Opus 5.5 9d8ef5e8f5 sceNetAdhoc: Ignore the server's answer to a timed-out game mode join
When entering game mode timed out before the server's group BSSID came
back, the late BSSID was taken for a normal group's, and the state went
to CONNECTED: every later CreateEnterGameMode then got ALREADY_CONNECTED.
At the ad hoc tests' 8x speed the 1 s timeouts in net/adhoc/gamemode are
125 ms of wall time, short enough for that on Windows; at 50x it happens
on macOS too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:21:57 +01:00
Henrik RydgårdandClaude Opus 5.5 c52f8c9657 Headless: --speed and --port-offset, and run the ad hoc tests at 8x
--speed=PERCENT runs a --realtime headless at an alternate speed (speed
control while connected is allowed for it), and pacing follows it. The
ad hoc tests now run at 800%, which takes their part of test.py -g from
about 71 s to 12 s; only local network round trips stay real time.

test.py also gives each ad hoc test run its own --port-offset: a
connection the previous run made with the same ports is still in TCP
TIME_WAIT, and connecting again failed with EADDRINUSE once runs came
close together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:35 +01:00
Henrik RydgårdandClaude Opus 5.5 e7548e1bea sceNetAdhoc: Measure socket and game mode timeouts in emulated time
The timeouts of blocking socket calls and of entering game mode are the
PSP's, but they were measured on the wall clock, so they came out
shorter in PSP time whenever emulation ran fast or slow. The wait for
the ad hoc server, which is a host-side round trip, stays on the wall
clock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:35 +01:00
Henrik RydgårdandClaude Opus 5.5 eb254d86e8 sceNetAdhoc: Don't take a refused PTP connect for an established one
On macOS, calling connect() again on a socket whose non-blocking
connect was refused returns EISCONN; only SO_ERROR and a failing
getpeername tell it apart from a connection. PtpConnect took EISCONN at
its word, so in P2P mode a connect to a port nobody listened on
"succeeded", and the game sent into nothing. Check getpeername first
and use SO_ERROR when it fails.

PollSocket also reports a refused connect as DISCONNECT now, as a PSP
does (net/adhoc/poll), instead of only ever noticing success.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:35 +01:00
Henrik RydgårdandClaude Opus 5.5 c67315779b sceNetAdhocctlInit: Log loudly when called twice
A PSP is left uninitialized by it (net/adhoc/adhocctl); not emulated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:35 +01:00
Henrik RydgårdandClaude Opus 5.5 603a63bc09 sceNetAdhocMatching: Report the pool as empty right after Init
A PSP reports the pool size less 0x20 and a maximum of 0 until anything
is allocated (net/adhoc/matching, recorded on a PSP). With a context the
faked half-pool maximum stays, since that isn't measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:35 +01:00
Henrik RydgårdandClaude Opus 5.5 34a6ace9ae sceNetAdhoc: Don't write the local MAC into the game's address argument
PdpCreate, PtpOpen and PtpListen use the local MAC whatever the game
passes (a stale MAC from an old savestate would otherwise break them),
but they did it by overwriting the game's copy. A game that passes
someone else's MAC from a table then has its own MAC in that table.
pspautotests net/adhoc/pdp passes its "other" MAC to PdpCreate and later
sends to it; every one of those sends came back to itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:35 +01:00
Henrik RydgårdandClaude Opus 5.5 6b14884e26 sceNetAdhoc: sceNetAdhocTerm leaves adhocctl and the group alone
A PSP stays in its group through sceNetAdhocTerm and sceNetAdhocInit,
and matching keeps working; only the sockets go, and calls on them
return NOT_INITIALIZED until the next init (net/adhoc/term, recorded on
a PSP). We also terminated adhocctl, so a game that restarts the adhoc
library inside a group found itself disconnected. Every other caller
already terminates adhocctl itself first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:35 +01:00
Henrik RydgårdandClaude Opus 5.5 a8a63fa11d sceNetAdhoc: Start socket IDs over at 1 after sceNetAdhocTerm
A PSP hands out socket IDs from 1 again after sceNetAdhocTerm and
sceNetAdhocInit (net/adhoc/socketids, recorded on a PSP). We kept
counting up. Pangya: Fantasy Golf tears down the network between its
room and the round, and then keeps using socket 1 without looking at
what PdpCreate returned, so every send and receive failed with
INVALID_SOCKET_ID and each player saw the other as "Off-line".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:35 +01:00
Henrik RydgårdandClaude Opus 5.5 d0d6f0f61b sceNetAdhocMatching: A child lists its parent first after a keepalive
- Fixes #22331

A child's GetMembers starts with itself, then switches to the parent
first one keepalive interval after it was accepted
(net/adhoc/pair/matchingorder, recorded on two PSPs). We kept it self
first. Bomberman's client checks that the first member is the host
before it marks itself ready, so its lobby stayed empty and the host's
start failed with "failed to connect to the game".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:14 +01:00
Henrik RydgårdandClaude Opus 5.5 0d686c1a95 sceNetAdhocGameMode: Send the master every period, changed or not
A PSP sends its master every 12 ms after the initial sync, and a peer's
replica reports each one as an update. PPSSPP only sent a master that
had changed, so a replica polled until updated waited for the next
change. Also take every packet waiting each period, not one: every
member sends each period. Measured in pspautotests
net/adhoc/pair/gamemodesync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:14 +01:00
Henrik RydgårdandClaude Opus 5.5 478d10ff19 sceNetAdhoc: A PTP connection only gives up with data outstanding
When the peer has left, a PSP drops the connection once data sent to it
has gone unacknowledged for (rexmt_cnt + 1) * rexmt_int. A connection
that only receives never fails. Count from the first data the peer
couldn't acknowledge: sent after it left, or within 10 ms before
(pspautotests net/adhoc/pair/vanishtiming).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:14 +01:00
Henrik RydgårdandClaude Opus 5.5 8bb1f8462f sceNetAdhoc: A PTP connection is established as soon as it's up
GetPtpStat only looked at a connection in progress 33 ms after the last
connect attempt, and PollSocket reported CONNECT 1 ms after it whether
the connection was up or not. On a PSP both show it the moment it's up,
about 5 ms after the connect (pspautotests net/adhoc/pair/ptpconnect).
The old timings were tuned for GvG Next Plus, Bleach and Fate; Fate's
network match still works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:14 +01:00
Henrik RydgårdandClaude Opus 5.5 3c9c01048a sceNetAdhocctl: Time GAMEMODE and leaving game mode as a PSP does
GAMEMODE comes 4.38 s after CreateEnterGameMode, or 1.35 s after
JoinEnterGameMode or the last member's arrival, whichever is later,
instead of a fixed 2 s after the last member (plus 60 ms for a member,
a workaround for Pocket Pool). DISCONNECT comes 650 ms after
ExitGameMode. Measured in pspautotests net/adhoc/pair/gamemodetiming.

A member that joins early still gets GAMEMODE before the host: it
doesn't know when the host entered game mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:14 +01:00
Henrik RydgårdandClaude Opus 5.5 882eee4c13 sceNetAdhocMatching: A denied P2P request goes back to a known peer
CancelTarget on an incoming P2P request marked the peer timed out. It
only escaped a TIMEOUT if one of its hellos came in before the next
timeout check, which it no longer does now that timeouts are checked
all the time. On a PSP it's a peer known from its hellos again, with no
TIMEOUT (pspautotests net/adhoc/pair/matching3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:14 +01:00
Henrik RydgårdandClaude Opus 5.5 08c76b8b25 sceNetAdhocGameMode: Create master and replicas as a PSP does
pspautotests net/adhoc/pair/gamemode:
- CreateMaster with size 0 fails with INVALID_DATALEN.
- CreateReplica for a MAC that isn't in the game mode group fails with
  0x80410D13. PPSSPP made one, and then blocked the thread forever
  waiting to sync with it.
- CreateReplica for the same MAC again gives ALREADY_CREATED. The check
  compared the pointer instead of the MAC, so it never matched.
- A replica's ID is the member's place in the member list, host first:
  the client's replica of the host is 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:14 +01:00
Henrik RydgårdandClaude Opus 5.5 60a49ccfd2 Ad hoc: Use the PSP's measured times instead of guessed ones
Where pspautotests net/adhoc/timing or net/adhoc/pair/timing measured
a value, use it:

- Call costs: PdpSend 21 us (10 broadcast), PtpSend 122, PtpFlush 196,
  GetPdpStat 1, GetPtpStat 2, PollSocket 18, GetScanInfo 2, GetPeerList
  and MatchingGetMembers 4 (no longer a 100 us reschedule),
  MatchingStart 416 instead of 30 ms, sceNetAdhocctlTerm 154,
  sceNetAdhocTerm 10 and sceNetTerm 209 instead of 10 ms each,
  GameModeUpdateMaster 80, UpdateReplica 9.
- Game mode data goes out every 12 ms, not every 0.5 ms.
- DISCONNECT comes 640 ms after ExitGameMode, not 105 ms.
- CONNECT after a Join no longer waits for a peer: a PSP delivers it
  before the host shows up in the peer list.

Several of the old values were tuned for particular games (BattleZone's
frame rate, Fate Unlimited Codes and Bleach through PollSocket), and
may have been making up for other differences that are fixed now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:15:13 +01:00
Henrik RydgårdandClaude Opus 5.5 214b14fe63 Add --nickname, and give each adhoc_session.py instance its own
Games that show the system nickname in their lobbies can then tell the
instances apart. Also: press --times N --every S for menus that need
several presses, and start clears the last session's screenshots, which
could otherwise pass for this one's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:08:10 +01:00
Henrik RydgårdandClaude Opus 5.5 96e31f8c47 WebSocket debugger: Enabling a log channel turns logging on
Headless turns logging as a whole off without --log, so a channel
enabled with log.channel.set still logged nothing, and a client
watching the log events saw none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:07:57 +01:00
Henrik RydgårdandClaude Opus 5.5 c3defa2e0f sceMd5: Keep the MD5/SHA-1 contexts 4-aligned, fixing a bus error on ARM32
The u64 total length made both context structs 8-aligned, while the
game's context only has to be 4-aligned (hash/hash puts it in a char
array). On ARM32, GCC then copies buf with vld1.64 [:64], which faults
on such an address - on hardware too, not just under qemu as the
known-failures entry assumed. Store the length as two words; the layout
is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:28:42 +01:00
Henrik RydgårdandClaude Opus 5.5 f449129dec sceNetAdhoc: Time a vanished peer's timeouts as a PSP does
A PSP checks a PTP connection's oldest unacknowledged data every
rexmt_int, counting from when the connection was established, and drops
the connection at the first check where that data is older than
(rexmt_cnt + 1) * rexmt_int. A matching peer times out keepalive_int *
init_count after its last keepalive, checked all the time instead of on
the console's own keepalive tick, which could add up to an interval.
Both measured with several settings in pspautotests
net/adhoc/pair/vanishtiming; DATA_TIMEOUT already matched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:07 +01:00
Henrik RydgårdandClaude Opus 5.5 b58368405b proAdhoc: Keep pinging the server while emulation is paused
The adhocctl poll only runs while emulation does, so with the pause menu
open, or stopped in a debugger, for 15 s the server dropped the client,
and a game in a lobby lost its peers until it rejoined. A small thread
pings when the poll hasn't, as the friendFinder thread used to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:07 +01:00
Henrik RydgårdandClaude Opus 5.5 ee86058ec7 sceNetAdhocctl: Leave the console out of its own peer lookups; give groups their own BSSID
From the basic and lifecycle pair tests, recorded on two PSPs:

- GetNameByAddr for the console's own MAC gives NO_ENTRY, and
  GetAddrByName doesn't find the console itself.
- GetPeerInfo with a size too small for the entry gives INVALID_BUFLEN.
- A PTP connection accepted over the relay reports the peer's port as
  the game sees it, not the relay's offset port.
- The built-in server makes up a locally administered BSSID for each
  group, which stays when the creator leaves. It was the MAC of the
  oldest member.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:07 +01:00
Henrik RydgårdandClaude Opus 5.5 4197f2326a sceNetAdhoc: A console that leaves the network goes quiet, as on a PSP
The vanish pair test (recorded on two PSPs) now passes on both sides,
over the relay:

- From the Disconnect call, a console's PDP and PTP sends succeed and go
  nowhere, and nothing reaches it.
- A PTP connection to a peer that has left the group fails with
  DISCONNECTED after rexmt_int * rexmt_cnt. Relay sockets now get their
  retry settings (and are zeroed), and an accepted one counts as
  connected.
- Matching data to a peer that has left gets DATA_TIMEOUT after the
  retransmissions instead of a DATA_ACK.
- The matching timeout for a silent peer is keepalive_int * init_count,
  plus MATCHING_TIMEOUT_EXTRA_US (0) to tune for internet latency. It
  was (keepalive + rexmt) * count + 500ms, later than a PSP's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:07 +01:00
Henrik RydgårdandClaude Opus 5.5 5021e3ccc1 sceNetAdhocctlInit: Don't wait for the login to the server
On a PSP, Init returns at once and a Create, Connect or Scan right after
it works (pspautotests net/adhoc/ctlinit). The login now runs on in the
background, and those requests wait for it, as they already did when it
was lost. The first login no longer resets the busy flag, which would
have cleared it for a request already waiting on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:07 +01:00
Henrik RydgårdandClaude Opus 5.5 fa51700d58 sceNetAdhocctl: Fail entering game mode the way a PSP does
On a PSP (pspautotests net/adhoc/gamemode):
- a JoinEnterGameMode whose host never shows up gets ERROR with TIMEOUT
  when its timeout runs out, and is out of the group afterwards. The
  joiner's GAMEMODE, queued as soon as the server answered and held
  until the host appeared, came after 5 s anyway;
- a CreateEnterGameMode whose first member isn't this console returns
  0 and reports INVALID_ADDR;
- in a normal group, CreateEnterGameMode reports ALREADY_CONNECTED and
  stays in the group, and ExitGameMode returns INVALID_IBSS;
- ExitGameMode outside game mode returns 0 with no event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:07 +01:00
Henrik RydgårdandClaude Opus 5.5 a276f00dd0 proAdhoc: Run the adhocctl client from a CoreTiming event
The friendFinder thread is replaced by friendFinderPoll(), which a
CoreTiming event calls every millisecond of emulated time on the CPU
thread. It handles everything the server has sent since the last poll,
instead of one event every 10 ms, and pings it at the same real-time
interval. Resolving the server and logging in, which can take seconds,
run on a thread of their own; the poll starts it and takes over the
session when it's done.

Everything else about the client is unchanged, including sceNetAdhocctlInit
waiting for the login. The savestate section goes to version 11 for the
poll event; older states get it scheduled on load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:07 +01:00
Henrik RydgårdandClaude Opus 5.5 1e3e9c7251 sceNetAdhocctl: Send Create, Connect and Scan without the 10 ms wait
On a PSP they return within about 30 µs (pspautotests net/adhoc/timing).
They waited 10 ms before trying to send the request; now the first try is
right away, and the call returns once it's sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:07 +01:00
Henrik RydgårdandClaude Opus 5.5 a2897c5b65 sceNetAdhocctl: Deliver CONNECT, SCAN and DISCONNECT at the PSP's times
A PSP delivers CONNECT 1.1 s after a Create, 0.53 s after a Join and
3.5-4 s after a Connect (which looks for the group first), SCAN 1.0 s
after a Scan and DISCONNECT 105 ms after a Disconnect, and the state
changes along with the event (pspautotests net/adhoc/timing and
net/adhoc/pair/timing). Deliver them then, counting from the request in
emulated time, or later if the server's answer takes longer. This
replaces the delays tuned for Dissidia 012, GTA: VCS and Tekken 5, and
Disconnect no longer blocks for 16.7 ms (Tekken 6): it returns at once,
as on a PSP.

Since the state now changes later, a Disconnect can come while a Create
is still pending. Keep track of whether the server has us in a group, so
the Disconnect still reaches it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:07 +01:00
Henrik RydgårdandClaude Opus 5.5 e5bafd423d sceNetAdhocMatching: Report every join request, after SetHelloOpt too
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>
2026-10-29 22:39:06 +01:00
Henrik RydgårdandClaude Opus 5.5 5cc3554667 sceNetAdhocMatching: Pass the pair matching tests
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>
2026-10-29 22:39:05 +01:00
Henrik RydgårdandClaude Opus 5.5 e94ae7f190 sceNetAdhocMatchingStart2: Check the stack sizes and partitions
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>
2026-10-29 22:39:05 +01:00
Henrik RydgårdandClaude Opus 5.5 25ee41f135 sceNetAdhocMatching: Hand out context IDs in increasing order
A PSP numbers matching contexts 1 to 15 in increasing order and then
starts over, instead of reusing the lowest free ID (pspautotests
net/adhoc/matching).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:05 +01:00
Henrik RydgårdandClaude Opus 5.5 ce81c07a69 sceNetAdhocMatching: Start returns 0, not the matching thread's ID
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>
2026-10-29 22:39:05 +01:00
Henrik RydgårdandClaude Opus 5.5 ae906cb292 sceNetAdhocMatching: Don't list a member twice
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>
2026-10-29 22:39:05 +01:00
Henrik RydgårdandClaude Opus 5.5 db7c9ede34 sceNetAdhocctlGetPeerInfo: NO_ENTRY for the local MAC
The console itself isn't in its peer list, and a PSP returns NO_ENTRY
for it (pspautotests net/adhoc/adhocctl).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:05 +01:00
Henrik RydgårdandClaude Opus 5.5 874080ced2 sceNetAdhocDiscover: Go through the states a PSP goes through
With nobody to find (pspautotests net/adhoc/discover): InitStart leaves
the result field alone and fails with 0x80411301 while running,
adhocctl enters the DISCOVER state after the first Update, and after a
Stop adhocctl goes back to DISCONNECTED with a DISCONNECT event, then
the status becomes COMPLETED with the result cancelled. RequestSuspend
while discovering fails with 0x80411303 instead of cancelling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:05 +01:00
Henrik RydgårdandClaude Opus 5.5 48c9bf9988 sceNetAdhocPdpRecv: Don't copy a datagram that doesn't fit
With a buffer smaller than the next datagram, a PSP returns
NOT_ENOUGH_SPACE with the datagram's size, copies nothing, and keeps
the datagram for the next call (pspautotests net/adhoc/pdp). The first
bytes were copied into the buffer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:05 +01:00