Both landed as our own commits and both owe him more than they said.
The on-disk pipeline cache header -- length, version, vendorID, deviceID and
pipelineCacheUUID -- is his design from EmuCoreC 47220b153, used as-is, including
using the UUID as the invalidation key so a driver swap rebuilds rather than
feeding a driver a blob it cannot read. What was missing there was the wiring:
nothing passed the cache to vkCreate*Pipelines, so it saved an empty file. That
part, and sharing it with the shader interpreter, are ours.
Splitting Adreno and Turnip out to a 64-wide group size is his too (b9f0f3631).
Our comment had held all of mobile at 32 on the claim that Mali was also 64-wide,
which is wrong -- Valhall warps are 16 lanes and Bifrost 4-8. His split was the
better call and the reason ours changed.
Tales of Xillia 2 (BLUS31397) polls this syscall in a loop: 20,664 calls in a 19
minute session, 5,123 of them inside a single second. It was logged at warning, so
each one wrote a line to external storage on Android -- the same shape as the SPU
recompiler diagnostics, and enough on its own to stop frames landing.
Reported as issue #76, an intermittent hang every few minutes that ends with
"Game has stopped responding - it is no longer drawing frames". That message comes
from the 30 second frame-stall watchdog, which is what a saturated log writer
looks like from the RSX thread.
Moved to trace. Nothing is lost: a container's size is fixed at creation, and
sys_memory_container_create and _destroy both still log at warning, so the events
that carry information are still recorded. sys_memory_get_user_memory_size in the
same file already has the equivalent treatment upstream -- it only logs when the
values it reports actually change.
Not confirmed as the cause of #76 yet; the reporter has not attached a log, and 19
minutes of play here did not reproduce the hang. It is the largest log source this
title produces by a wide margin.
Booting a game while another was still tearing down left the app frozen on the
last frame of the previous game.
The boot gate asked Emu.IsStopped(), whose default overload is
m_state <= system_state::stopping
so it answers true while the previous VM is still stopping -- and while it is
loading. Kill() only signals the threads and hands the joining to a detached
"Emulation Join Thread", so the state reaches stopping at once and the guard read
that as stopped. The entire wait block was skipped precisely when it was needed,
which is why no "previous VM still running" line was ever logged.
On device: Stopping emulator at 0:10:14, BootGame at 0:10:17, and at 0:10:31 the
join thread was still waiting on PPU[0x1000004] "SPU Interrupt Thread0" -- 17.4
seconds -- with seven SPUs parked in EXIT|w|G-PAUSE and one PPU thread spinning at
100%.
Use the IsStopped(true) overload, which requires system_state::stopped and is
reached only once that join thread has finished. The ten second bound and the
boot-anyway fallback are unchanged, so a teardown that genuinely hangs still gets
reported rather than freezing the UI.
Only the three checks in _rpcsx_boot are changed. Other IsStopped() callers here
want "not running" and the loose overload is right for them.
This does not fix why that interrupt thread fails to exit, which is still open. It
stops a slow teardown from becoming a boot into a half-destroyed VM.
cpu_on_stop() is a teardown hook and nothing enforces that it runs a single time.
A PPU thread that re-enters the stop path without exiting reports again on every
pass: one thread ("SPU Interrupt Thread2") was producing "PPU thread perf stats
are not available." roughly every 10 microseconds, near 100,000 lines a second.
That is survivable on a desktop. On Android the log goes to external storage, so
it pins the log writer and drags down the shutdown it is describing -- the
emulator logged "Stopping emulator..." and never reached "All threads have been
stopped", leaving the next boot stuck on the last frame of the previous game.
Guard the reporting with a flag so it happens once per thread. The flag is
deliberately left out of serialization: it describes this run's reporting, not
guest state.
This does not address why the thread re-enters the stop path, which is a separate
question -- it stops that from being an emulator-wide stall while it is open.
The client thread's inner loop breaks out to the outer sem_rpcn.acquire() for
every state except one: connected and authentified with no game running. The
only blocking wait sits inside the `authentified && !Emu.IsStopped()` branch, so
that case fell through to `while (true)` with nothing to wait on and span a full
core for as long as the user stayed signed in outside a game -- at the menu,
between titles, and throughout shutdown.
Measured on a Retroid Pocket 6 sitting at the library: `RPCN Client` at 100%,
utime 21964 against stime 58, so a userspace spin rather than a syscall storm.
Wait in the fall-through case instead. Breaking out to the outer semaphore would
also stop the spin but nothing releases it when a game starts, so the pings would
never resume.
Only reachable once RPCN actually authenticates, which is why it survived
upstream: signing in is new on Android.
A 15 minute Prototype session wrote 56,881 log lines, and 49,644 of them came from
the SPU recompiler -- peaking at 2,473 lines in a single second, written to
/sdcard. The bursts land exactly when a game is already stalling to compile new
blocks, which is the worst possible moment to add synchronous file writes, and
they stop when compilation finishes. That matches the reported symptom: seconds of
lockup that recover on their own.
Every one of these is per-block or per-instruction:
8963 New SPU block compiled successfully was success
7833 Precompiling fallthrough was notice
4337 Precompiling filler space was notice
3449 SPU block is a loop was notice
2418 MFC_EAH / MFC_Cmd not constant was warning, per INSTRUCTION
1516 Trampoline simplified was error, and is routine
843 SPU Block Dump was notice, and is multi-line
696 GETLLAR pattern entry point was notice
~1400 PUTLLC16 / pattern breakage family was notice and success
Upstream can afford these: a desktop has a fast disk and nobody is writing to
external storage. Demoted to trace, so they stay available by raising the SPU
channel and cost nothing during normal play.
The genuine faults keep their level -- MFC_Cmd invalid size and unknown command
are still errors, and they are rare.
This does not claim compilation is free. It removes the logging so what remains
can be measured, which is not possible while the instrument is this loud.
Adreno waves are 64 lanes, and a workgroup narrower than the wave does not pack
together with its neighbours -- it occupies a whole wave and masks the surplus
lanes off. At 32 that idled half of every wave on every dispatch, and these
kernels run on every texture upload of a kilobyte or more, plus every deswizzle
and detile.
Nothing argues the other way here. The kernels carry no shared memory and no
barriers, so group size is a scheduling hint with no LDS or synchronisation
cost to trade against.
Adreno and Turnip only. The comment this replaces claimed Mali was also 64-wide
and used that to justify holding everything at 32; that is wrong -- Valhall
warps are 16 lanes and Bifrost 4-8, so 32 already spans several of them and
there is no half-empty wave to reclaim. Xclipse is RDNA-derived and prefers
wave32 for compute. Both stay where they were.
Group size is baked into the generated GLSL, so this invalidates shader and
pipeline caches once on first launch after the update.
The mobile branch of the per-vendor group size table picks 32 by falling
through to the NVIDIA case. Adreno and Mali both run 64-wide waves, so 32
plausibly leaves half of each wave idle -- but that is reasoning, not a
measurement, and guessing wrong here costs performance silently.
Read ARMSX3_CS_GROUP_SIZE, which driver_env.txt already plumbs through
setenv(), so both candidates can be compared on one build without a settings
field or a second APK. Powers of two only, clamped to maxComputeWorkGroupSize
and maxComputeWorkGroupInvocations, because an over-large local_size_x fails
shader compilation rather than validation. The default is unchanged; this only
makes the question answerable.
Every vkCreate*Pipelines call passed VK_NULL_HANDLE for the pipeline cache, so
the driver re-did the whole of its own compilation work for every pipeline, in
every run. On mobile that work is a visible stall the first time each pipeline
is seen -- and it was being thrown away at every shutdown.
Give render_device a VkPipelineCache seeded from <cache>/vk_pipeline_cache.bin
and written back at teardown, and hand it to both pipeline creation calls. The
file is keyed on vendorID, deviceID and pipelineCacheUUID, so a driver update
or an adrenotools driver swap rejects the old blob and rebuilds rather than
feeding a driver a cache it cannot read. Oversized blobs are dropped instead of
being allowed to grow without bound -- the cache is shared by every title, so
one cold run is the cheaper failure.
This is orthogonal to the RSX shader cache: that one remembers WHICH pipelines
a title needs, this one makes each one cheap to create.
The shader interpreter was opening a private cache and destroying it on the way
out, which discarded exactly the expensive ubershader compiles. It now borrows
the device's. Teardown order already guarantees the pipe compiler threads are
joined before the device is destroyed, so the readback needs no extra locking.
vkGetPipelineCacheData was missing from the generated Android dispatch table;
regenerated with it, no other entry point changed.
Seventeen commits. The substantial one is kd-11's ROP_OUTPUT_REMAP series
across rsx/fp, glsl and both backends, which ARMSX3 did not have at all.
Two conflicts.
nv4097.cpp: upstream added the ROP remap plumbing to the format-change checks,
we have profiler instrumentation and an ARM64 observe() on the two hot FIFO
reads. Different parts of the same file, so upstream's version is the base and
ours is re-applied on top. The g_xform_const_words increment is included
deliberately: the profiler reports average batch size as words/calls, so
dropping it would have printed 0 rather than nothing, which is worse than an
absent stat.
ISO.cpp: took upstream's magic-read check. It is a real fix --
`!file.read_at(...) == 5` parses as `(!x) == 5`, which is false for every x, so
the guard never fired and a short read left `magic` uninitialised. Our reverted
reader has no check there at all, and read_at returns a byte count in this
version too, so the corrected form applies cleanly.
This does NOT undo the ISO reader revert. The refactor that broke reading for
some users is still reverted; only the one-line magic check comes across.
"Server error 1" is ErrorType::Malformed, and it was our fault rather than the
server's: two buttons posted queries with a required field blank, and the
server rejects the whole query rather than naming the field.
Reset password sends the account's email so the server can mail a token, but
the email box was only rendered while creating an account. Outside that mode it
sent an empty address every time. The box is always shown now.
Resend token sends the password, and Save deliberately clears that box, so
anyone who saved before pressing it sent an empty one. Having typed it a moment
ago is not the same as it being in the field.
Both are checked before sending now, along with account creation, and the
message names the field to fill in rather than failing at the server.
The error text is better too. Malformed, Invalid and the unknown case were
surfacing as a number with no way to act on it. Malformed now says outright
that it is a bug and asks which button was pressed, because if it appears again
the guards above have missed a path.
Not fixed here: sign-in reporting Invalid Password for credentials that work on
desktop. That is a distinct server code rather than a catch-all, and the cause
is not yet known -- desktop stores the password exactly as this does, so it is
not a hashing difference.
The ISO reader revert is confirmed working by the reporter, so this ships it.
Upload signing is now requested explicitly with -Parmsx3.uploadSigning rather
than used whenever keystore.properties happens to exist. Once that file was
created, every release build silently started signing with the upload key, and
an APK signed differently from the one already installed cannot be installed
over it -- so a sideload build becomes something testers cannot install, and
Android's error does not mention signatures.
It was being worked around by hiding keystore.properties by hand before each
build, which is the kind of step that gets forgotten exactly once and then
wastes a tester's evening. build-play-aab.sh passes the flag; nothing else
does, and asking for it without the keystore present is now an error rather
than a silent fallback.
The upstream merge brought a six-commit ISO series that regressed reading on
some images. A tester's ISOs that loaded on 0.9.2 now fail at the very first
header read:
ISO: init: Failed to read region information (region_count=0)
ISO: iso_archive: Corrupt ISO file: Decryption failed
Both of his games fail identically, so it is not one bad dump, and other
people's ISOs still load, so it depends on the image format -- most likely the
3k3y path, which has its own watermark detection and key extraction at 0xF70
and 0xF80, separate from the plain one.
The reader is reverted to its 0.9.2 state. The change implicated is the region
count read moving from char_arr_BE_to_uint(sec0_sec1.data()) to
read_from_ptr<be_t<u32>>(sec0_sec1), alongside decrypt_data moving from raw
pointers to std::span, but this is not a targeted fix: it restores code that
demonstrably worked rather than guessing which line of the refactor is wrong.
is_valid() is kept, because System.cpp calls it and it is one line that has
nothing to do with the failure.
What this gives up is real: the series contained out-of-bounds write and
overflow hardening for ISO parsing. A reader that cannot open a user's games
is the worse of the two, and the hardening should come back with a version of
the refactor that works. The path validation from the same series that lives
outside these two files is untouched.
Unverified against the actual failure -- the ISOs here all load either way, so
this could not be reproduced locally. It is verified not to break what already
worked.
Builds were reporting whichever commit cmake last configured against, not the
one being built. rpcs3/git-version.h is generated at CONFIGURE time, and
build-variants.sh deliberately skips reconfiguring an already-correct build
dir because re-running cmake regenerates LLVM's headers and costs a full
rebuild. So the stamp froze, and every build after that lied about itself.
The gap was wide. HEAD is 20120-da26a455; the header on disk still said
19985-91952ae4, a commit from before 0.9.2.
This is not cosmetic. A tester running 0.9.3 reported that older commit in
their log, which sent an investigation hunting for a regression among upstream
ISO changes their build did not contain, and very nearly had a fix built and
sent for a version they were not running. A build that misreports itself makes
every report from it ambiguous.
stamp-git-version.sh writes the header from HEAD and is called by both build
entry points before anything compiles. It only writes when the contents differ,
so an unchanged HEAD does not force a rebuild, and ninja rebuilds just the
translation units that include it.
The script bundled whatever core happened to be sitting in jniLibs, and
build-variants.sh overwrites that path once per variant, so the file left there
is simply whichever ran last. Running the variants and then rebuilding the
bundle would have shipped an armv8.2 core inside a minSdk 30 bundle:
installable on devices that cannot execute it, failing at dlopen, with nothing
in the failure to point at the cause.
Play serves one bundle to every device, so the ISA floor has to be the lowest
ARMSX3 supports. The legacy core is now stripped into place by this script
every time, and it refuses to run if that core has not been built.
The bundle already shipped was correct -- the legacy core was staged by hand
before it was built -- but only by hand, which is the part worth removing.
The bundle verifier reported libarmsx3-core.so missing from a bundle that
plainly contained it, at base/lib/arm64-v8a/libarmsx3-core.so.
Under `set -o pipefail`, `unzip -l "$AAB" | grep -q x` fails whenever x IS
present: grep exits at the first match, closes the pipe, and unzip takes
SIGPIPE, so the pipeline reports failure. Every check for something that must
be there was inverted, and every check for something that must be absent passed
for the wrong reason -- grep found nothing, read to the end, and no signal was
raised.
Capturing the listing first and piping printf into grep instead only moved
which process took the signal. The listing is matched with `case` now, which
has no subprocess to kill.
Worth stating plainly: this was a verifier that would have reported a clean
bundle whatever went wrong, which is the failure mode the script exists to
prevent. It only surfaced because the one check that should have passed was the
one that failed.
Checking that keystore.properties exists was not enough. A template with the
placeholders still in it passed the check and went on to build, failing much
later inside signing with an error that says nothing about the cause. It now
rejects the placeholders too, so the message arrives before the fifteen minutes
rather than after them.
Also locate a JDK. The script assumed the calling shell had one on PATH, and
without it gradle fails with "Unable to locate a Java Runtime" several steps in,
which points at Java rather than at the environment. Android Studio's bundled
JDK is used when JAVA_HOME is unset, with /usr/libexec/java_home as a fallback
and a clear failure if neither exists.
Two things stood between the flavor split and an uploadable bundle.
The release build type was debug-signed, with a comment saying to swap it
before any public build. It now uses an "upload" signing config when
keystore.properties exists and falls back to the debug key when it does not, so
GitHub alphas stay sideloadable without the key present while Play gets a
properly signed bundle. The keystore and the properties file are both already
gitignored, and nothing reads or echoes them outside the build.
build-play-aab.sh refuses to start without that file and prints the keytool
command to create one, rather than spending fifteen minutes producing a bundle
Play will reject.
Minify is off for the Play bundle and on everywhere else. This is not a
preference: AGP 9.2.1's R8 writes its mapping as mapping.prt, a compressed
per-class archive, while packageBundle still requires a plain mapping.txt, so
an AAB cannot be built with R8 enabled at all. ARMSX2 ships its Play build with
minify off entirely, so this matches existing practice rather than introducing
a compromise, and a 94 MB native core dominates a 76 MB APK -- shrinking the
Kotlin was never where the size is.
Driven by a gradle property, matching how armsx3.minSdk is already threaded
through by build-variants.sh, rather than by the variant API.
Verified: the github release APK still builds at 76.5 MB with R8 running.
An AAB is a ZIP and its entries are compressed, so grepping the archive finds
nothing and reports a clean bundle whatever is inside it. The first version of
this script did exactly that. It passed all five exclusion checks and then
failed to find the applicationId it was supposed to find, which is the only
reason the method got questioned at all -- every check that mattered was a
false pass.
It extracts the bundle now and reads base/manifest/AndroidManifest.xml for the
permissions, and the archive listing for the native libraries. Verified against
a real bundle: com.armsx3.play present, REQUEST_INSTALL_PACKAGES,
MANAGE_EXTERNAL_STORAGE, RECORD_AUDIO and the update provider all absent, and
libarmsx3_lsfg.so not among the shipped .so files.
The manifest is protobuf rather than text, so LC_ALL=C and grep -a stay: the
NUL bytes in it would otherwise make grep declare the file binary and print
nothing, which reads the same as a pass.
Play will not take the build ARMSX3 ships today, and the two runtime flags that
were already here could never have made it acceptable. The manifest said so:
it is the declared PERMISSION in the bundle that gets rejected, not the code
path behind a boolean. So the split is by source set, which is the only thing
that actually removes anything.
github keeps what a sideloaded build should have: the in-app updater, all-files
storage for an arbitrary data folder, and frame generation. play has none of
them, and its applicationId is com.armsx3.play so the two install side by side
instead of over each other.
Moved rather than flagged: com.armsx2.update and its FileProvider resource into
src/github, with no-op composables in src/play so that source set still
compiles. REQUEST_INSTALL_PACKAGES and MANAGE_EXTERNAL_STORAGE now live in the
github manifest, so the play bundle never declares them.
Frame generation is excluded by moving libarmsx3_lsfg.so into
src/github/jniLibs. A packaging block inside a productFlavor is NOT honoured --
it applied to both and dropped the library from the github build too, which the
build caught. Excluding the file is the whole exclusion: the shim is dlopen'd
by name and the core already reports the feature unavailable when it is absent,
which is the same path a device that cannot run it takes.
build-play-aab.sh builds the bundle and refuses to emit one that fails any of
five checks, because a flavor split is only worth as much as the thing that
notices when it stops working. It greps the bundle with LC_ALL=C and -a on
purpose: a NUL byte makes plain grep declare the file binary and say nothing,
which reads exactly like a pass.
build-variants.sh follows the rename to assembleGithubRelease; there is no
flavorless release variant any more.
Both failures in the savedata fixup were logged as fatal while the code
carried straight on -- the loop continues, the emulator starts, and one save
is left as it was. On Android a fatal line goes to logcat at ANDROID_LOG_FATAL,
so it reads like a crash, and it was reported as one.
It is also permanent rather than transient when it happens. Android storage can
refuse a directory rename outright with EPERM, and nothing about launching
again changes that, so it fired on every single launch and looked like a fault
that was getting worse. A user hitting it has no way to tell that their games
are unaffected.
Both are errors now, and both say what the consequence is and what to delete if
it keeps happening, rather than repeating an alarm about something the emulator
has already decided to continue past.
Continuing with the modules that did compile is correct -- the dispatcher
entry for an uncompiled function interprets, nothing runs garbage -- but it is
per-instruction dispatch and it is slower than the interpreter outright. Saint
Seiya measured 6fps against 23 with most of its modules missing.
A game at that speed looks broken, and it gets reported as broken, when the
real answer is one restart away. An honest stop is better than a degraded run
that invites the wrong bug report.
So this ends the boot the way running out of memory always did. What is
different from before is the reason: the message names it and says what to do,
and it stays on screen because the overlay is drawn by the RSX thread rather
than the one this stops.
The fallback itself stays in place for the ordinary case of a single module
failing for some other reason, which is upstream's design and is worth keeping
-- one bad module costing its own functions is a fair trade. Running out of
memory is not that case: it takes most of the executable with it.
Both changes that were brought back are out again. Neither recovered the frame
rate after switching frame generation off, and both broke rendering.
That is every attempt at this reverted. What remains is the code as it stood
before any of it: frame generation is slow, and switching it off does not
release what it allocated.
The GLES DSA shim is kept, because it belongs to the upstream merge and not to
this work -- it landed in the wrong commit and reverting that commit took it
with it, which broke the build for a reason unrelated to frame generation.
The findings from the three audits are still correct as descriptions of what
the code does. Acting on them, one at a time and with the reasoning written
down each time, still made the result worse on every attempt. So the fault is
not in the individual fixes but in something about this subsystem that reading
it has not revealed, and the next attempt should start from a measurement on
the device rather than from another reading of the source.
This is the change that stopped turning frame generation off leaving the
frame rate tanked -- confirmed by which build that was reported on, and by the
SUBOPTIMAL acquire fix not reproducing it on its own.
It replaces the per-frame vkDeviceWaitIdle on framegen's device with a poll on
a sync fd exported from a fence framegen already creates. Reasons it is safe
to bring back while the other three stay reverted: it only runs when frame
generation is producing frames, every failure path falls back to the device
wait, and it changes no layout, no ownership and no barrier -- which is where
the rendering damage came from.
Still out, and staying out: the cross-device ownership barrier, the
generated-frame acquire semaphore with its teardown, and the deferred-present
guard in frame_context_cleanup.
Swept up in last night's blanket revert of the frame-generation work, and it
should not have been. It is the one change of the five that cannot affect
rendering: it corrects error handling and touches nothing about layouts,
ownership or synchronisation.
VK_SUBOPTIMAL_KHR is a success code and the image IS acquired.
present_generated_frame treated it as a failure and returned without
presenting, so the image was never handed back -- one swapchain image lost per
generated frame, permanently, on a driver that reports SUBOPTIMAL as a
standing condition. Once the pool empties the real frame's acquire blocks its
whole 100ms timeout every frame, which is ten frames a second, and only a
swapchain rebuild recovers it. That is why turning frame generation off never
gave the frame rate back.
The generated present's result is handled for the same reason: OUT_OF_DATE and
SURFACE_LOST also leave the image unpresented, and none of the recovery flags
were being raised because generated frames never pass through present().
The other four stay reverted. This one is restored on its own so that if
anything is still wrong, the cause is a single small change rather than five
entangled ones.
Catches up 98 commits from RPCS3/rpcs3. Seven files conflicted:
PPUTranslator.cpp -- upstream fixed the ARM64 float-to-int saturation
inversion independently, and the two fixes are the same fix. Took upstream's
ordering so it stops re-conflicting, kept one line of the reasoning.
rpcn_types.h, rpcn_client.cpp -- our hand-rolled protocol 31 bump was a
stopgap to stop the server refusing us. Upstream's trophy sync (cb175278b) is
the real implementation, so it replaces ours outright.
nv0039.cpp -- upstream refactored the strided copy and extracted
validate_buffer_notify; ours was the older code plus a profiler include. Took
upstream's, re-added the include.
VKQueryPool.cpp -- both sides had real changes. Upstream added a lock around
the pool cache; ours has the bounded occlusion-query wait and the render-pass
fix for the Adreno device loss. Kept ours, re-applied their lock.
VKResourceManager.cpp -- upstream moved GC completion onto a
driver_manager_thread, which supersedes our flush parameter and offloader
dispatch entirely. Took theirs; no callers passed the second argument.
VKGSRender.cpp -- our flush-site counter and their driver-manager drain are
independent. Both kept.
Upstream also began attaching 3D and array levels through DSA, which
EXT_direct_state_access has no NamedFramebufferTextureLayer for, so the GLES
shim gains one that binds and uses the non-DSA entry point.
Reverted in the same commit, because they are what is on the tester's device
and it is broken: the sync-fd handoff, the SUBOPTIMAL acquire fix, the
cross-device ownership barrier, the generated-frame acquire semaphore, and the
deferred-present guard in frame_context_cleanup. Each was defensible on its
own reading of the code and the result was worse every time -- slower, judder
described as nauseating, and rendering faults that outlast switching frame
generation off. Five attempts is enough to stop treating the next theory as
better than the last.
What stays fixed is everything outside the present path. What goes back is the
state before tonight: frame generation is slow and does not release its memory
when switched off.
Two things frame generation was getting away with rather than doing.
The acquire for a generated frame passed VK_NULL_HANDLE for both the semaphore
and the fence. The specification forbids that outright, and the practical
consequence is that nothing made the blit wait for the presentation engine to
finish with the image -- the UNDEFINED old layout on the barrier discards
contents, it does not order anything, so the blit could overwrite an image
still being scanned out. It acquires with a semaphore now and the submit waits
on it.
A ring of eight rather than a single semaphore, because a binary semaphore may
not be waited again before it has been signalled again and up to three
generated frames are acquired per flip. Eight is more than two flips of slack,
so a slot is never revisited before the submit that waits on it has retired.
The other is a crash rather than a hazard. Frame generation's images are file
-scope globals created on the renderer's device, and nothing released them, so
stopping a game and starting another at the same resolution skipped the
rebuild -- capture_presented_frame only rebuilds when the dimensions change --
and recorded images belonging to a destroyed device into a live command
buffer. They are released in ~VKGSRender now, after the vkDeviceWaitIdle it
already does and before the swapchain goes.
That location is the point. An earlier attempt at the same teardown ran from
inside the capture hook, which executes while the frame's primary command
buffer is still being built, and it destroyed rendering outright. A
device-idle point the renderer owns is where this always belonged.
The blit that puts a generated frame on screen named
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL for an image that was never in that
layout, and read it without ever taking ownership from the device that wrote
it. framegen releases its outputs with newLayout = VK_IMAGE_LAYOUT_GENERAL and
dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL, so this was a layout mismatch
and a missing queue-family acquire at the same time.
Undefined by the specification either way, and on a tiler the practical result
is reading whatever survived in cache instead of what framegen wrote -- which
is the corrupted rendering that appears whenever frame generation is switched
on.
The image is acquired from VK_QUEUE_FAMILY_EXTERNAL into the graphics family
and transitioned GENERAL -> TRANSFER_SRC_OPTIMAL before the blit, then
released back the same way afterwards, because framegen's own acquire barrier
on the next generation assumes it finds the image exactly as it left it.
This is about visibility and ownership, not timing: generate() already does
not return until framegen's submission has completed.
present_generated_frame treated VK_SUBOPTIMAL_KHR as a failure. It is a
success code and the image IS acquired, so the early return left it held and
never presented -- one swapchain image gone per generated frame, permanently.
This driver reports SUBOPTIMAL as a standing condition rather than a one-off,
which present() already accounts for a few lines below, so the pool emptied
within a few frames of turning frame generation on.
Once it is empty the real frame's acquire in flip() blocks its full 100ms
timeout every frame. 100ms is ten frames a second, which is the hard lock that
was reported, and it explains the part that made no sense: turning frame
generation back off changed nothing, because the images are gone from the
swapchain and only a rebuild -- a resize or a restart -- brings them back.
The present result was discarded for the same frames. OUT_OF_DATE and
SURFACE_LOST also leave the image unpresented, and none of the recovery flags
were raised, because generated frames never pass through present() where that
handling lives. Both are now routed the same way.
Also submit with flush before presenting. The present runs on this thread
while multithreaded RSX would still have the submit queued on the offloader,
which presents a swapchain image before the blit that fills it.
Every generated frame ended with vkDeviceWaitIdle on framegen's device. It is
the hardest sync primitive Vulkan has and it ran once per frame, which is a
large part of why frame generation cost more than it returned.
It was there because the two devices share no semaphore. The upstream
semaphore path cannot supply one on Android: framegen's device deliberately
does not enable VK_KHR_external_semaphore_fd -- it shares through
AHardwareBuffer instead -- so vkImportSemaphoreFdKHR resolves to nullptr there
and every semaphore handed across is -1. That is not a flag that can be
flipped.
A fence can be exported where a semaphore cannot. framegen already creates one
per generation pass, so this adds VK_KHR_external_fence_fd, exports a sync fd
after the passes are submitted, and hands it back through a new
armsx3_lsfg_present_fenced. Our side polls that fd instead of idling a device.
Three things this deliberately does not do:
The existing completion fence is not the one exported. SYNC_FD export has copy
transference, which resets the source fence, so exporting it would leave the
slot-reuse wait at the top of present() blocking on a fence with no pending
signal -- a hang traded for a stall. A dedicated fence signalled by a
zero-batch submit is used instead, which signals once all previously submitted
queue work completes.
The extension is probed, not required. framegen's required-extension list
throws on a miss, so a driver without it would fail vkCreateDevice and lose
frame generation altogether -- a regression rather than a degradation.
The device wait stays as the fallback. A null entry point, a failed export, a
poll timeout or a poll error all fall back to it, and the warning for that is
emitted once per session rather than once per frame.
armsx3_lsfg_present keeps its signature and forwards to the new path with a
null fd, so the two bodies cannot drift. ABI goes to 3; the version check is
left exact rather than loosened.
The serialised path submits the frame, then blocks on that whole submission
before framegen may read the capture, then blocks again on framegen's device.
Two full stalls per frame, one of them a complete frame of GPU work on the
critical path. That is the reason frame generation costs more than it gives
back on this hardware.
Pipelining exists to remove the first of those: it runs a frame behind and
interpolates the pair ending at the PREVIOUS frame, which the game has had a
whole frame to finish, so the wait is normally already satisfied when it is
reached.
It was disabled because the held-back frame could be recycled underneath it.
That is fixed at the choke point in the commit before this one, so this is
the switch and nothing else.
Kept as its own commit deliberately: if the pipelined path misbehaves on
device, reverting this alone returns to the serialised path with the reclaim
fix still in place.
Frame generation's pipelined path holds one frame back by a present, and the
guard that stopped that frame being recycled underneath it lived in
advance_queued_frames. There are two functions that walk m_queued_frames and
call frame_context_cleanup, and that was one of them.
check_present_status() is the other. It had no check at all, and
flush_command_queue calls it unconditionally on the way out -- so any of the
twenty or so flushes in a frame could retire the held-back frame, and
flush_command_queue(true) drains the queue outright, which made it certain
rather than likely. flip()'s own acquire-retry spin calls it too, exactly when
swapchain images are scarce, which is the condition pipelining creates by
design. That is the "already retired" that fired twice on device and kept
pipelining switched off.
The guard moves into frame_context_cleanup itself, which is the single point
every reclaim path goes through, so a seventh path added later cannot miss it.
Also stop a dropped present leaving a stale present_image behind. Leaving it
set is not harmless: the slot rotates back around and trips
ensure(present_image == umax) in flip(), and reinitialize_swapchain selects
contexts by that field and then calls frame_context_cleanup, whose first line
asserts a command buffer the context no longer has. Dropping a present should
cost a frame on screen, not a fatal two frames later.
No behaviour change on its own: nothing sets m_deferred_present_frame while
pipelining is disabled.
Releasing framegen's resources when the setting went to off broke rendering.
Arkham City lost its character models, then almost everything stopped drawing.
Both halves are reverted: the release_shared_images() call and the shutdown()
that followed it. Adding a vkDeviceWaitIdle on our device before the release
did not fix it, which rules out the in-flight capture blit being the whole
story -- the damage reaches past the shared images, and I do not yet know how
far. Guessing again on a renderer that draws nothing is not worth the trade.
What the revert restores is the known-good behaviour and the known bug with
it: switching frame generation off keeps every byte it allocated, so the frame
rate does not recover until the game is restarted. That is worse than it
should be and better than a black screen.
The teardown itself is still the right thing to do. It has to happen somewhere
the present path is not mid-flight -- a device-idle point owned by the
renderer -- rather than from inside the capture hook, which runs while the
frame's primary command buffer is still being built and submitted.
Shader compilation "dips" are not the renderer waiting on a pipeline. Both
async shader modes are built so nothing ever stalls for one -- the interpreter
draws it or the draw is dropped. The dip is the compile work itself competing
for cores the emulator is already short of.
The pipeline compiler workers never set an affinity at all. PPU, SPU and RSX
all do, and the big.LITTLE policy deliberately fences the SPUs off the prime
core so RSX can have it -- and then a shader burst was free to land on exactly
that core anyway. They are pinned to thread_class::general now, which is the
existing policy for helper threads and already resolves to the little cluster
on big.LITTLE and to every core on a uniform machine, so desktop is unchanged.
The pool was also sized to the wrong thing. The auto heuristic counts host
threads, so an 8-thread phone asked for 2 workers -- a number chosen for cores
those workers are now not allowed on, while its 3 little cores sat idle
through every burst. It sizes to the cluster it is pinned to instead, which
raises compile throughput without taking anything back from emulation: the
cores it adds are the ones nothing else wanted.
Not touched: the driver's compiler itself, which is Qualcomm's and is the
actual cost. This only stops it fighting the emulator for the same cores.
Releasing the shared images and the context was not enough. Measured on
device: the release ran exactly once at the moment frame generation was turned
off, and the frame rate still did not come back.
What was left is framegen's own VkDevice. It is created lazily on first use
and nothing ever tore it down -- shutdown() existed with no callers at all --
so a second logical device stayed alive on the same GPU for the rest of the
process, holding queues, allocators and driver-side state. On a tiler that is
not free, and it is the part that survived the previous fix.
Safe to do from here because initialize() is lazy and guarded by its own
initialized() check inside generate(), so switching frame generation back on
rebuilds the device rather than finding it missing.
Turning frame generation on cost performance, as it should. Turning it off did
not give any of it back, and only restarting the game did.
Everything frame generation needs is allocated on the present path and nowhere
else: two shared input images, up to three generated outputs, and a context on
framegen's own Vulkan device holding imported AHardwareBuffers for all of
them. The "off" branch of that path was a bare early return, so none of it was
ever released -- the feature stopped doing work while keeping every byte it
had taken. On a tiler that is the expensive half.
The teardown already existed and was reachable from exactly one place, the
resize path, which is why this survived: it needs frame generation to have run
and then be turned off in the same session to show at all.
Only the mode going to off frees anything. The same branch also caught a frame
with no source image, which is a transient state on the present path rather
than the user switching the feature off -- tearing down there would destroy
and rebuild the context repeatedly. The two conditions are separate now.
Arkham City never finished compiling, and neither did LEGO Batman 2. The log
said "LLVM crash recovery invoked" 240 times and then killed main_thread,
which looks like a codegen bug and is not one.
What actually happened: utils::memory_commit failed with ENOMEM inside the
disposable LLVM worker. That thread dying is how run_recoverable_llvm reports
any failure, so an out-of-memory device was indistinguishable from bad
codegen. It cost a long detour through max_map_count, disk space and
overcommit before the errno in the fatal gave it away, so the JIT's allocator
now uses a checked commit and throws a plain "Out of memory" instead.
The fatal part was the symbol resolvers. ppu_initialize ensure()d that every
group's __resolve_symbols was present, but that function lives in the compiled
output: when every module in a group fails, it is simply absent. The ensure
turned a partial compile into a dead main_thread, which discarded the 170
modules that HAD compiled and surfaced as a boot that never ends.
That contradicts the design either side of it -- a module that fails to load
is deliberately not fatal, because a guest function with no compiled code
keeps its dispatcher entry and is interpreted. A missing resolver is now the
same: report it, skip it, let that group interpret. Losing one group's speed
beats losing the boot.
Also tell the user. Out of memory is the only compile failure they can act on,
and the useful action is not obvious: compiled modules are already in the
cache, so starting the game again resumes rather than restarting the work. One
message after the workers join, not one per module -- once memory is short
every remaining module fails identically, and two hundred popups would be
worse than none.
Lowering Max LLVM Compile Threads also avoids it, and is deliberately not
suggested in the message: compile time is already the common complaint, and
halving the workers to dodge a case that is now survivable is a bad trade.
Rumble did nothing in any game. Everything needed for it existed and none of
it was wired to anything else.
The core side was fine throughout: the virtual pad is created with
CELL_PAD_CAPABILITY_ACTUATOR, its two VibrateMotors are initialised, and
_rpcsx_getPadRumble reads them, so cellPadSetActDirect had somewhere to land.
Above that, three dead links in a row.
The pump that polls getPadRumble and drives the phone's vibrator was written
and never started -- startRumblePump had no callers at all. The core cannot
notify the JNI layer when the guest writes the motors, so with nothing
polling, no guest rumble could ever reach the device. It now runs for the VM's
lifetime, started once the emulator is past boot and stopped in a finally, so
the abnormal-teardown path that exits via stopRequested cannot leave the motor
buzzing on whatever value a dead guest last wrote.
The enable flag was two flags. NativeApp.sRumbleEnabled had three writers --
the settings toggle, the in-game menu and app start -- and no readers. The
pump consulted a private copy in Rpcs3Bridge that only setPadVibration wrote,
and setPadVibration had no callers either. So the toggle wrote one store and
the pump read another. There is one flag now; the private copy is a read-only
accessor onto it.
Rumble is polled on port 0 only. The device has one vibrator, so there is
nothing to spend a second port on.
The account screen reported nothing until a button was pressed, so after a
restart a saved account looked like no account: the username was filled in and
the password box said one was stored, but nothing said the thing the user
wanted to know.
It now reads the state from the core on entry and says so.
The wording matters here, because "logged in" is not a state that exists.
RPCN keeps no persistent session -- every connection re-authenticates from the
saved credentials, and the client is destroyed as soon as the last shared_ptr
to it goes, which happens the moment a sign-in test returns. So a restart
cannot preserve a login, and claiming one would be a lie that breaks the first
time someone checks. What survives is the ACCOUNT, and that is what the line
reports; "Signed in as" is reserved for a live authenticated client, which
means a game is actually online.
peek_instance is added for that: reporting state must not be the thing that
creates a client, or opening the settings screen would spawn the three RPCN
threads and immediately tear them down again.
The Network tab offered three of the ten settings the core's Net node has.
The seven it did not were reachable only as raw core overrides, and one of
them is the setting that answers the question the tab exists to answer.
RPCN replaces Sony's PSN. A publisher's own backend never was PSN, so a game
that talked to one is not reachable through RPCN at all -- it is reachable by
resolving its hostnames somewhere else, which is DNS address and IP swap
list. Those had no writer anywhere in the app, so "use a custom server" had
no answer short of hand-editing config.yml. They are first in the tab now,
and their descriptions say which of the two layers they belong to, because
that distinction is the whole confusion.
Added with them: IP address, Bind address, PSN Country, Derive MAC from PSID
and Clans Enabled -- all six Settings.kt sites each, plus the bridge
dispatch, the setters, the search index and the tab's reset list. A key
missing from the dispatch is dropped in silence with no build error, so the
emitted keys and the handled keys were diffed rather than eyeballed.
EditableTextRow was dead ARMSX2 DEV9-era code with no callers, a hardcoded
"0.0.0.0" and no controllerFocusable, which would have made every one of
these rows unreachable on a pad. It takes a placeholder and a description now
and registers with the pad-nav registry.
Saved RPCN servers are a view onto cfg_rpcn's own "Hosts" list rather than a
second store, so add, remove, reset and anything the core does to it agree by
construction. Reset restores the shipped list and selects np.rpcs3.net, which
is the way back after typing a custom address over the top of it; removal of
the official entry is refused, matching upstream's own guard, and reported
instead of silently doing nothing. Host descriptions are user text being
pasted into a JSON field, so they are escaped on the way out.
Also exposes cfg_rpcn's experimental IPv6 support, which had no UI either,
and fixes the PSN row's label -- it still read "PSN (Simulated)" from when it
was a two-state toggle that could not select RPCN.
Three things stood between the new RPCN account screen and the live server.
The client announced protocol 30, so np.rpcs3.net rejected every connection
with "Protocol Version Error (outdated RPCS3?)" before any credential was
looked at. Raising RPCN_PROTOCOL_VERSION alone is not enough -- the version
is a contract over the command enum, so the three commands added upstream
alongside it (GetRoomMemberDataExternalList, UnlockTrophy, SyncTrophies) have
to exist too, or every opcode after the insertion point shifts and the
mismatch shows up later as garbled replies instead of a clean refusal.
The other two were in the sign-in test, which was the one RPCN entry point
written standalone instead of through rpcn_with_connection.
It asked for the client with get_instance(0, true), and that flag does not
return an error: it calls fmt::throw_exception, which off a guest thread is a
fatal abort. Tapping "Test sign-in" before PSN status had reached the core
therefore killed the process. It reads like a validity check and behaves like
an assert, which is why desktop's get_rpcn_connection passes the default.
That state is easy to reach by accident, and not through user error:
Settings.applyTo() only runs at game boot or while a game is live, so
choosing RPCN from the library updates the UI and the store but leaves
config.yml on Disconnected until something boots. Testing an account does not
depend on that setting in the first place -- it needs credentials and a
reachable server -- so the check is dropped rather than deferred.
With the abort gone it hung instead, silently, with nothing in the log after
"Loading RPCN config". rpcn_thread's state machine only acts on want_conn
while disconnected, and wait_for_authentified sets want_auth alone: on a
fresh client the thread woke, found no connection request, broke back to its
semaphore and never released sem_authentified. Connecting first is what
np_handler does, and the four account operations already did it through the
shared helper. Sign-in now reports a real result either way.
Also correct a comment claiming cfg_rpcn stores a password hash. It does not;
set_password writes the string straight into rpcn.yml as plaintext.
The pacing added in 0.9 holds a button transition open long enough for the guest
to sample it. It ran on every write, and it could not tell a repeat from an edge.
Analog triggers write through it on every motion event -- dozens a second while
held, every one of them "pressed". Those repeats consumed the queue, so the
release that followed was the entry that reached TRANSITION_MAX_PENDING and got
silently dropped. Three symptoms, one cause: the queue replaying stale states 40ms
apart is the delay and the apparent double-press, and the dropped release is the
button that "presses itself and stays down until you press it again". Only
triggers showed it because only triggers stream same-state writes.
A repeat now returns immediately without touching the pacing budget, so a
trigger's pressure stays current and cannot starve its own release. And the
overflow branch no longer drops a release: losing a press costs one input, losing
a release leaves the pad holding a button the user let go of with nothing to clear
it. A late release beats a stuck one.
Reported against 0.9.2 in Ratchet & Clank (long jump is R2 held plus X), with 0.8
working -- 0.8 predates the pacing. The earlier guess at this, that a Retroid
trigger in "both" mode had two writers, was wrong; that guard is still correct on
its own terms but it was not what broke this.
The RPCN client has been compiled into this core the whole time -- rpcn_client,
rpcn_config, the localized login and account-creation error strings, 302 symbols
in the shipped library. None of it could be reached.
"PSN status" is a three-valued core setting (Disconnected, Simulated, RPCN) and
the port surfaced it as a BOOLEAN:
setEnum("Net@@PSN status", if (enabled) "Simulated" else "Disconnected")
so np_psn_status::psn_rpcn had no writer anywhere in the app. And even had it
been selectable, an RPCN account needs an NPID, a password and a server, and
there was no screen to enter any of them -- grepping the whole UI for "npid"
returned nothing but two error-message strings. Online was not broken here, it
was unreachable, and anyone reporting "online doesn't work" was right in a way
nobody could act on.
Seven exports carry the account operations the core already implements: read and
write the config, create an account, resend the activation token, send a reset
token, reset the password, and test a sign-in. Each returns a finished sentence
on failure and an empty string on success, rather than an ErrorType or an
rpcn_state -- otherwise both enums would need a second copy on the Kotlin side,
kept in step by hand across a dlopen boundary that is explicitly allowed to
version-skew. They are resolved without ensure() like the frame-gen group, so a
core predating them degrades to "unavailable" instead of refusing to load.
The setting becomes a three-way picker, and psnStatus becomes an Int. Its readers
accept a stored Boolean so installs written before this read back as Simulated,
which is what true meant.
The account section appears only when RPCN is selected -- offering it otherwise
invites setting up an account the emulator will not use. It seeds from the core's
saved config, and the password field stays blank with its label saying one is
stored, because cfg_rpcn keeps a hash and there is no plaintext to show.
Every one of these blocks on the network and runs on Dispatchers.IO. Untested
against a live server: the flow matches what the desktop dialog does, but no
account has been created from a device yet.
Three faults on the same path, all of which turned a failed install into a
successful-looking one.
The extraction loop discarded what write() returned, and extract_success was
declared true and never assigned again -- the two occurrences in the file are its
declaration and the branch that reads it. So the failure branch was unreachable,
every file logged "Created file" whatever happened, and m_written_bytes counted
bytes INTENDED rather than written, which is why progress reaches 100% on an
install that did not complete. A short write now stops the file, logs what it got
against what it wanted, and marks the entry failed.
Nothing checked free space either. A full device gave one of two outcomes and
neither said "disk full": a hard abort out of ensure(r > 0) in fs::file::write,
or -- if the backend returned a short write instead of failing -- a truncated
file recorded as installed, which surfaces much later as a corrupt game nobody
can account for. The PKG header carries data_size, its own total for its
contents, so this preflight is exact rather than a floor; the 64MiB margin covers
directory entries and filesystem overhead that figure excludes.
Third, every error reached the UI as "Installation failed". app_version is not a
crash and not a bad file: it is the installer correctly refusing a game update
because the base game is not installed, or because the update does not match the
version that is. Desktop RPCS3 explains that in a dialog. Here it was
indistinguishable from a broken package, which is how a Tekken Tag 2 update came
in as an emulator bug report.
Retroid pads have an L2/R2 mode called "both": the trigger sends
KEYCODE_BUTTON_L2/R2 AND an analog axis. sendTrigger writes the pad button from
the axis, and the key path wrote it again -- one physical squeeze, two independent
writers on the same button, which arrives as a delayed or doubled press. Long
jumps in the Ratchet games hold R2 and were unreliable because of it; a reporter
had it in 0.9.2 and says 0.8 was fine.
sendTrigger already carries the opposite guard: a pad with no trigger axis at all
leaves "the key path in sole charge", added when Switch Pro controllers had their
held trigger cancelled by every stick movement. This is that guard's mirror, and
the two together mean exactly one writer owns a trigger on every pad -- the axis
where there is an axis, the key where there is not.
Keyed on the physical keycode before remapping, because what decides ownership is
how the hardware reports the trigger, not what the user bound it to.
Pads that report triggers ONLY as keys are unaffected: they have none of the three
axes, so the guard does not fire and the key path still owns them.
Past the loading screen the game hits a second, separate failure: the RSX FIFO
desyncs and reads a RET with an empty call stack -- 19 of them in one session,
last cmd 0x20000 every time. recover_fifo() resets it each time and eventually
gives up and kills the RSX thread ("Dead FIFO commands queue state"). The game
then sits at 0 fps with audio playing perfectly, because everything except the
renderer is still alive, which reads as a hang rather than a dead thread. The
semaphore acquires that time out alongside it are downstream: a desynced FIFO
never runs the release that would satisfy them.
Ordered & Atomic plus a 20us wake-up delay clears it. That is what the fatal
message itself recommends, and this is the first evidence here that the advice
is worth anything -- the same suggestion sits unanswered on two other reports.
Which of the two does the work is not established; both were changed at once and
it has not been A/B'd. Both cost performance, hence per-title rather than global.
Also: get_backtrace and get_backtrace_symbols were #ifndef ANDROID, so they
compiled to nothing on the only platform this port runs on. Every native fault so
far has had to be read out of a tombstone or symbolized by hand, and an access
violation gets neither -- that path freezes the emulator instead of aborting, so
the process survives and Android never writes one. Implemented with
_Unwind_Backtrace, which bionic always has, and dladdr for names; frames print
library-relative because the shipped .so is stripped and loaded at a random base,
and that offset is what llvm-symbolizer takes against the unstripped build.
The access violation handler now prints it. Yakuza reached that path once, reading
location 0xc on the RSX thread with the FIFO empty and parked at a self-jump, and
guest state alone could not say which of our functions dereferenced null.
Nothing downstream checks free space. The game creates its directory, starts
copying, and the writes fail one at a time with nobody reading the return value --
so the install bar stops partway and sits there. That is indistinguishable from
the emulator hanging, which is exactly how it turned up: Yakuza Dead Souls stalled
at 78% on a device with 809MB free and produced not one line of log about it.
CELL_GAME_ERROR_NOSPACE is what the real system returns here and games already
handle it -- they put up their own "not enough space" dialog -- so this turns a
silent stall into the message the title was written to show.
The floor is 256MB and deliberately small. How much a game will write is not known
at this point (the size a title declares is advisory and many pass
CELL_GAME_SIZEKB_NOTCALC), so this cannot predict a failure; it only catches the
case where there was never a chance. An install that would have fit is unaffected.
Free space is logged either way, so a stall that is NOT this now says so.
The game runs at 1fps with the flattener on, and not by being slow: the RSX blocks
on nv406e::semaphore_acquire until the wait times out, draws one frame, and does
it again. 145 timeouts in a single session, all on semaphore 0x50300FE0, while the
GPU itself was doing 3.06 ms of work per frame -- so roughly 997 ms of every frame
was the timeout. The acquire outruns the release that should satisfy it from the
very first frame: awaited 0x1 against an observed 0x0, and still 8 behind at 0x68.
Disabling reordering clears it completely and the game boots and plays.
The cause is NOT established, and the obvious candidate does not hold:
flattening_helper only drops registers carrying always_ignore, and that set is four
INVALIDATE methods with no semaphore in it -- a semaphore release takes the default
branch and flushes the batch, which is the safe path. So this is an empirical
per-title workaround and the mechanism is still open.
Both serials: BLUS30826 is the US disc, NPUB31509 the PSN release the original
report came from.
The STOCK entry is not optional. This port keeps one global config.yml, so a value
written for one game persists into the next; without the stock default, booting
Yakuza would leave FIFO reordering disabled for everything launched afterwards.
Same trap the Uncharted 3 entry documents.
Make optional VSH CPU compilation on `File âž” All Titles âž” Create LLVM
Caches` dialog. By default it is now excluded due to VSH caches are
fully covered by the dedicated submenu `File âž” Firmware`.
Use sizeof(u128) instead of 16.
Clear padding after archive_read_block.
Use aligned_div instead of manually aligning blocks.
Fix local_buf size when using raw ptr of the original buffer.
Due to a bug where `SHUFB`'s GFNI constant generation path expects to be combine using a select instead of a OR, it was causing issues with on non-AVX512 CPUs so support was reverted (see #19217). That can still happen on AVX512 CPUs when the shuffle is single source. This patch fixes it and re-lower the target requirements back to just GFNI by avoiding the OR well on the GFNI path. I also renamed a variable and added a comment to better clarify its behavior.
- Instead of filling over 10 arguments and having the ctor silently drop half of them, we create proper wrappers to construct objects for a singular purpose.
Add on Game List title a brief recap of total number of entries in the
list and total number of Disc, HDD and all the other remaining types of
content.
- Verified with hardware tests. RSX does not support proper loops.
- The LOOP/REP instruction simply codes a "REPEAT n" instruction for a block of code.
- There is no accumulator register. The compiler emits a preamble inside the loop block to simulate the running counter.
- Oddly enough, the original start and step values are stored in the instruction but are unused. Maybe useful for debugging real hardware?
The constant generation AVX512-ICL path only requires the feature GFNI, which exists on non-AVX512 CPUs. This was a hold-over from when the shuffle step was merged together. (The proceeding unsigned minimum is from SSE2)
Add parameter checks to sys_net_infoctl
Fix P2P getpeername() fatal
Fix possible deadlock in tcp_timeout_monitor
Remove vport assert from poll
Add upgrade path for new P2PS state in savestates
Fix P2PS close_stream() not waking threads
Fix possible deadlock in P2PS connect()
Ensure RST is sent on unhandled packets
Add thread lock for p2p sockets(used for poll/select to avoid event gap)
Fix poll/select returning EINTR if no sockets were polled
Implement sys_net_infoctl cmd 6(sys_net_get_sockinfo)
Cleanup sys_net_infoctl cmd 9 code
Add SYS_NET_STATE_* values to header
Rewrite P2P and P2PS poll/select implementations
Add extra P2PS state for disconnected and report it as readable with 0 bytes(EOF)
Fix P2PS sockets connecting without being bound first missing hashmap insert
Add missing error check in sceNpSignalingActivateConnection
@@ -518,10 +518,69 @@ val EN: Map<String, String> = mapOf(
"backend.renderer.software"to"Software",
"net.internet.label"to"Internet Connection",
"net.internet.description"to"Lets the emulated PS3 reach the network. Required for anything online; leave off if you only play offline, since some games hang trying to connect.",
"net.psn.label"to"PSN (Simulated)",
"net.psn.label"to"PSN status",
"net.psn.off"to"Off",
"net.psn.simulated"to"Simulated",
"net.psn.rpcn"to"RPCN",
"rpcn.title"to"RPCN account",
"rpcn.description"to"RPCN is the community replacement for PSN, and what makes online play work. An account is created here, not on a website: the server emails you a token that activates it.",
"rpcn.server"to"Server",
"rpcn.username"to"Username",
"rpcn.password"to"Password",
"rpcn.password.stored"to"Password (one is saved \u2014 leave blank to keep it)",
"rpcn.email"to"Email address",
"rpcn.email.why"to"Only used to send the activation token, and a reset token if you forget your password.",
"rpcn.token"to"Token (from the activation email)",
"rpcn.save"to"Save",
"rpcn.test"to"Test sign-in",
"rpcn.create"to"Create account\u2026",
"rpcn.create.go"to"Create it",
"rpcn.create.hint"to"Fill in a username, password and email, then tap Create it.",
"rpcn.token.resend"to"Resend token",
"rpcn.reset"to"Reset password",
"rpcn.working"to"Contacting the server\u2026",
"rpcn.saved"to"Saved.",
"rpcn.signedIn"to"Signed in. Online should work in games that support it.",
"rpcn.created"to"Account created. Check your email for the token, enter it above and Save.",
"rpcn.token.sent"to"A new token has been emailed to you.",
"rpcn.reset.sent"to"Done. If you asked for a reset token, check your email, then enter it with a new password.",
"net.psn.description"to"Reports a signed-in PSN account to the game without contacting Sony. Some games gate features or trophies behind a PSN session and will not enable them otherwise. This does not connect to real PSN.",
"net.upnp.label"to"UPnP",
"net.upnp.description"to"Asks your router to open ports automatically for peer-to-peer play. Only useful with an online connection, and only if your router allows it.",
"net.dns.label"to"DNS server",
"net.dns.description"to"Which DNS server the emulated PS3 uses. This is what a private or fan-run game server needs: RPCN replaces Sony's PSN, but a publisher's own servers were never PSN, so reaching a revival of one means resolving its addresses somewhere else. Leave at 8.8.8.8 unless a server tells you otherwise.",
"net.swap.label"to"Hostname redirects",
"net.swap.description"to"Send individual addresses somewhere else, as host=1.2.3.4 joined by &&. Finer than changing the DNS server, because it moves one hostname instead of every lookup. Entries that do not parse are ignored.",
"net.ip.label"to"Console IP address",
"net.ip.description"to"The address games are told this console has. 0.0.0.0 lets the emulator work it out, which is almost always right.",
"net.bind.label"to"Bind address",
"net.bind.description"to"Which network interface to use when this device has more than one. 0.0.0.0 means any.",
"net.country.label"to"PSN country",
"net.country.description"to"Two-letter country code reported to PSN and RPCN. A few games change region-specific behaviour based on it.",
"net.mac.label"to"Derive MAC from PSID",
"net.mac.description"to"Generate the console's network address from its ID instead of using a fixed one, so it differs per user. Some online services notice when two consoles share an address.",
"net.clans.label"to"Clans",
"net.clans.description"to"Enable the clan features some online games expose. Needs a working RPCN sign-in.",
"net.address"to"Address",
"rpcn.hosts.title"to"Saved servers",
"rpcn.hosts.description"to"Keep more than one server and switch between them. The official server cannot be removed, so there is always a way back.",
"rpcn.hosts.add"to"Save current",
"rpcn.hosts.name"to"Name for this server",
"rpcn.hosts.remove"to"Remove",
"rpcn.hosts.reset"to"Reset to official",
"rpcn.hosts.added"to"Server saved to the list.",
"rpcn.hosts.removed"to"Server removed.",
"rpcn.hosts.resetDone"to"Back to the official server (np.rpcs3.net).",
"rpcn.hosts.selected"to"Switched server. Test sign-in to check it.",
"rpcn.need.username"to"Enter your username first.",
"rpcn.need.password"to"Enter your password first \u2014 Save clears the box, so type it again.",
"rpcn.need.newPassword"to"Enter the new password you want to set.",
"rpcn.need.email"to"Enter the email address for this account.",
"rpcn.account.saved"to"Account saved:",
"rpcn.account.saved.note"to"It stays saved between restarts. RPCN keeps no permanent session, so games sign in with this account when they go online \u2014 there is nothing to log into again.",
"rpcn.account.connected"to"Signed in as",
"rpcn.ipv6.label"to"Experimental IPv6",
"rpcn.ipv6.description"to"Use IPv6 for RPCN where available. Experimental upstream; leave off unless the server asks for it.",
"adv.section.cpuAccuracy"to"CPU Accuracy",
"adv.section.cpuAccuracy.help"to"How faithfully the PS3's Cell processor is emulated. Looser settings are faster but can corrupt graphics, physics or saves in games that depend on exact behaviour. Change one at a time.",
"adv.xfloat.label"to"SPU Float Accuracy",
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.