Commit Graph
49995 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 cf17b0ac79 Merge pull request #22841 from hrydgard/more-ui-tweaks
More UI tweaks, allow buffered frames = 2 again
2026-10-29 15:48:24 +01:00
Henrik Rydgård b319b1783a Load round rect radius from theme. Set it to 0 in 1995. 2026-10-29 15:28:37 +01:00
Henrik Rydgård 9c5f87f33d Allow picking 2 frames to buffer again. 2026-10-29 15:25:34 +01:00
Henrik Rydgård d09a9ffa11 Correct corner radius of popups to match inner rounded things 2026-10-29 15:12:55 +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ård 5ceca071fe Merge pull request #22838 from hrydgard/lag-sync-master
Lag sync: Fix drift and pacing. Set built-in relay to pump every millisecond
2026-10-29 09:16:22 +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 fe6cfb6c77 Test: intr/releaseself, a subinterrupt handler releasing itself
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:22:19 +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 7103341202 GL: Halve the defragmented push buffer when the big allocation fails
When a frame spilled over into several push buffers, Defragment freed
them and asserted if one block of their summed size couldn't be
allocated. On 32-bit devices that contiguous allocation fails where the
pieces fit, which Play reports from 32-bit ARM show at the start of a
frame. Halve the size until it fits; a smaller buffer just spills into
more buffers again, as it would have before defragmenting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 02:05:35 +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 fd5f9206f0 GPU: Drop non-indexed draws whose vertices run past the end of memory
Only the start of the vertex range was checked. Nothing is mapped after
the end of RAM or of the VRAM mirrors, so a draw starting near either end
faulted on the first read past it: in TestBoundingBoxFast, the vertex
decoder or the software renderer, depending on the path. 3000 triangles
at 0x047FFF00, or a 4x4 bezier patch at 0x047FFF80, crashed both Vulkan
and the software renderer. Prims, beziers and splines now check the
whole range on both, including in the fast loop and when merging draws.
Indexed draws still only check the index range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 01:38:47 +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 f1274c1889 TextureCache: Never rehash the PPGe atlas
A HASH_RECHECK forced a rehash even of the RELIABLE atlas, which hashed
its fake address as if it were in emulated memory: a memory exception
in the middle of the game's next system dialog update. Loading a state
saved after one had drawn text was enough to hit it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 00:20:14 +01:00
Henrik Rydgård 83e616eaf9 Merge pull request #22834 from hrydgard/inet-9-timing
sceNetInet, sceNetApctl, sceNetResolver: Timing from hardware measurements
2026-10-28 21:06:11 +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ård 27ee45409c Merge pull request #22831 from hrydgard/aemu-relay-server
Built-in aemu_postoffice relay next to the built-in ad hoc server
2026-10-28 16:51:31 +01:00
Henrik RydgårdandClaude Opus 5.5 441f08c5c1 Lang: Remove the warning that the built-in server lacks a relay
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 14:48:47 +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 129e3b7312 test.py: Run the two-console tests, and give the ad hoc tests a server
The pair tests (net/adhoc/pair) run on two headless instances through
Tools/adhoc_multi.py and the host's built-in relay. They're all in
tests_next for now. The single-console ad hoc tests get the built-in
server and real-time speed, which they need to get past creating a
group, with a timeout to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 14:48:47 +01:00
Henrik RydgårdandClaude Opus 5.5 dfa22e5532 Build the built-in relay server in every build system
Add aemu_postoffice's relay server to the Visual Studio, UWP, legacy
Android and libretro builds, next to the client. In the Visual Studio
projects its objects get their own names, since Common already has
Log.cpp and the client's enet.c. aemu_postoffice now leaves out pthread.h
where it isn't used, which MSVC doesn't have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 14:48:47 +01:00
Henrik RydgårdandClaude Opus 5.5 643abd5eb5 adhoc_multi: Use the host's built-in relay with --relay
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:56:56 +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
Henrik RydgårdandClaude Opus 5.5 22d009c7bb CMake: Build the relay server with the current aemu_postoffice
The server now needs enet, common.cpp and snapshot.cpp, and builds as
C++17. It takes enet's implementation from the client library.

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ård 3f40ddc8db Merge pull request #22829 from hrydgard/inet-7-state
sceNetInet: TCP socket states and bind's port sharing
2026-10-28 13:40:49 +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ård 7da0be4f0a Merge pull request #22828 from hrydgard/inet-6-msg
sceNetInet: Recvmsg, Sendmsg rewritten, and sendto without an address
2026-10-28 13:07:47 +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ård edfeb7653b Merge pull request #22827 from hrydgard/inet-5-poll
sceNetInet: Poll and select as on hardware, and host sockets past FD_SETSIZE
2026-10-28 12:24:20 +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