* ci: let an autotest be skipped
Some VitaAutotests tests check timing, like how long a short delay takes,
and on Vita3K their outcome depends on how busy the CI runner is, so they
can pass on one run and fail on the next. The expectations file can now
mark such a test skip, which leaves it out of the run.
* ci: run the latest VitaAutotests
Move AUTOTESTS_REF to the latest VitaAutotests commit, which adds 28
scheduler and sync tests:
- stagnant_mask passes.
- delay_precision, timeout_threshold and timer_period check timing, so
they're skipped.
- standby_handover_order is skipped too. Vita3K runs its threads at once,
so the order it checks comes down to a race of about 1 ms.
- The other 24 fail, mostly because threads ignore their core and
priority, and atomics because no sceKernelAtomic call is implemented.
* kernel: implement the atomic calls
Every sceKernelAtomic call was a stub that returned 0 and left memory
alone. They now work on the guest value with host atomics, the way the
atomics test shows a Vita does: the AndGet calls return the new value,
the GetAnd calls, CompareAndSet and DecIfPositive the old one. AddUnless
stores its last argument instead once the sum reaches it, comparing as
signed and wrapping only at 32 and 64 bits, and DecIfPositive takes 0
down to -1 at 8 and 16 bits.
* kernel: report an unlock of a deleted lightweight mutex as not owned
A delete clears the owner in a lightweight mutex's work area, so a Vita
fails a later unlock with LW_MUTEX_NOT_OWNED, while Vita3K said the
mutex was unknown. That's what cond_mutex_delete sees when a condition
wait ends because its mutex was deleted.
* kernel: return from a ForVM suspend once the thread has stopped
sceKernelSuspendThreadForVM returned as soon as it asked the thread to
stop, so a context read right after it could race with the thread still
running guest code. A thread woken while suspended also went back to
its code and wrote its result before it stopped. On a Vita the suspend
only returns once the thread has stopped, and a woken thread reports
SUSPENDED and stays in its wait call until the resume, with the context
it had while it waited. Now the suspend waits for the thread to stop,
and a woken thread stays in its wait call as suspended. A wait whose
timeout passed while suspended leaves its queue first, so a later wake
can't take the place of the timeout as it can't on a Vita, and stops on
its way back to its code, which a context read waits for.
Found in the review of #4240
(see https://github.com/Vita3K/Vita3K/pull/4240#discussion_r4273124329).
* kernel: keep the ForVM and debugger suspends apart
The ForVM suspend shared suspend_requested with the debugger's, so a
ForVM resume let go of a thread that the debugger or
sceKernelWaitExceptionForMono had suspended, and the debugger's resume
let go of a thread the ForVM count held. Now the ForVM suspend only
uses its count, and the thread stays suspended while either holds it.
The debugger's resume returns whether the thread runs again, so the
gdb stub doesn't wait for a thread the ForVM count still holds.
Found in the review of #4240
(see https://github.com/Vita3K/Vita3K/pull/4240#discussion_r4273124415).
* ci: run the latest VitaAutotests
Move AUTOTESTS_REF to the latest VitaAutotests commit. It adds
thread_suspend_for_vm_stops, which checks that a ForVM suspend returns
once the thread has stopped, a case to thread_context_vfp for a thread
woken while suspended, a case to thread_status_matrix for a signal
after a suspended wait timed out, cases to msgpipe_vectors for long
vector lists, their total size and empty pieces, and
msgpipe_failed_sizes for the sizes failed message pipe calls write. The
first two pass, and msgpipe_vectors and msgpipe_failed_sizes still
fail, since Vita3K doesn't have the vector calls.
* kernel: fail a CB delay of 0 like a Vita
sceKernelDelayThreadCB(0) returned 0 and ran the pending callbacks. On
a Vita it fails with SCE_KERNEL_ERROR_INVALID_ARGUMENT, like
sceKernelDelayThread(0), and runs no callbacks.
* kernel: run a CB wait's pending callbacks only once its object is found
Every CB wait ran the pending callbacks first, so a wait on an unknown
UID ran them before it failed. On a Vita such a wait fails at once and
runs no callbacks, while one that finds its object runs them even when
it ends at once or has a timeout of 0. The callbacks now run right
before the wait, after the lookup and the checks of the export.
* kernel: take a free lightweight mutex without running callbacks
sceKernelLockLwMutexCB ran the pending callbacks even when it took a
lightweight mutex no other thread held. A Vita takes such a mutex in
user code, so it runs no callbacks. It now only runs them, and waits,
when another thread holds the mutex.
* kernel: report a size of 0 when a message pipe wait fails
A message pipe send or receive whose wait timed out, was canceled or
ended with a delete left its size untouched. On a Vita it writes the
size it moved, which is 0 here, since Vita3K only moves data once a
wait ends. A call that fails on its arguments still writes nothing.
* kernel: let a message pipe transfer move a list of pieces
MsgPipe::send and MsgPipe::receive took one host buffer. They now take
a list of pieces in guest memory and copy each one straight to or from
the pipe's buffer, so the vector calls won't need to copy their pieces
into a buffer of their own first. A plain call passes one piece.
Nothing else changes.
* kernel: implement the message pipe vector calls
sceKernelSendMsgPipeVector, sceKernelReceiveMsgPipeVector, their CB
forms and their try forms returned an unimplemented error. They now
check the guest's list of pieces and hand it to the pipe as it is,
which sends the pieces as one message and receives into them in order.
A list has no limit on its number of pieces, where a Vita takes at
least 8192. Like on a Vita, every piece needs a buffer even when it is
empty, only the size of all the pieces together is checked against the
pipe, and a list of only empty pieces ends at once in both modes,
unlike a plain ASAP call of 0 bytes.
* ci: read each autotest's output from the file it writes
The runner took a test's output from the sceClibPrintf lines in
Vita3K's log, but the log collapses repeated lines into a "Skipped N
duplicate messages.." line, which then counts as guest output, and a
test that prints the same line twice loses the second one. Every test
also writes each line to its output file in the emulated Vita, so the
runner now reads that, and the log only when there is no file.
* ci: run the latest VitaAutotests
Move AUTOTESTS_REF to the latest VitaAutotests commit, which adds 57
more tests on thread scheduling, wait queues, sync objects, threads,
timers, the ForVM thread context, registers after a syscall, the stack
free size, stat modes and save data slots, and give each of them its
outcome on Vita3K in expectations.txt. Six of them pass, the others
fail on what each line says, and thread_status_matrix leaves Vita3K
hung after the app exits.
* kernel: keep a thread's current CPU state after a nested run loop
run_loop cleared the thread-local current CPU state on exit, so after
a callback or thread event handler ran in a nested run_loop, the rest
of the thread ran without one, and code that looks it up, like the
kubridge fault handler, found nothing. The guard now puts back the
state from before.
* kernel: run a thread's end handler however the thread ends
The thread end event handler only ran after sceKernelExitThread or
sceKernelExitDeleteThread, and even then its nested run loop saw the
exit or delete request and returned before running it. A Vita runs it
for a thread that returns from its entry too. It now runs on every
end, with the requests set aside while it runs and kept, together with
any new one like a teardown's, after it.
* kernel: start thread event handlers with the VFP state of a Vita
A thread event handler ran with the thread's FPSCR and VFP exception
setting. On a Vita it starts like a callback, with FPSCR 0x03000000
and a setting of 0, sees the thread's TPIDRURW, and the thread gets
its own values back after it.
* kernel: lay out a thread's TLS stack and guard slots like a Vita
TLS slot 2 held the low end of a thread's stack and slot 3 the high
end, where a Vita has the high end in slot 2 and the low end in slot
3, as their names say. Slots 6 and 7, which Vita3K names after libc's
longjmp value and stack check guard, were 0, where on a Vita every
thread starts with a random value in each. They now hold the values
one thread had on a PS Vita TV, the same for every thread.
* kernel: report the Vita's wait UID for a thread signal wait
A thread waiting for a thread signal reported wait UID 0, where on a
Vita it reports 0x00010221 in a plain wait and in one with callbacks,
a value that isn't a UID.
* kernel: check the ForVM calls like a Vita
sceKernelSuspendThreadForVM and sceKernelResumeThreadForVM used the
debugger's suspend, which does nothing for a thread that isn't running,
so the resume of a waiting thread marked it running while it still
waited, and sceKernelGetThreadContextForVM read any thread at any time.
On a Vita these calls need a target with the attribute 0x04000000, the
context read needs it suspended, the suspend and resume calls return a
count, and the read checks the sizes of its structures first. The ForVM
suspend now has a count of its own that stops the thread before it next
runs guest code, and an emulator resume leaves it in place. The context
shows user mode. For a thread that waits, so is inside a syscall, it
shows the kernel's FPSCR and only d8 to d15, with every other d register
reading the NaN 0x7FF8DEAD7F80DEAD, like on a Vita. A thread stopped in
its own code shows its own VFP state, also like on a Vita.
* ci: run the latest VitaAutotests
Move AUTOTESTS_REF to the VitaAutotests commit that adds 47 more tests,
and give each of them its outcome on Vita3K in expectations.txt. Four
of them pass, the others fail, crash or time out on what each line
says. The runner now finds tests by their .expected files, since the
suite also has the source of a module under tests/.
* cpu: handle TPIDRURW
User code may read and write TPIDRURW with MRC and MCR, but Vita3K
only handled TPIDRURO, so the JIT asserted on the first access.
TPIDRURW now has its own value per thread. It is 0 when a thread
starts, also when it starts again, and the thread gets its own value
back after a callback. _sceKernelGetThreadContextForVM reports it
instead of TPIDRURO.
* kernel: start threads and callbacks with the VFP state of a Vita
Threads started with FPSCR 0, where a Vita starts every thread with
0x03000000, round to nearest with flush-to-zero and default NaN. A
callback ran with the thread's FPSCR and VFP exception setting, where
on a Vita it starts with those of a new thread and the thread gets its
own back after it. The other guest calls Vita3K makes from HLE code,
like library callbacks, keep the caller's state as before.
* kernel: fix thread signal timeouts and targets
sceKernelWaitSignal ignored its timeout and waited until a signal came,
and sceKernelSendSignal used the target without checking it, so a
signal to a UID that isn't a thread crashed Vita3K. A wait now ends
with WAIT_TIMEOUT when its timeout passes, writes back the time left
when it waited, and leaves the timeout alone when a signal was already
there. A signal to UID 0 fails with ILLEGAL_THREAD_ID and one to what
isn't a thread with UNKNOWN_THREAD_ID, as on a Vita.
* kernel: check thread attributes and report them in thread info
sceKernelCreateThread took any attributes and thread info always
reported 0, and sceKernelChangeCurrentThreadAttr was unimplemented. A
Vita creates threads only with the attributes 0x2000, 0x01000000,
0x04000000 and 0x80000000, fails with ILLEGAL_ATTR for any other, and
reports the attributes given plus 0x80000000, which every user thread
has. sceKernelChangeCurrentThreadAttr takes only 0x01000000 and
0x80000000, and thread info doesn't show a change to either.
* kernel: leave TLS slot 0 at 0 for a new thread
Vita3K wrote 1 to TLS slot 0 as a stub process ID that nothing reads,
where a new thread on a Vita finds 0.
* kernel: report pending callbacks and clear taken callback arguments
Thread info always reported 0 in fNotifyCallback, where a Vita reports
1 for a waiting thread with a notified callback that hasn't run yet. A
callback that runs takes its notifications, and its info still showed
the last argument, where on a Vita it reads 0 like the count.
Each thread now counts its callbacks with notifications they haven't
run for, in place of the flag that woke its waits, so a callback that
was cancelled or deleted no longer counts as notified.
* kernel: delete a thread's callbacks when it ends
The callbacks a thread created outlived it, so another thread could
still notify and run them. On a Vita they go away when the thread ends,
whether it returns, exits or deletes itself, and their pending
notifications never run.
* kernel: wake one waiter when signaling a condition
Signaling a condition woke every waiting thread, like signaling all of
them does. It now wakes only the first thread in the queue, as a Vita
does.
* kernel: release the mutexes of a thread that ends
A thread that ended while holding a mutex kept it forever, and its
waiters waited until they timed out. On a Vita the mutex is unlocked
when its owner ends and goes to the next waiter, so do that.
* kernel: write back the time left after a message pipe wait
Message pipe sends and receives that waited never wrote the time left
back to their timeout, so one that timed out still had all of it. They
now write it back like the other waits.
* kernel: keep a thread's exit status like a Vita does
A thread's exit status is now DORMANT until it first starts,
NOT_DORMANT while it runs, and then the value it returned or exited
with. The thread info reports it for every thread, not just ended ones,
a wait for the end of a thread that never started gets it at once, and
getting the exit status of a thread that never started fails with
DORMANT. A callback's result no longer replaces the exit status, and
sceKernelGetThreadExitStatus calls the thread manager's version instead
of keeping its own copy.
* kernel: fix the errors for starting a running thread or deleting oneself
Starting a thread that isn't dormant now fails with NOT_DORMANT instead
of RUNNING, and a thread deleting itself fails with ILLEGAL_THREAD_ID
instead of NOT_DORMANT, as on a Vita.
* kernel: report CPU affinity masks as they were set
A thread created with an affinity mask of 0 reported every user core,
while on a Vita it reports 0, and so does changing its affinity. Getting
the affinity now returns the mask as it was set. A main thread whose
process doesn't give one gets every user core, which is what a Vita
reports for it. sceKernelGetThreadCpuAffinityMask2 still reports every
user core for a mask of 0.
* kernel: implement sceKernelGetThreadStackFreeSize
It was unimplemented. It now returns the whole stack for a dormant
thread, and the space below the stack pointer for one that started, or
fails with UNKNOWN_THREAD_ID for a UID that isn't a thread.
* kernel: fix the errors for thread affinity and exit status arguments
Creating a thread with an affinity mask that has cores games can't use
now fails with ILLEGAL_CPU_AFFINITY_MASK instead of
INVALID_CPU_AFFINITY, and getting an exit status into a NULL pointer
fails with ILLEGAL_ADDR, as on a Vita.
* kernel: copy as much of a thread's info as the game asks for
sceKernelGetThreadInfo failed for any size but the struct's. Like the
other info getters, it now copies the start of the struct, as many bytes
as the size the game sets, and fails with NO_MEMORY for a size past the
struct. The fields it doesn't fill in are now 0.
* kernel: count timer time like a Vita does
A timer kept only the clock time it was last started or stopped at, so
a new timer wasn't at 0, a stopped one went on counting and setting the
time didn't move the count. It now keeps a count that starts at 0,
stops while the timer is stopped and goes on from there when it starts
again, and setting the time replaces it. Stopping a timer returns 1
when it stopped it and 0 when it was stopped already, the other way
around before. The base is now the process time the count runs from,
and sceKernelSetTimerTimeWide returns the whole 64-bit old time.
* kernel: give a failed timer poll no bits
A timer poll or wait that didn't get the event still got the timer bit
as its result. It now gets 0, as on a Vita, and only a take that gets
the event gets the timer bit.
* kernel: let a waiting writer go first on a reader/writer lock
Unlocking a reader/writer lock handed it to the waiters in queue order,
so readers that waited before a writer went first, and new readers got
in while a writer waited. Like on a Vita, an unlock now gives the lock
to the first waiting writer, or to every waiting reader if no writer
waits, and a read lock fails or waits while a writer waits.
* kernel: order message pipe waiters by the pipe's own attributes
Message pipes ordered their receivers by the thread priority attribute
the other objects use, which creating a message pipe refuses, and their
senders always in FIFO order. Receivers now queue by priority with
attribute 0x2, as on a Vita, and senders with 0x1.
* kernel: report an event flag's initial pattern in its info
The event flag info gave the current pattern as the initial one too.
It now gives the pattern the event flag was created with.
* kernel: round message pipe buffers up to 4 KiB
A message pipe got a buffer of exactly the size it was created with.
On a Vita the buffer is rounded up to a multiple of 4 KiB, which its
info reports, so a 256 byte pipe holds 4 KiB.
* kernel: implement sceKernelGetMsgPipeInfo
It was unimplemented. It now reports a message pipe's name, attributes,
buffer and free sizes and how many threads wait to send or receive,
like the other info getters.
* kernel: report a callback's notification count in its info
The callback info left the notification count at 0. It now gives how
many times the callback was notified since it last ran.
* kernel: fill lightweight mutex and condition work areas like a Vita
Games can read these work areas, so they now hold what a Vita leaves in
them. Creating a lightweight mutex or condition clears its work area
first, and unlocking a lightweight mutex writes its new owner and count
to it. A lightweight condition's work area holds its mutex's UID after
its own, and both become 0xFFFFFFFF when it is deleted.
sceKernelDeleteLwCond now calls the thread manager's version instead of
keeping its own copy.
* ci: run the latest VitaAutotests
Move AUTOTESTS_REF to the VitaAutotests commit that adds two tests, a
thread that holds a read lock reading again while a writer waits, which
passes, and changing the priority of a waiting thread, which still
fails.
* ci: run the latest VitaAutotests
Move AUTOTESTS_REF to the VitaAutotests commit that adds 34 more tests,
and give each of them its outcome on Vita3K in expectations.txt. Five
of them pass, the others fail or crash on what each line says. The
names column is wider for the longer test names.
* kernel: refuse creating a lightweight mutex with no work area
Creating a lightweight mutex with a NULL work area wrote through the
NULL pointer, which crashes Vita3K on some hosts. It now fails with
ILLEGAL_ADDR, as on a Vita.
- tools/autotests/run.py runs the suite headless on a Vita3K build,
launching each test with its own --launch-uri, and compares each
test's output with its expected file.
- tools/autotests/expectations.txt gives every test its expected
outcome on Vita3K: pass, fail for other output, crash or timeout,
with what each test that doesn't pass catches. Any other outcome
fails the run, so a fix changes its line to pass, and a new crash or
hang can't hide behind an expected failure.
- The Linux x64 build job builds the vpk at AUTOTESTS_REF and runs the
suite on the Vita3K it just built, headless and with no firmware.
- The suite uses the Vulkan renderer on lavapipe, Mesa's CPU Vulkan
driver, so the job needs no GPU. The Vulkan loader only sees
lavapipe, so a runner that has a GPU gets the same results.
- Pin the jobs that build with the system Clang (Linux desktop and CodeQL Analyze) to `ubuntu-26.04` and `ubuntu-26.04-arm` instead of `ubuntu-latest`.
`ubuntu-latest` [moves to 26.04 soon anyway](https://github.blog/changelog/2026-09-17-ubuntu-26-generally-available-and-latest-migration/#ubuntu-latest-is-migrating-to-ubuntu-26-04), and pinning means a future label change can't silently change the build.
- 26.04 ships a newer Clang that supports newer C++ features such as `std::expected` with `libstdc++`. Clang 18 on 24.04 doesn't.
- Drop the `mhier/libboost-latest` PPA. It has no 26.04 build, which made apt update fail, and 26.04 ships Boost 1.83 itself.
- Install `libgstreamer-plugins-extra1.0-0` instead of `libgstreamer-plugins-good1.0-0`, which GStreamer 1.28 merged into it.
Add Windows ARM64 build.
- Simplify vcpkg installation with `--triplet=<triplet>`.
- Remove useless `if` in the job `Compute git short SHA`.
- Rename macOS/Windows x86_64 artifacts.
- Disable Discord Rich Presence on Windows ARM64.
- Add flags to disable inclusion of timestamps in PCH files with clang-cl.
- Enable Visual Studio for ARM64 in CMake presets.
- Fix the Windows update script still using '0.2.0' when parsing version.
- external: Update ffmpeg-core
- Restore/Update Linux runner to the latest version.
- Remove the commands for Linux ARM64 that are no longer needed now.
- Simplify steps to get commit information.
- Avoid passing secrets within the command line.
- Change URLs to allow steps be used in forks without many changes.
- Merge Linux zip/appimage builds into single job (8 → 5 jobs)
- Replace matrix.config with global env.BUILD_CONFIG
- Simplify matrix to include-only (remove version/exclude)
- Consolidate create-release job (2 → 1 job)
- Download ghr once instead of twice
- Process artifacts for master/store in single loop
- Reorder steps for logical flow
- Add job names for cleaner UI display
- Update build_updater.sh for multi-arch compatibility
- Update vita3k_update.cpp for Linux ARM64 support
- Add CI workflows for ARM64 zip and AppImage builds
* external: Update VulkanMemoryAllocator-Hpp to version v3.2.1
* external: Update capstone to version 5.0.6
* external: Update cubeb
* external: Update fmt to version 11.1.4
* external: Update googletest to version v1.16.0
* external: Update imgui to version v1.91.3
* external: Update imgui_club
* external: Update libfat16
* external: Update nativefiledialog-extended to version v1.2.1
* external: Update pugixml to version v1.15
* external: Update sdl to version 2.30.11
* external: Update spdlog to version v1.15.2
* external: Update stb
* external: Update vita-toolchain
* external: Update xxHash to version v0.8.3
The `ubuntu-latest` CI was just changed today to use ubuntu 24.04. This had the effect of breaking normal linux operation due to it being built with a newer version of libc than we previously targeted.