Commit Graph
16425 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 dc450f79b3 sceNetAdhoc: Hand out socket IDs in increasing order, like the PSP
A PSP numbers adhoc sockets in increasing order instead of reusing the
lowest free ID, and a PDP or PTP create that fails its address or port
check, or finds the PDP port in use, uses up an ID too (pspautotests
net/adhoc/pdp and net/adhoc/ptp). Find free slots starting after the last
one handed out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:05 +01:00
Henrik RydgårdandClaude Opus 5.5 97b18b95bb sceNetAdhocPollSocket: Report what a PSP reports
On a PSP (pspautotests net/adhoc/poll):
- a PTP listener reports SEND and FLUSH, and ACCEPT with a connection
  waiting;
- a connected PTP socket reports SEND, CONNECT and FLUSH, and RECV with
  data waiting;
- a connection whose peer has closed reports DISCONNECT alone, and the
  call returns EXCEPTION_EVENT;
- an unknown ID puts INVALID in the first entry;
- a blocking poll with nothing to report by its timeout returns TIMEOUT,
  and one that hits a disconnected socket or the poll alert returns at
  once.

A connection PtpOpen started counts as connected once it's up. Sockets
still connecting keep PPSSPP's own events (Fate Unlimited Codes,
Bleach), and a refused connect still isn't reported, since PPSSPP keeps
retrying it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:05 +01:00
Henrik RydgårdandClaude Opus 5.5 0d089972a3 sceNetAdhoc: Socket alerts don't stop calls that can complete at once
On a PSP (pspautotests net/adhoc/alerts), PtpConnect on a connected
socket and PtpFlush with nothing left to send return 0 with their alerts
set, and the poll alert reports ALERT alone in revents. A connection
PtpOpen started counts as connected once it's up, even if no call has
looked at it since.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:05 +01:00
Henrik RydgårdandClaude Opus 5.5 7e44dc7e8d sceNetAdhocPdpCreate: Give port 0 a port in 0x1000-0x7FFF
On a PSP, a PDP socket created on port 0 gets a free port between
0x1000 and 0x7FFF (pspautotests net/adhoc/limits). The host picked
one instead, usually 0x8000 or above once the port offset was taken
off. Pick it the way PtpOpen already does for PTP, and try another if
the host port is taken. Port 0 still doesn't become the fixed port the
offset alone would give (Phantasy Star Portable 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:39:05 +01:00
Henrik Rydgård 9c5f87f33d Allow picking 2 frames to buffer again. 2026-10-29 15:25:34 +01:00
Henrik Rydgård 37fa5896c1 Merge pull request #22839 from hrydgard/play-crash-fixes-4
Play crash fixes: vertex range, interrupt and vtimer edge cases, GL push buffer
2026-10-29 09:17:42 +01:00
Henrik Rydgård 1ed60bb835 Merge pull request #22836 from hrydgard/headless-ppge-slots
Headless/debugger fixes, and a crash loading states after a system dialog
2026-10-29 09:17:17 +01:00
Henrik RydgårdandClaude Opus 5.5 d5503978e7 Interrupts: Check whether a sub-interrupt is enabled when its turn comes
All the enabled sub-interrupt handlers were queued when the interrupt fired,
so a handler disabled by an earlier one in the same VBLANK still ran, and one
an earlier handler enabled waited for the next VBLANK. The PSP checks each
handler as it gets to it (intr/disablepending): skip a disabled one, and
queue a newly enabled one if it comes after the handler that's running.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:58:33 +01:00
Henrik RydgårdandClaude Opus 5.5 0987fdb169 VTimers: Don't run a pending timer that was cancelled, stopped or given a new handler
The interrupt ran it anyway, and after sceKernelCancelVTimerHandler or
sceKernelSetVTimerHandler with no handler that meant jumping to address 0.
This happens when one timer's handler cancels another timer that came due
together with it, or when a timer comes due while interrupts are suspended.
On the PSP none of these run (threads/vtimers/pending/cancelpending). The
pending entry becomes an invalid id, which the interrupt pops and skips, as
with deleted timers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:58:32 +01:00
Henrik RydgårdandClaude Opus 5.5 b566634318 Add --lag-sync, and let it pace headless --realtime
The JIT cores only return from the run loop at the end of a frame, so
--realtime paces once per frame, like the app. With --lag-sync, lag
sync paces every emulated millisecond instead and --realtime's own
sleep stays out of its way. Two instances measuring round trips over
the relay (pspautotests net/adhoc/pair/timing) go from 15-31 ms to
3 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:33:14 +01:00
Henrik RydgårdandClaude Opus 5.5 d40290b30d sceDisplay: Keep lag sync from drifting, and let it pace alone
Lag sync ("Force real clock sync") waited for 1 ms after the end of the
previous sync, so every sync's oversleep added up: GTA VCS lost 0.4 s
per minute against the wall clock. Wait instead for a fixed base plus
the emulated time since, which can't drift, and start over from now
only when more than 100 ms behind (a pause, or loading).

While it runs, the frame throttle's own waits are skipped. They were out
of phase with lag sync, which then ran flat out to catch up, so frames
were bursts again. The wait itself is the precise one the frame
throttle uses, instead of a yield() spin on Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:33:14 +01:00
Henrik RydgårdandClaude Opus 5.5 b40c427807 Built-in relay: Pump every millisecond
Every message through the relay waited for the next pump, every 8 ms,
or 100 ms while nobody was connected, which is when a game's first
connection comes in. That was most of what a round trip took once the
emulators stopped running in frame bursts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:33:14 +01:00
Henrik RydgårdandClaude Opus 5.5 06a504c9d0 Interrupts: Don't drop the running entry when a handler releases itself
__ReleaseSubIntrHandler erased every pending entry for the sub-interrupt,
including the one at the front, which is the handler that's running. A
handler releasing its own sub-interrupt then left
__KernelReturnFromInterrupt popping an empty list (or the next entry),
which is undefined behaviour and fits Play reports crashing in free under
it on 32-bit. Skip the front entry while in an interrupt, as
__CancelRaisedInterrupts already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 01:58:13 +01:00
Henrik RydgårdandClaude Opus 5.5 8f6d7fcf94 Log CPU exception reports in headless too
Headless sets a debug output listener, which only passes the output on
when collecting a test's output, so a game crashing in headless left
nothing in the log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 00:21:49 +01:00
Henrik RydgårdandClaude Opus 5.5 93b7dacc1e WebSocket debugger: runUntilTime that's already there, and across loads
A target time that had already passed, relativeUs 0 for one, failed
without a stop event, so a client waiting for cpu.stepping waited until
its own timeout. Now it doesn't run, and reports the stop right away.

A savestate loaded during the run kept the deadline's absolute time,
which belongs to the timeline the load replaced: a state from later in
the game stopped the run at once, one from earlier ran it for as long
as the gap. Now the deadline keeps the time it had left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 00:21:49 +01:00
Henrik RydgårdandClaude Opus 5.5 df72f3be81 WebSocket debugger: Any savestate slot in headless
game.state.save and game.state.load with a slot were limited to the
app's slot count (5 by default), which only means how many the UI shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 00:20:14 +01:00
Henrik RydgårdandClaude Opus 5.5 b719c7e027 PPGe: Load the atlas in headless too
Headless skipped it, so system dialogs (savedata messages, the network
dialogs) had no text, which made driving a game through them blind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 00:20:14 +01:00
Henrik RydgårdandClaude Opus 5.5 f865361c8d sceNetResolver: A lookup takes 5 ms, as on hardware
sceNetResolverStartNtoA returned at once, but on a PSP a lookup through
the access point takes 4-18 ms (pspautotests net/inet/timing), with the
thread asleep waiting for the answer, so the game's other threads run.
It now takes 5 ms and lets them.

The host lookup itself is still synchronous, and stops the emulator
while it waits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:30:07 +01:00
Henrik RydgårdandClaude Opus 5.5 8583d3beaa sceNetInet, sceNetApctl: Quicker access point connection, and no busy waits
pspautotests net/inet/timing measures, on a PSP that has connected to
an open network before: JOINING 11 ms after sceNetApctlConnect,
GETTING_IP after about 2.6 s, GOT_IP after about 3.6 s (6.6 s the first
time), and DISCONNECTED 1.1 s after a disconnect. We took 2 s to get to
each of the three, 6 s in all, and 10 ms to disconnect. Games have to
wait for the connection anyway, so it's now quicker than hardware:
JOINING after 10 ms, GETTING_IP 1.25 s later and GOT_IP 0.5 s after
that, with the delays in named constants. Disconnecting takes 0.5 s,
the state staying as it was until then.

The handlers also got 0 as the old state of every step but the first.
Each step now starts from the state the one before ended in.

recv, recvfrom, select and poll still added a 500 us or 1 ms delay to
every call, a workaround from before blocking calls waited for real.
They now take what they take on the PSP when they return at once,
4-15 us (net/inet/timing).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:30:07 +01:00
Henrik Rydgård 7aecf86c4d Merge pull request #22830 from hrydgard/inet-8-stack
sceNetInet: The firmware's stack check, and GetPspError as it computes it
2026-10-28 19:00:09 +01:00
Henrik RydgårdandClaude Opus 5.5 fc9a3d3a39 sceNetAdhoc: Give the built-in relay the port its client connects to
aemu_postoffice's default happens to match, but nothing tied the two.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 14:48:47 +01:00
Henrik RydgårdandClaude Opus 5.5 d293cd59b5 proAdhocServer: Let players log in from an IP that's already in use
The relay tells players apart by MAC, so players behind one NAT, or
several instances on one machine, can all play through it. The second
login used to be dropped without an answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:56:56 +01:00
Henrik RydgårdandClaude Opus 5.5 5e92e0e02a sceNetAdhoc: Fix the built-in relay's thread
Join the old relay thread before starting a new one (assigning to a
joinable std::thread terminates), don't map the UPnP port when another
relay already has it, sleep the intended interval (the elapsed time was
subtracted the wrong way round), use one worker per pool, and guard the
log helper against empty messages. Drop the warning that the built-in
server doesn't support relay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:56:56 +01:00
Katharine Chui 92b00eb28a fix code formatting 2026-10-28 13:56:56 +01:00
Katharine Chui 7af6661cf0 add upnp call around relay server loop 2026-10-28 13:56:56 +01:00
Katharine Chui c8a9ed025a start aemu_postoffice relay with built-in adhoc server 2026-10-28 13:56:56 +01:00
Katharine Chui 4791043e8d build aemu_postoffice relay server module in cmake 2026-10-28 13:56:56 +01:00
Henrik RydgårdandClaude Opus 5.5 fdbce5fbf2 sceNetInet: The firmware's stack check, and GetPspError as it computes it
Every export of pspnet_inet.prx but the address conversions starts with
sceKernelCheckThreadStack(), and fails without doing anything if the
thread has less than 0x1000 bytes of stack free (0xFE0 for socket()):
the socket calls with -1, without setting errno, and Init, Term,
GetErrno, GetPspError and SocketAbort with 0x80410005. Each function's
limit plus its own stack frame comes to that. pspautotests
net/inet/errnothread shows it on a thread with a 4 KB stack.

GetPspError is 0x80010000 | errno, and 0 for no error, rather than a
table of guesses, so 0x80010001 on a thread with no errno record yet
(net/inet/errnothread).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:41:44 +01:00
Henrik RydgårdandClaude Opus 5.5 3ee22fbd3d sceNetInet: TCP socket states and bind's port sharing
Following pspautotests net/inet/errors, states and reuse:
- bind decides itself whether a port is in use, as 4.4BSD's in_pcbbind()
  does with SO_REUSEADDR and SO_REUSEPORT. The host can't, since its
  sockets have both on for running several instances, so any number of
  sockets could bind the same port. A socket that's bound already is
  EINVAL.
- Shutting down the sending side of an unconnected TCP socket, or a
  failed connect, throws away its TCP state as in 4.4BSD's tcp_usrreq(),
  after which bind, listen, connect, accept and shutdown fail with
  EINVAL, and its port is free again. shutdown otherwise only fails for
  a bad how, and still reaches the host, so a thread waiting in accept
  wakes up.
- listen on an unbound TCP socket binds one, which Winsock didn't, and on
  a connected one succeeds.
- connect on a listening socket is EOPNOTSUPP. Winsock's WSAEINVAL for
  it looked like a connect in progress, and a blocking socket waited
  forever. A second connect on a loopback connection the host hasn't
  noticed finishing is EISCONN once it has.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:09:28 +01:00
Henrik RydgårdandClaude Opus 5.5 d76e1a4496 sceNetInet: Recvmsg, Sendmsg rewritten, and sendto without an address
Recvmsg was a stub that failed without setting errno. It now receives
like recvfrom, scattering into the buffers and waiting like the other
blocking calls. Sendmsg is rewritten around the same msghdr reading.
Following pspautotests net/inet/msg:
- Recvmsg truncates the address to namelen, gives TCP none and a namelen
  of 0, sets MSG_TRUNC in msg_flags, and controllen to 0.
- Sendmsg with no buffers sends an empty datagram, which macOS failed.
- sendto without an address works on a connected socket. Linux failed
  it, given an address of length 0.

On Windows, Sendmsg uses WSASendTo, since WSASendMsg only takes
datagram sockets, and Recvmsg WSARecvFrom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:25:39 +01:00
Henrik RydgårdandClaude Opus 5.5 cc1fad6e2a sceNetInet: Check up to 64 sockets at once on Windows, as before
Splitting larger checks into groups of 64 for Winsock's select() was
untested, and no game is known to poll more sockets than that. Past 64,
it now logs an error and checks the first 64, as the old code did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:42:58 +01:00
Henrik RydgårdandClaude Opus 5.5 759c9e590a sceNetInet: Poll and select as on hardware, and host sockets past FD_SETSIZE
Following pspautotests net/inet/polledge:
- poll reports only what was asked for, and never POLLHUP or POLLERR,
  even for a peer that closed or reset the connection.
- select with a closed descriptor in a set fails with EBADF and leaves
  the sets alone. It used to skip it.

The host side now uses poll() outside Windows, where select() can't take
a descriptor numbered FD_SETSIZE or above and FD_SET wrote past the
fd_set. macOS reports a peer that closed as POLLHUP with POLLPRI, and
the POLLPRI is ignored, as its select() shows nothing for it. Winsock's
select() has no such limit, but takes only 64 sockets a set, so it's
called on groups of 64, and poll is no longer cut short at 64 entries
there. A failing poll sets the host's errno rather than EINTR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:37:32 +01:00
Henrik RydgårdandClaude Opus 5.5 59e9d889e1 sceNetInet: Socket options as the PSP keeps them
The PSP's getsockopt and setsockopt work like 4.4BSD's, which
pspautotests net/inet/options records in detail, and most of it didn't
match: flags read back as their bit (SO_REUSEADDR as 4, SO_NBIO as
0x80), buffer sizes have their own defaults, getsockopt copies as much
as fits and says how much, and unknown options and levels fail with
ENOPROTOOPT and EINVAL.

So the socket-level options are now kept in InetSocket, and only the
ones that change what the host socket does are passed on to it. That
also stops the timeouts reaching the host, where Winsock read the
timeval as a millisecond count. Accepted sockets copy their options from
the listener, like BSD's sonewconn(). IP_TTL is kept too, as Windows'
default is 128 and the PSP's 64.

Timeouts are set in microseconds, but kept in 10 ms clock ticks, and
one under a tick is no timeout at all (net/inet/limits). Buffers can be
up to 233016 bytes (net/inet/limits), 4.4BSD's limit with 256-byte
mbufs, where we allowed 8 MB. SO_ERROR reads as an SCE error code rather
than an errno (net/inet/tcp).

The host's TCP send buffer is capped at 64 KB, closer to the PSP's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 10:59:24 +01:00
Henrik Rydgård 9bece86f08 Merge pull request #22825 from hrydgard/inet-3-addresses
sceNetInet: Addresses in and out like the PSP
2026-10-28 10:56:31 +01:00
Henrik Rydgård 2816d6a409 Merge pull request #22818 from hrydgard/adhoc-arg-checks
Adhoc: Check function arguments more like the real thing.
2026-10-28 10:29:26 +01:00
Henrik RydgårdandClaude Opus 5.5 0a86a248da sceNetInet: Addresses in and out like the PSP
- accept and recvfrom write only as much of the address as its length
  allows, and set the length to that (pspautotests net/inet/udp and
  tcp). They wrote all 16 bytes and set it to 16. They now also give the
  host an address of their own, so one the game passes without a length
  can't make the host fail the call with EFAULT.
- InetAton and InetAddr take the forms 4.4BSD's inet_aton() does
  ("1.2.3", "16909060", "0x7f.1", octal with a leading zero), which the
  host's inet_pton() didn't. InetPton takes only a plain dotted quad,
  and only AF_INET. All three stop at a space. InetNtop returns NULL with
  ENOSPC when the string doesn't fit, leaving the buffer alone, and with
  EAFNOSUPPORT for other families (net/inet/addr).
- getpeername after the peer closed is ENOTCONN rather than macOS's
  EINVAL, as 4.4BSD checks for a connection first (net/inet/errors).

Coded Arms Contagion calls InetNtop with a 4-byte buffer, which now gets
NULL, as on hardware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 10:29:23 +01:00
Henrik RydgårdandClaude Opus 5.5 de175c7d03 proAdhoc: Say why MAC comparisons ignore the first byte
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:52:55 +01:00
Henrik RydgårdandClaude Opus 5.5 e0a6a662f4 sceNet, sceNetAdhoc: Cite the tests for behaviour that's the PSP's
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>
2026-10-28 09:52:55 +01:00
Henrik RydgårdandClaude Opus 5.5 591cec315e sceNetAdhocMatching: Put the game workarounds behind compat flags
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>
2026-10-28 09:52:40 +01:00
Henrik RydgårdandClaude Opus 5.5 011f6b1d98 sceNetResolver: Work without sceNetResolverInit, like the PSP
On a PSP, Create, lookups, Stop and Delete all work before Init and after
Term, and a second Init fails with 0x80000020 (pspautotests
net/inet/offline). Instead of starting out initialized, which Outrun 2006
needed, drop the init requirement, so every game gets the PSP's
behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:52:22 +01:00
Henrik RydgårdandClaude Opus 5.5 03076115bd sceNet, sceNetApctl: Check arguments like the PSP
From pspautotests net/inet/offline, recorded on a PSP:

- sceNetGetMallocStat(NULL) gives 0x80410103, and sceNetThreadAbort and
  sceNetFreeThreadinfo on something that isn't a thread 0x80410104.
- sceNetApctlAddHandler takes two handlers (the internal handler calls
  aren't limited), DelHandler tells an impossible ID (0x80410A82) from a
  free one (INVALID_ID), and GetState(NULL) and
  GetBSSDescIDListUser(NULL size) give 0x80410A03. A NULL buffer in
  GetBSSDescIDListUser asks for the size instead of failing.

GetState before Init and GetInfo while disconnected still succeed: the
first check was disabled on purpose at some point, and the simulated
infrastructure connection relies on the second.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:52:22 +01:00
Henrik RydgårdandClaude Opus 5.5 8a48a157d4 sceNetResolver: Don't fault on a NULL address, and check arguments
sceNetResolverStartNtoA with a NULL address pointer wrote to address 0
and stopped the game. From pspautotests net/inet/offline, recorded on a
PSP: a NULL name or address gives INVALID_PTR, an empty name 0x80410418,
a NULL buffer in Create INVALID_PTR, and Stop with nothing running
ALREADY_STOPPED (the check was inverted). PollAsync and WaitAsync check
the resolver ID, and PollAsync its status pointer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:52:22 +01:00
Henrik RydgårdandClaude Opus 5.5 0904b47639 sceNetAdhocMatching: Check arguments like the PSP
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>
2026-10-28 09:52:22 +01:00
Henrik RydgårdandClaude Opus 5.5 58d73cbbc6 sceNetAdhoc, adhocctl: Check arguments like the PSP
From the net/adhoc pspautotests (pdp, ptp, poll, limits, adhocctl,
gamemode), recorded on PSPs:

- PDP and PTP: NULL pointers and nonzero flags give INVALID_ARG, before
  the socket ID is checked. Zero and multicast addresses give
  INVALID_ADDR, ports 0 and 65535 INVALID_PORT, and send lengths over
  65523 or NULL data INVALID_DATALEN. A length of 0 sends nothing and
  succeeds. Zero buffer sizes, retransmit settings and backlogs are
  accepted. PdpRecv takes a NULL sender address or port. Running out of
  socket IDs gives SOCKET_ID_NOT_AVAIL, and out-of-range IDs on
  delete/close INVALID_SOCKET_ID. PollSocket rejects flags other than
  NONBLOCK.
- adhocctl: Init checks its stack size, priority and ad hoc ID;
  AddHandler needs Init, allows the same function twice and takes at most
  4 handlers; DelHandler on an unknown ID gives ID_NOT_FOUND; bad group
  names give adhocctl's INVALID_ARG; GetParameter and GetPeerList fail
  outside a group.
- Game mode: a NULL group name and a bad minimum are rejected,
  GetGameModeInfo fails outside game mode, and the entry timeout reports
  adhocctl's TIMEOUT.

The DBZ Shin Budokai 2 workaround that replaces the game's local MAC with
ours stays, so another console's MAC is still accepted when creating a
socket.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:52:22 +01:00
Henrik RydgårdandClaude Opus 5.5 79537d18bd sceNetInet: errno per thread as on hardware, and the socket table
errno:
- A thread that hasn't made a call yet reads 0x80410001 from GetErrno,
  as the PSP has no errno record for it until then. errno is saved in
  savestates now, in a new sceNet section version.
- socket(), close() and setsockopt() clear it when they succeed.
- Running out of descriptors is EMFILE, at 255 like the PSP, and an
  unconnected UDP socket sending without an address EDESTADDRREQ, which
  Winsock calls ENOTCONN.
- On Windows, ENOBUFS, EPROTOTYPE, ENOPROTOOPT, EMSGSIZE, ENETRESET and
  EDESTADDRREQ now map to the PSP's errnos. They only did elsewhere.
All from pspautotests net/inet/errors, options and errnothread.

The socket table:
- Descriptor numbers are handed out round-robin up to 255, as on the PSP
  (net/inet/states), rather than the lowest free one.
- Closing frees the slot even if the host's close fails, and Close and
  CloseAll take the lock the rest of SocketManager does.
- accept no longer leaks the host socket when it runs out of numbers.
- bind no longer asks UPnP to forward port 0, and closing the socket
  removes its forward, unless another socket still uses the port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:50:48 +01:00
Henrik RydgårdandClaude Opus 5.5 5610c9ff9b sceNetInet: Validate arguments, failing with errno set
Bad arguments now fail the way the PSP fails them, with -1 and errno
set, rather than reaching the host or returning something else:
- A descriptor that isn't open is EBADF. The result used to be EBADF's
  number, 9, which looks like success (pspautotests net/inet/errors).
- Buffers and addresses the host reads or writes directly are checked to
  lie in game memory, failing with EFAULT, rather than letting a host
  recv or send run past the end of it.
- socket() with anything but an AF_INET TCP, UDP or raw socket, with a
  protocol that fits, is EPROTONOSUPPORT (net/inet/errors).
- bind with an address shorter than 16 bytes is EINVAL, and with another
  family than AF_INET EADDRNOTAVAIL (net/inet/errors).
- shutdown with a how other than 0-2 is EINVAL (net/inet/states).
- sendto with an address shorter than 16 bytes is EINVAL, which Winsock
  calls EFAULT, and Sendmsg with any control data, or a short address,
  EINVAL (net/inet/msg).
- poll reports a negative fd as POLLNVAL like any other that isn't open,
  rather than failing (net/inet/polledge).

The pspautotests update brings in the new net/inet tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:02:20 +01:00
Henrik Rydgård 302e408069 Merge pull request #22817 from hrydgard/play-crash-fixes-3
Fixes for crashes from the 1.20.5 beta Play reports: savedata list count, GL startup race
2026-10-27 23:07:17 +01:00
Henrik Rydgård 5c84203bbe Merge pull request #22512 from hrydgard/inet-select-wait
sceNetInet: Wait in emulated time instead of blocking the host
2026-10-27 22:17:24 +01:00
Henrik RydgårdandClaude Opus 5.5 ad9c65ae39 EmuThread: Run the graphics context's ThreadStart before starting the emu thread
On OpenGL the render thread's ThreadStart fills in the GL strings and
picks the buffer strategy, while the emu thread, started just before
it, could already be in NativeInitGraphics reading them. A Play report
crashed walking the strings map in BuildReportingInfo. ThreadStart also
resets the exit flags, which could undo an early exit by the emu
thread. Libretro already starts its emu thread in this order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 22:11:09 +01:00
Henrik RydgårdandClaude Opus 5.5 e10ebbeb44 Savedata: Keep the list count at 0 when a name in the list is rejected
The multi-name path counted names straight into saveDataListCount, so
returning on a path traversal name left a count behind with the list
already freed by Clear(). The next savestate then serialized that many
entries from a null list and crashed in Do(std::string). Count on the
side, and don't write entries for a list that isn't there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 22:04:45 +01:00