kirk_engine.h and amctrl.h guard their declarations, but AES.h and SHA1.h
never did, and kirk_engine.h includes them from outside its own guard. So the
AES_* and SHA1* functions got C++ linkage in any C++ file that reached them
through there, and only linked for callers that happened to wrap the whole
header in an extern "C" of their own. Nothing had called AES_* from C++
before, so it stayed hidden until something did.
Guarding the two headers instead lets every caller include them plainly, and
the wrappers scattered around the tree come out. Both are pure declarations
over kirk_common.h's typedefs with no system headers behind them, so there's
nothing in there that shouldn't be wrapped.
kirk_engine.h also uses size_t without including anything that defines it,
which only held together because its includers happened to have it already.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
naettClose cleared res->request and then asked the backend to close the
response - but the request is what a backend needs to do that. On Windows it
owns the WinHTTP handles, so the close had nothing to work with, which is part
of why it did nothing at all. Backend first, then clear.
With that in place, the Windows close unhooks the status callback and shuts the
request handle, so a completion raised later can't write through the response
after it's freed. It stops short of a full cancel: a callback already running on
another thread isn't waited for, which needs the HANDLE_CLOSING handshake.
On Apple, invalidateAndCancel returns before the session has finished with its
delegate, so clear the delegate's pointer back to the response first and have
didReceiveData check it, the way didCompleteWithError already did.
Android is still the only backend that genuinely cancels and waits, so naett.h
now says a response should be complete before it's closed rather than leaving
that to be discovered.
getEnv called AttachCurrentThread and nothing detached. processRequest gets away
with it by detaching its own thread at the end, but naettPlatformInitRequest and
naettPlatformFreeRequest run on whatever thread the caller is using - and a
thread that exits while still attached takes the process down on Android, with
"Native thread exiting without having called DetachCurrentThread". They now
attach only when the thread isn't already, and detach on the way out.
pthread_create's result was ignored. Without a worker nothing ever sets
res->complete, so the caller polls naettComplete forever.
getOutputStream throws for anything from a refused connection onwards, and the
calls after it ran with that exception still pending, which most of JNI doesn't
allow. GetMethodID also returns NULL for a method it can't find, and calling
with a NULL jmethodID aborts the VM - so the call helpers check. Same for a
header whose value list is empty, which handed GetStringUTFChars a null.
Checked with the NDK's clang; the other backends were syntax-checked the same
way, against stub headers.
The worker reads a queued CURL* out of a pipe eight bytes at a time, tracking
how much it has so far - but it always read into the start of the buffer rather
than at that offset. A short read would have resumed mid-pointer and eventually
handed curl_multi_add_handle a spliced pointer. It never bit because a write
that size to a pipe is atomic, so reads are all-or-nothing, but the code is
written as though it isn't.
The write that queues the request was unchecked. If it ever failed, the request
was never run and never completed, and the caller sits in naettComplete
forever. It reports the failure now, and retries on EINTR.
curl wants an easy handle out of its multi before cleanup; that needs
curl_multi_remove_handle, which meant adding it - and curl_multi_cleanup, for
the init failure paths that leaked the multi handle and the pipe - to the dlopen
table.
workerRunning is written by the worker and read by the request path, so it's an
atomic rather than a plain int. And the read/write callbacks passed the body
callbacks' int return straight back to curl, which takes a size_t: a negative
came through as an enormous count rather than the error it was.
WinHttpQueryHeaders only writes the size it needs when it fails with
ERROR_INSUFFICIENT_BUFFER. Any other failure left bufSize at zero, so we
allocated nothing and unpackHeaders walked wcslen over it looking for the
double-null that terminates the list. Check the size, check the second query,
and allocate zeroed with room for a terminator.
winToUTF8, winFromUTF8 and wcsndup can all return NULL - on a failed conversion
or a failed allocation - and not one caller checked. packHeaders is the one that
mattered: its result goes straight into headers[0].
res->bytesLeft is a size_t counting down from what WinHTTP announced. If a read
ever returned more than that, it wrapped to an enormous count and the loop kept
reading.
createDelegate built its NSURLSessionDataDelegate with objc_allocateClassPair
and then sent it +alloc without ever calling objc_registerClassPair. The runtime
requires registration before a class pair can be used; everything up to then is
still being assembled. Register it, after the methods and the ivar go on.
The response header arrays were VLAs sized from the count the server sent, so a
response with enough headers walked the stack off the end, and one with no
headers at all declared zero-length VLAs, which is undefined by itself. Heap
now, and skipped when there's nothing to read.
The NSURLSession was also kept in the response without retaining it, while the
autorelease pool it came from is drained on the way out of the function. It only
survived because a session keeps itself alive while it has tasks running. Retain
it, release it when the response closes.
Finally, addMethod/addIvar signalled failure with assert alone, which is
compiled out in release - a delegate missing its methods would just never
receive data and the request would hang with nothing logged.
naettFree frees the method and url it strdup'd but never the user agent, which
stringSetter allocates exactly the same way. We set a user agent on every
request, so that leaked on every one of them, on all platforms.
On Linux, headerCallback strndup's each header line and only hands it to the
header list when it finds a colon - the status line and the blank line that ends
the block don't have one, so it leaked those every response, and again per hop
when following redirects.
defaultBodyWriter grew its capacity by doubling an int until the new data fit.
Both the length and the resulting capacity come from the response, so that's
signed overflow on a large one, and a negative capacity then reaches realloc as
a huge size_t. It also assigned the realloc result straight over the old
pointer, so a failed allocation lost the buffer and the memcpy below went
through NULL. Grow in int64_t, cap at INT_MAX, and report failure by returning
short - which is what every caller already checks for.
naett has had a complete libcurl backend all along; we just never built it,
so Linux ran with HTTPS_NOT_AVAILABLE. That means no homebrew store over
HTTPS, and RetroAchievements talking to plain http://retroachievements.org.
libcurl is loaded with dlopen rather than linked, the same way we handle the
Vulkan loader, so it stays a soft dependency: we need the curl headers at
build time, but a build made here still starts on a machine without libcurl
installed - it just reports HTTPS as unavailable, exactly like today. Distro
packagers get the behavior they'd expect either way, and certificate
validation comes free from the system CA store.
New net::HTTPSAvailable() answers "did that work", and SDLMain folds it into
SYSPROP_SUPPORTS_HTTPS, which everything downstream already degrades on.
Four fixes to the backend itself, all noted in ext/naett/README-ppsspp.md:
- panic() called exit(1) on a pipe or curl_multi_perform failure. Taking the
emulator down because a download failed isn't acceptable - the backend now
disables itself and requests complete with naettGenericError.
- CURLINFO_RESPONSE_CODE writes a long into res->code, which is an int. Eight
bytes into four, getting away with it only because the next field absorbs
the zeroes.
- curl_easy_setopt is varargs and wants a long for these options; int literals
and int variables are UB on LP64.
- naettPlatformCloseResponse called through a null function pointer when
libcurl was missing. Found by testing that path, which segfaulted.
CI needs libcurl4-openssl-dev (curl-dev on Alpine) or it would quietly keep
building without HTTPS.
naett has been a submodule pinned at v0.3.3; upstream has had no commits since
April 2024, and we want to carry local changes (next up: a libcurl-backed HTTPS
path for Linux). It's ~1500 lines of MIT C, smaller than several things we
already vendor, so bring it in-tree and drop the submodule.
Also drop the generated single-file amalgam (naett.c) that every build system
was compiling, and build src/*.c directly instead - otherwise the file you edit
isn't the file that gets compiled, which is a trap for anyone patching this.
example/ and testrig/ (a whole Android Studio project) are gone with it.
Two changes were needed to make the sources build on their own, both noted in
ext/naett/README-ppsspp.md along with the upstream commit:
- naett_internal.h now includes naett.h, which the amalgam pulled in first.
- naett_linux.c now includes stdio.h/stdlib.h. It calls exit/calloc/realloc/
free/fprintf without ever including either, and only got away with it because
naett_core.c sat above it in the concatenation.
No functional change - Linux still has HTTPS_NOT_AVAILABLE set, so it doesn't
build naett at all yet.
Rename naett to naett-lib
The RISC-V branch had its instruction-decode logic (including a locally
defined info struct) written directly inline in HandleFault(), unlike
the other three architectures which each delegate to a dedicated
AnalyzeLoadStore function in their disassembler file. Move it into
ext/riscv-disas.h/.cpp as RiscVAnalyzeLoadStore, matching the existing
X86AnalyzeMOV/Arm64AnalyzeLoadStore/ArmAnalyzeLoadStore pattern.
Common and basis_universal call zstd functions directly but never
declared the dependency after being split into their own
CMakeLists.txt files, relying on directory-scope include_directories()
that no longer reached them. Worked on Linux via system zstd.h, but
broke the Android NDK build which has no such fallback.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMv4XdM5dThs9Avr5FPXRv
gason, vma, cityhash, 7zip (ext/lzma-sdk), basis_universal, pugixml,
kirk (ext/libkirk), sfmt19937, xbrz, and xxhash were each defined with
a small add_library() block directly in the root CMakeLists.txt, even
though ext/CMakeLists.txt already exists and handles every other vendor
library (freetype, imgui, naett, discord-rpc, libchdr, zstd, miniupnp,
armips, glew, snappy, ...) via add_subdirectory(). Gave each one its
own ext/<name>/CMakeLists.txt to match that established pattern; xxhash
stays a loose file pair in ext/ (it never had its own directory) so its
add_library() lives directly in ext/CMakeLists.txt instead.
ext/libkirk/CMakeLists.txt already existed but was dead - nothing
add_subdirectory()'d it, and its source list was stale (missing
amctrl.c/.h, which the live inline definition in root had). Replaced
its contents with the current, correct list instead of leaving two
diverging definitions around.
Dropped several target_include_directories()/include_directories()
calls that came along with these (e.g. cityhash, kirk, xbrz, xxhash,
and the lzma-sdk one for 7zip): traced their actual consumers and found
each library's own sources resolve their sibling headers via the
default same-directory quote-include rule, and every external consumer
already uses the full "ext/<name>/..." path resolved through the global
root include - so these were dead weight regardless of position.
Replaced two single-value alias variables (LIB7ZIP_LIBRARY, always
"7zip"; BASISU_LIBRARIES, always "basis_universal") with their target
names directly in Common/CMakeLists.txt, rather than trying to carry
them across the new add_subdirectory boundary - plain set() variables
don't propagate back up out of a child scope without PARENT_SCOPE, so
keeping them as-was would have silently broken Common's link line.
Verified with a fully clean rebuild (removed build/ entirely), a
HEADLESS=ON UNITTEST=ON build, and a LIBRETRO=ON build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSNaZnHCjmryS3ziVN9gZU
- Make GetPdpStat behave more like in P2P mode
- Now fetches accumulated ready to be received amount of data
instead of only the immediate to be received Pdp block
- Make GetPtpStat behave more like in P2P mode
- Now fetches accumulated ready to be received amount of data
instead of the size of the next server send block
- Make PtpRecv behave more like in P2P mode
- Fix block like behavior where whole server send block has
to be consumed before getting data from the next send block
- Fix block like bahavior where sender sends in small PtpSend
calls while receiver receive with less frequent but bigger
buffer PtpRecv causes bloat(and eventually server kick)
on relay
- bump aemu_postoffice
- timeout TCP connection attempts at 5s to not block the library user too much
- set send and receive buffer sizes after connection to avoid some blocking cases on some platforms with small defaults (windows?)
- pop OSD message when relay connection fails
Improve nonblock relay recv operation responsiveness, it helped a
few games on PSP get back to full speed during multiplayer, might
help severely underpowered devices on PPSSPP as well.