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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
__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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>