PPSSPP has access to the URI, so a frontend or adb can launch the game with it,
which isn't true of a plain path on modern Android.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The interpreter's MMIO dispatch was a hardcoded chain for GPIO and the syscon
serial port. Devices now register an address range in Core/HW/MMIO and get
each access with its width and the MIPSState making it, for registers that
answer each CPU differently. An access no device handles is logged and reads
as poison, as before.
GPIO and syscon serial become the first two devices. They're reset at boot,
which they never were, and saved in savestates (SaveStart section version 4).
ll/sc, lwl/lwr and lwc1/swc1 reach MMIO in the interpreter too, and kernel
blocks leave lwc1/swc1 to it like the other loads and stores. swl/swr and
lv/sv still treat it as a bad address. TestMMIO runs a kernel-mode access on
each core and the registers through a savestate.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#18528
Launchers typically open the game with ACTION_VIEW and a content:// URI, so
they could never pass options before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
"Compress game" in the game screen's ... menu, for ISO, CSO, CHD and PSN
EBOOT.PBP games. It opens a list of the CSO presets (and Extract to ISO
for anything that's not an ISO already):
- Best: libdeflate level 12, 64K blocks
- PSP Compatible: zlib level 9, 2K blocks, readable by custom firmware
These match maxcso's presets, and the CSOs come out byte-identical to its.
The writer, Core/FileSystems/CSOCompress, reads the input through PPSSPP's
own block devices, so recompressing is a single pass with no temporary
file.
The original is deleted when done, unless "Keep the original file" is
checked (off by default). When the new name is taken, by a kept original
or another file, a number is added to it.
The work runs on a thread using every core, with a progress bar and time
left at the bottom of the game screen. It keeps going when the screen is
left, and the ... menu offers Cancel meanwhile. On the game list the
game's icon is covered by "Compressing...", and clicking it opens the
game screen instead of playing. When done, recents follow the new file
and the game screen and lists refresh.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CCxekYqPnzNr5d8Tp1q7aZ
Only the compressor (deflate_compress.c, utils.c and their headers) from
libdeflate 1.7, the version LunaMoo/maxcso vendors, for writing CSO files.
Built as part of Core on every platform. MIT license.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CCxekYqPnzNr5d8Tp1q7aZ
backbufferResize comes from the UI thread, and changed g_display while the
render thread was drawing with it. Like iOS, it now leaves the new size for
the render thread to apply before its next frame, or applies it directly if
no render loop is running. A rotation in a resize that's superseded before
it's applied still forces the swapchain recreate.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
joystickAxis copies the axes in batches on the stack instead of allocating
per event, and bounds the count by the arrays it got. queryGameInfo's cache
is a local, in a helper so it's gone before the thread manager is torn down.
Removes Android_GetFreeSpaceByFilePath (never called) with its Java side, and
isLandscape, which only existed to call NativeGetAppInfo early.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Scoped storage no longer flips on while no activity is registered: whether
legacy storage was preserved is fixed per install, so it's asked once.
- Free space reads as unknown (-1), not 0 bytes, without an activity.
- Strings from Java (file listings and info, crash history, the camera list,
the input debug string) go through GetJavaString, so names with characters
outside the BMP aren't mangled, and a null string can't crash the crash
history.
- Requests (inputbox, browse_*) whose command can't be delivered are failed
instead of left waiting forever.
- A font that fails to load uses the default typeface (-1) instead of font 0,
and is retried later if there was no activity yet. Missing exception checks
added in the text renderer.
- Method IDs and the storage callbacks are looked up once in JNI_OnLoad.
- Unknown permissions are logged correctly.
Also removes what was left of Moga support, the unused backbuffer format and
a stray MessageBox macro, and tidies System_GetCameraDeviceList.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The location permissions imply android.hardware.location as required,
on top of the .gps and .network subfeatures we already marked optional.
Also skip the permission prompt on devices without location hardware.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
RECORD_AUDIO implies android.hardware.microphone with required=true
unless declared otherwise, which hides the app from devices without one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Android 17 ignores fixed orientations on sw600dp+ displays, but exempts
games, and we declare appCategory="game". Tested on an Android 17 tablet
emulator: with the category, Portrait is honored (letterboxed); without it,
the system logs "Ignoring requested fixed orientation". So stop hiding the
rotation setting there, and drop the smallestScreenWidthDp plumbing that
only existed for that check.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Part of #21585
Android 17 apps need ACCESS_LOCAL_NETWORK to serve other devices on the LAN.
The upload screen now asks for it when it opens, starts the web server once
it's granted, and shows a warning and a retry button if it's denied.
The permission only exists from API 37, so check for that rather than 35.
On Android 15 and 16 it reads as denied, which left Remote ISO's connect tab
stuck behind a permission request that could never succeed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GPU_Null derives straight from GPUCommon and has no thin3d context, framebuffer
manager, texture cache or shaders. It runs display lists with the flow control,
bounding boxes, matrix loads (the hardware backends'), immediate vertices and
block transfers of the others, moves the vertex and index pointers past each
draw and curve, and charges the per-vertex and fill cost the software renderer
does. A DrawEngineCommon that queues nothing provides the vertex decoders.
It's for measuring the CPU emulation. From savestates, God of War, Tekken 6,
Wipeout Pure and LocoRoco flip the same frames in 10 emulated seconds as with
Vulkan and software, in 44-77% fewer instructions than Vulkan.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PSPAR code types run the way TempAR's cheat_apply_pspar() runs them:
writes, 32-bit and masked 16-bit conditions with the D0 terminator stack,
load/set/add/write offset, C0 loops with D1/D2, the Dx data register
codes, patch (0x0E), memory copy (0x0F) and the safe data store (0xC4),
which lets a cheat write over its own lines.
PSPAR's button codes (type 9/A at 0x09F00000 and up) read the real pad,
like TempAR's hijack option, as does TempAR's fake pad address
0x0A000000. TempAR's _N extensions aren't supported, and folder codes
(0xCF) do nothing.
Lines can now end in a # or // comment, a cheat can't mix _L and _M
lines, and importing from cheat.db keeps _M lines. The unit test runs
the PSPAR tests from TempAR's tests.db.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The native IR JITs get the emulator thread's host PC sampled every 50 us (a
signal on macOS and Linux, SuspendThread on Windows), mapped to blocks, the
routines they call and host functions. The report has each hot block's MIPS,
IR and host code with the samples per host instruction. The IR interpreter's
block timing, previously behind IR_PROFILING, now runs on the same switch.
Both cost nothing when off.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The triangle is convex, so once a row has been entered and left, the
rest of it is outside. This cut cycles by 5% in Wipeout Pure, where depth
raster is on for the lens flares.
The test checks the covered pixels against a per-pixel edge test.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
TestBoundingBoxFast transformed and tested one vertex per iteration,
with a scalar divide and a min/max chain each vertex had to wait on.
It now transposes four vertices into lanes and does it all as vectors,
with the same operations in the same order, so the results don't change.
It was 16.6% of the emulator thread in Wipeout Pure (with depth raster
off) and 5% in GTA: Vice City Stories.
Per vertex, it's 1.8x faster on an M1, 2x on x86-64, 1.7x on a Galaxy S8+
and 10% faster in 32-bit mode there.
The test compares it with the old version on random draws in every
position and index format.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
FakeJit was the native JIT for architectures without one, but the config
already forces those onto the IR interpreter, so CreateNativeJit() now
returns that instead. The vertex decoder fallback no longer needs
FakeEmitter.
JitBlockCache was the block cache of the pre-IR JITs. Its header keeps
the debug interface types the IR block caches use. Also removes the
JitInterface link hooks only it called, and the JitState fields tied to
it. This fixes a null dereference in the ImDebugger memory section,
which read GetBlockCache(), always null with the IR JITs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The IR-based one replaces it. With that, every native JIT is IR-based, which the JIT viewer and
the settings' comments now assume. The ARM emitter test loses its part that drove the old FPU
register cache.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The backend follows the ARM64 one: a dispatcher, a register cache on top of the IR one, and
native exits, syscalls, breakpoints and transfers. Everything else goes to the IR interpreter
for now. Floats are mapped one lane per S register (no SIMD mapping yet), and addresses are
masked, as on every 32-bit build.
The JIT core now uses it on ARM32 in place of the old ArmJit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Their IR-based replacements stay. The JIT core now creates the IR JIT on both, so "JIT using
IR" is the same as "JIT" on every architecture (ARM32 still uses its legacy JIT for both). The
settings no longer offer it, a saved JIT_IR loads as JIT, and the JIT harness and CPUEquivalence
fuzzer drop the duplicate run.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The old disarm handled the integer instructions but only a fraction of VFP
and NEON, and got several of those wrong. The new one decodes ARMv7-A ARM
state with VFPv4 and NEON, printing UAL with upper case mnemonics.
The new ArmDisasm test runs every ARM emitter function through it, two
examples each. Tools/arm_disasm_check.py compares the test's output with
llvm-mc; they agree on all of it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rewrites most of DisArm64.cpp. Previously missing: test-and-branch,
exceptions other than brk, hints and barriers, adc/sbc, conditional
compare, extr, literal loads, exclusives, unscaled and FP register
offset loads, ldpsw, SIMD structure loads/stores, scalar pairwise,
fixed-point conversions, fccmp, most two-reg misc ops, modified
immediates, smov/umov/dup from GPR, and the scalar shift forms.
Fixed: sp printed as x31, unsigned load offsets sign-extended, ldrsb and
friends shown as stores, adrp ignoring the page, double precision
1-source ops shown as single, fcmpe shown as fcmp, smulh/umulh, the
bitmask decode for 64-bit elements, and fpcr shown as fpsr. Common
aliases are used now (cset, mul, tst, cmn, lsl/asr/ubfx, sxtw...).
TestArm64Disasm runs two examples of every emitter function through it.
The expected strings were verified with Tools/arm64_disasm_check.py,
which compares all 833 instruction words against llvm-mc.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Android headless build only did software rendering. Vulkan takes the
offscreen path desktop headless already uses, and OpenGL ES renders into an
EGL pbuffer, so both run from adb shell with no window. Assets are found next
to the executable, like on desktop, instead of in an APK path that no longer
exists.
frametests.py --adb pushes the binary, assets and dumps to a device, runs each
dump there with Vulkan and GLES, and compares the screenshots with the host's
Vulkan render using Tools/image_compare.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
They are meant to be separate shared libraries that driver.cpp loads into the
driver's namespace by name (ext/libadrenotools builds them that way, and so
does the gradle build). Linked into an executable, file_redirect_hook.c's
global fopen() takes over every fopen in the process, including the one in
libc's own startup, and hook_fopen() calls fopen() again forever. That hung
ppsspp_headless (and the unit test executable) before main().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before Android 11, decorFitsSystemWindows(false) sets LAYOUT_STABLE, and
the system bar insets then keep the space of hidden bars. So in
landscape, where the status bar is always hidden, the UI had an empty
band at the top (seen on a Galaxy S8 and a Pocophone F1). Count only
the bars we show, and reapply the insets after changing that.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ContentUri catches Exception, but an Error got through, and none of the
native wrappers checked. A thrown int call returned 0, which reads as
SUCCESS, and the exception stayed pending, so the next JNI call aborted.
copyDocument/moveDocument are API 24/25 while the folder browser is
offered from 21, so those threw NoSuchMethodError there - now they return
a failure so the callers fall back.
Also convert non-URI names to Java's modified UTF-8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Catch takePersistableUriPermission failing in ShortcutActivity, as
DocumentResultProxyActivity already does. Some pickers don't grant it.
- Treat a cancelled permission request (empty results) as denied.
- Handle getCacheDir() returning null, like getFilesDir().
- Don't let the input device debug string, read from the render thread,
throw ConcurrentModificationException into native code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
requestLocationUpdates() throws IllegalArgumentException for a provider
that doesn't exist (WiFi-only tablets, TVs, Chromebooks), which crashed
any game opening the GPS. Register each provider separately.
Without an IR blaster, the IR service is null, and InfraredHelper was
created anyway, so a sceSircs call NPE'd. Also catch transmit() failing,
like the SecurityException on API 21/22.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pspKeys and pspAxis were written from the input thread and the render
thread without a lock. Entering VR camera adjust "released" held keys by
sending PSP button codes as keycodes, which released nothing; use
ControlMapper::ReleaseAll instead.
Input devices are now matched by ID. InputDevice objects are replaced after
device changes, so reference comparison registered the same device again.
Removing a device now releases what it was holding.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GetJavaString() returned Java's modified UTF-8, so input like emoji
reached C++ as invalid UTF-8. Commands to Java now get converted the other
way. Request IDs are made on several threads, and the permissions map was
written on the Java UI thread and read on the render thread. The frame
command debug log no longer prints params, which could hold a password.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An input box whose show() threw, a second input box open at onDestroy, and
browse commands dropped for a destroyed activity or failing in
processCommand were never answered, leaving the native side waiting (a
game's OSK forever). The image proxy also crashed the app on a picker
result without a URI, a SecurityException or an OOM.
Input boxes now answer exactly once, instead of a success followed by an
ignored failure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The passwordMasking flag of System_InputBoxGetString() never reached Java,
and both sides logged the entered text, including RetroAchievements passwords.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Oboe input callback used to call Microphone::addAudioData directly,
writing PSP memory from the audio thread. Now it only pushes samples
into a SampleRing, and pollRecording drains it on the CPU thread.
Starting and stopping still go through Java, which asks for permission.
Every host with a microphone polls now, so sceUsbMic drops what only the
push path needed: QueueBuf's recursive mutex, restarting the mic on a
device change, and the startMic parameter nobody read. Also removes the
unused System_AudioRecordingState and Microphone::isNeedInput.
The Java microphone_command branch didn't return true, so every command
logged "processCommand failed".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Zips the given files (content:// paths included) into a folder of their own
in the app's cache and offers the zip through System_ShareFile. The receiving
app reads it whenever it likes, so it can't be deleted right away: each share
deletes the folders of earlier shares once they're an hour old.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Offers a file through the system share sheet, for things like sending a
zipped savestate to a friend. Android for now (SYSPROP_SUPPORTS_SHARE_FILE):
content:// paths from a picked folder are shared as they are, and plain paths
through a FileProvider. Either way the receiving app can read just that file.
With no MIME type given, it's guessed from the extension.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replaces the instance counter with a weak reference to the current activity,
and says in the log when an activity takes over, and when a replaced one skips
something because the native side isn't its anymore.
The render loop state is static now, with the activity whose surface it runs
on: there's one render thread. An activity only stops the loop if it runs on
its own surface, and one that finds another activity's loop still running
logs an error and stops it before starting its own.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Now that EGL is ours rather than GLSurfaceView's, the swap interval can be set,
so turning vsync off (and fast-forward without skipping flips) works on Android
GL too. Not in VR, where OpenXR presents, and not with a config whose minimum
swap interval is 1.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
onPause used to join the render thread and stop the audio, so anything taking
focus while the game stayed on screen (a permission dialog, a share sheet, the
other app of a split screen before Android 10) tore down the whole graphics
context and silenced the game. Emulation, audio, sensors and input listeners
now follow onStart/onStop. The render loop itself already follows the surface;
onStop joins it too, for devices that keep a stopped activity's surface.
The camera stays on onPause/onResume, as it goes to the focused app.
A finishing activity lets go of the render thread in onPause, since a quick
relaunch creates the next activity before this one's onStop, and that onStop
then leaves the audio and location updates alone.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Initialize() runs for the first activity only, but set up per-activity
fields: after any recreate, audioManager was null, so audio focus was
never taken or given up again, and sustained performance mode was off.
- The static camera helper kept the first activity's display. It now gets
the application context.
- An activity backed out of (or destroyed by the system) with the native
side left running stayed referenced by it until the next one registered,
and native code could still post commands to it. It now lets go, and a
command arriving at a destroyed activity is dropped (an input box request
answered as cancelled).
- An open input box is dismissed in onDestroy, instead of its window
leaking and its request never being answered.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>