aemu_postoffice is now at upstream main, which has the relay performance
fixes: the built-in relay server waits for traffic and pumps as soon as
it arrives instead of sleeping 1 ms between pumps, and pumps on its own
thread instead of waking three workers twice each per pump; closing a
session no longer sleeps 50 ms (the delay is now an option, 0 here,
where everything but a PTP connect's thread, which is joined before its
session closes, runs on the CPU thread); connects and blocking waits for
the server's replies wait on the socket instead of sleeping or spinning.
Unread PTP data is also delivered before a close now.
The pair tests through the relay used to barely gain from speed (29 s at
600%, 22 s at 1600%); now 19 s and 11 s, as fast as P2P at 600%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Calls that checked one byte of a buffer, then used the game's length:
- GetPeerList and GetAddrByName cleared that many bytes; GetPdpStat and
GetPtpStat filled that many entries. Clamped to valid memory.
- PollSocket read and wrote count entries.
- GetNameByAddr, GetPeerInfo, Join, GetGameModeInfo and UpdateReplica
wrote or read whole structs.
- PtpSend and PtpRecv used the whole data length.
- PdpSend and PdpRecv took host pointers from generic wrappers. Their
syscalls now check the game's buffers and pass NULL for bad ones,
which the calls already reject, since game mode calls the functions
with host buffers.
- Matching SelectTarget, CancelTargetWithOpt and SendData copied the
whole opt or data length. GetMembers cleared a NULL buffer when given
a bad one.
Also: the matching thread list is savestated without the contexts, so a
context's index may be past its end after a load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 6.60 PSP returns SCE_KERNEL_ERROR_ILLEGAL_ADDR for one (checked with a
throwaway test; the other bad addresses hang the PSP, so they can't go in
pspautotests).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Calls that checked one byte of a game's buffer and then wrote or read the
whole thing, or trusted a negative length:
- sceNetEtherNtostr/Strton, the apctl BSS descriptor calls, GetState and
sceNetGetDropRate.
- sceNpAuthGetTicket wrote its whole ticket whatever the length; it's
now built locally and written once the buffer is known to hold it.
sceNpAuthGetTicketParam checked its limits after copying, and walked
ticket params past the ticket's end.
- sceNp2's communication ID and passphrase, a negative maxServerIds, and
an unchecked unknown1Ptr.
- sceParseUri's work area and scheme string, sceParseHttp's negative
header length and value address, and ReadVariableSizedStruct's size.
- sceKernelCreateLwMutex initialized the work area without checking it
at all, and the other LwMutex calls checked one byte of it.
- sceNetAdhocctlGetScanInfo cleared the length the game gave, of a
buffer checked by one byte (or any length, negative ones included).
Also: a game mode join inserted at begin() + 1 into a possibly empty
list of players.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A console that has left the network sends into the air and receives
nothing, but the relay checked whether it was disabled first. On the
Windows runner the host of pair/vanish finished and took its relay with
it, the client's reconnect failed and disabled the relay, and the
client's PTP send and recv afterwards came back DISCONNECTED.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed/unsigned comparisons, bool/int mixes and narrowing in our own code,
plus duplicate NOMINMAX defines. The UWP Common/Core/UI projects now define
_CRT_NO_POSIX_ERROR_CODES like the desktop ones, so SocketCompat.h no longer
redefines the errno macros there. ImMemView's byte search no longer strdup()s
the query (it leaked on the error path).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its entry in sendTargetPeers stayed, so the next blocking connect from
the same thread on a socket that got the same id returned BUSY
(pspautotests net/adhoc/pair/basic, connect to a closed port, after a
connected socket was closed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Off the network the datagram reaches nobody and the send succeeds, but
the session was recovered first, and a send whose session wasn't up
returned WOULD_BLOCK (pspautotests net/adhoc/pair/vanish, PDP send after
leaving).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each instance paced itself against the wall clock, so on a busy machine
the one that got less CPU fell behind, and anything a test times across
instances (a peer leaving, a matching timeout) came out differently with
the load: pair/vanish and pair/matching3 failed on macOS CI.
With --sync-file and --sync-count, instances on one machine share a file
of one slot per instance. CoreTiming ends a slice at every emulated
millisecond, publishes it there, and waits until every other instance
has reached it too, so they're never more than a millisecond apart. It
works for any number of instances. One that stops moving (a debugger
pause, a crash) is waited for 2 s and then left out until it moves
again, and one that blocks the CPU thread in real time on something the
others must do (the relay PTP connect) stands aside meanwhile.
Tools/adhoc_multi.py and adhoc_session.py use it (--no-sync turns it
off). The host's 1 s head start is gone: they wait for the server's
port instead. adhoc_multi writes each instance's output to a file, as a
full pipe would now stop the other instances too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Fixes#19248
The server loop polled every socket and slept 10 ms, so it woke 100
times a second with nothing to do, and every message could wait up to
10 ms of real time before it was handled, which at a sped-up emulation
speed is many times that in game time. It now waits in select() on the
listening socket and every user, and wakes at least every 50 ms to notice
being stopped. It also no longer stops serving everyone while the hosting
instance is paused in the debugger.
pspautotests: pair/discover and pair/ptpmsg.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A PSP keeps what arrives in the socket's buffer, of the size given to
PdpCreate, and drops a datagram that doesn't fit: a buffer of 0 gets
nothing, one of 2048 bytes only some of ten 1000-byte datagrams
(pspautotests net/adhoc/pair/flow). Each transport now only hands over
the datagrams that have arrived (PdpTakeDatagram), into a queue per
socket that is filled every millisecond and whenever the game looks, and
PdpRecv, its blocking retry, GetPdpStat, PollSocket and game mode read
from that. This replaces each transport's own receive paths, so the
relay now reports the sender with NOT_ENOUGH_SPACE too, and P2P's
blocking receive no longer misreads a stale errno.
A datagram to ourselves goes straight into our own socket's queue, as on
a PSP, so it's there at once through the relay too (net/adhoc/term).
Also from comparing the transports:
- P2P: a blocking Accept of a connection from outside the group keeps
waiting instead of returning -1.
- Relay: an accepted socket gets an id of its own, not its listener's,
which blocking calls are told apart by.
- ALERT in PollSocket comes from the socket in both transports.
- No send/receive timeouts are set on the game's sockets: all host
sockets are non-blocking, and in relay mode the ids aren't host
sockets at all.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A PS1 disc can be a cue sheet, .ccd or .mds rather than an image, and
GetSizeUncompressedInBytes tried to open one as a block device. Measure the
disc the sheet describes instead, and count its image files in the size on
disk too, not just the sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Winsock never makes a socket whose connect failed writable; the failure
shows in select's exception set, with the reason in SO_ERROR. The poll
now checks both, and SO_ERROR on a writable socket too, which is where
other systems put a refusal.
Windows still retries a refused connect for about a second before
reporting it, far longer than a PSP at the tests' 800% speed, so
pair/basic is a known P2P failure on Windows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A connect PtpOpen started counted as up as soon as getpeername
succeeded, which on Windows happens while it's still in progress, even
one about to be refused. It now needs the socket ready too, and a
pending connect's failure is read from SO_ERROR, the only place Windows
reports it (pspautotests net/adhoc/pair/basic).
- Out of the group, PdpRecv only dropped what its peek saw; on Windows a
peek can fail and leave the next datagram unchecked, so a vanished
client still got the host's data (pair/vanish).
pspautotests: pair/vanish times PTP's failure from when the peer left the
peer list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The connect PtpOpen starts goes through the relay on a host thread, so
when it finished in emulated time depended on the emulation speed and on
how busy the host was. At --speed=800 on a loaded CI runner it often
wasn't up within the 100 ms net/adhoc/pair/ptppoll polls for, while on a
PSP it's up a few ms after PtpOpen.
The relay's per-millisecond poll now waits (at most a second, once per
connect) for a connect in progress to finish, filling local listeners'
backlogs meanwhile, since the listener may be on the same console.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- GetPtpStat on a listener read its listen session as a connection,
which could write past it; a listener now reports nothing waiting.
Neither GetPtpStat nor GetPdpStat reports a dead session's negative
status as a byte count any more, and a new PDP socket starts with
nothing waiting instead of its buffer size.
- A poll of a connection whose peer has closed reports DISCONNECT, as
the P2P transport and a PSP do (pspautotests net/adhoc/poll).
- Only a send that went out counts as the last send for a vanished
peer's timeout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A connect to a port nobody listens on is refused within a second on a
PSP, and every later connect on that socket is refused too, even once
something listens there (pspautotests net/adhoc/pair/basic,
pair/ptpclose). PPSSPP recreated the socket and tried again until the
game's timeout ran out, to cover a peer that wasn't listening yet.
Dissidia 012 and Fate/Unlimited Codes, named in the old comments, still
get to their matches.
Also set the established time wherever a connection becomes established,
which the timing of a vanished peer's failure counts from.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
How long an adhocctl request waits for the server before giving up, and
how long a joiner's GAME event waits for the host to show up, were
measured in wall time, so a game saw them shorter when sped up and
longer when slowed down.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A PSP completes a connect to a listener without the game calling Accept,
so an opened PTP connection is up a few ms after PtpOpen (pspautotests
net/adhoc/pair/ptppoll). The relay only answers the connecting side once
the listening side accepts, so the relay transport now accepts pending
connections into a queue as long as the listener's backlog, from a new
per-millisecond AdhocTransport::Poll() and from polls and accepts, and
PtpAccept takes them from there. A poll reports ACCEPT while the queue
isn't empty.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Out of the group, a PSP's sends reach nobody and nothing reaches it, and
a PTP connection to a peer that has left fails once its retransmits would
have run out. Only the relay transport did this; the P2P one kept sending
over the host's sockets, so neither side ever noticed the other was gone
(pspautotests net/adhoc/pair/vanish).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
- 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>
SettingsPage::RestoreDefaults() adds a button that, after asking, puts the
config settings added to the page before it back to their defaults, running
the config's OnChanged reactions. Config::RestoreSettingToDefault() does the
work for a single setting, including those in blocks that reset as a whole.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
--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>
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>
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>
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>
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>
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>
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>
- 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>