A debugger client drives the app from a script, and the logo screen only
gets in its way.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An Android 11 TV played nothing over a Bluetooth headset since the switch
to Oboe, while its speakers worked. Testing on it showed AAudio with MMAP
is silent there and AAudio without MMAP works, as does OpenSL ES (also
when the headset is connected mid-game), whatever the stream usage. MMAP
should never be used for a Bluetooth route, so it's a firmware bug.
Audio settings get an Audio API choice and a Low-latency audio (MMAP)
checkbox, with hints, so other devices with the same kind of bug can be
worked around. The API defaults to OpenSL ES on TVs and before Android
8.1, where the choice isn't offered, and AAudio elsewhere. Changing them
reopens the streams right away. System Information shows the output
stream that got opened (API, MMAP or not, rate, buffers, device) and any
audio error.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Dialog screens (tabbed, two-pane, simple and the others built on
UIBaseDialogScreen) center their content at up to 1400 wide, instead of
stretching settings rows and buttons across very wide windows.
On large screens, those screens and the main screen also get an inset on all
sides, since controls right at the edge are hard to see there. It grows with
the screen's smaller dimension, from nothing at 600 to the full inset at
1000, and is always full on TVs. The game, popups, and the screens that
position things to match the game's screen (display layout, touch control
layout and tests, custom button and visual mapping) are left as they were.
The root's bounds are computed by UIScreen::GetContentBounds, since
UI::Margins can't hold widths this large.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
NativeShutdownGraphics, which Android runs whenever the app loses its
surface, destroyed the imgui context and the ImDebugger along with the
device objects, so window layout and debugger state reset on return.
Now only the device objects go there (the backend already recreates
them), and the context and debugger are torn down in NativeShutdown.
Both configs are still saved on device loss, in case the app is killed
in the background.
imgui.ini is now read and written through our File API rather than
imgui's fopen, which can't reach a memstick behind a content URI.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
POPS (the PS1 emulator) uses HOME for its menu. The touch button is hidden by
default, and HOME still has no default key mapping. It's also appended to the
custom button and gesture key lists.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ChoiceGroups no longer have hardcoded margins. A new ChoiceGroupList holds them,
with padding and spacing that give the same look as before. Settings tabs, developer
tools, the two-pane settings columns, the pause menu and the rest put their groups in
one, and a few screens adjust its padding and spacing to their layout.
Also add the two groups in the remote disc streaming settings that were created but
never added, and make the settings search look inside groups: it only checked a tab's
direct children, so with ChoiceGroups a filter hid everything.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LinearLayout had a public padding member that only DisplayLayoutScreen set. Make it
private with a setter, and fix the measuring: the vertical retry used the horizontal
padding, the AT_MOST retries and the weighted children ignored it, and growing to fit
weighted children counted it twice.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PathBrowser lists on one thread, so going up from the network folder
waited for its listing to finish (up to 8 seconds) before the drive list
showed. Pass GetFilesInDir a cancel flag that PathBrowser sets whenever
the path changes, or on destruction, and check it while waiting.
Also don't probe the drives in the drive list for game folders.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The path display says Network instead of \\, and the folder button in
the drive list gets the new I_NETWORK icon and a translated name.
images.svg also picks up I_TOGGLE_ON and I_TOGGLE_OFF, not used yet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The drive list gets a Network entry, the path //, which lists the
servers in the shell's network folder (the old WNet enumeration finds
nothing on a modern network). Going up from \\server now lands there
instead of in the drive list.
Network listings run on a thread of their own with a deadline, since
they can block for a long time and can't be cancelled: the network
folder keeps its enumerator open for half a minute after it has found
everything, and NetShareEnum on a server that doesn't answer hangs too.
We take what has turned up after a quiet second, or after 8 seconds at
the most.
GameBrowser skips probing the servers for game folders, which would
contact each one from the UI thread.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each ItemHeader becomes a ChoiceGroup with that title, and the items
under it are added to the group, as on the audio tab.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
UIAtlas generates an antialiased white corner for each radius in
ROUND_RECT_RADII (8, 16, 24, 32 UI units) and each style (filled, and
thin/medium/thick outlines from ROUND_RECT_THICKNESS) at the atlas DPI.
DrawBuffer::DrawRoundRect mirrors the corner into all four corners and
stretches the corner's last texel row/column along the edges, so a
round rect is 9 quads (8 for outlines).
Other radii use the nearest corner, scaled. The LogoScreen temporarily
draws some for testing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DetectGameRegionFromID() only knew PSP IDs, so a PS1 one (SLUS, SCES,
SLPS and so on) came out as homebrew. A PS1 disc image or EBOOT with no
icon of its own showed the app icon in the game browser, and the list
views left out the ID of any game drawn without a picture.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A CD CHD is read through its track metadata: each track's frames are
padded to a multiple of four, a pregap is stored only when its type
starts with V, and audio is big-endian. A .chd that's a CD is a PS1 disc,
PSP discs being DVDs. The FileLoader-backed core_file the PSP CHD block
device used is now OpenCHDThroughLoader(), for both.
An .mds gives each track's start, pregap and length, and where in the
.mdf it's stored, with 96 bytes of subchannel data after each sector.
Anything between tracks isn't stored. A bare .mdf goes through its .mds,
which the game browser lists, and archives install with both.
MODE2/2336 cue sheets, which only had their sector headers made before,
are now tested too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A zip or 7z with a PS1 disc in it (a cue sheet and the files it names, a
.ccd with its .img and .sub, or a lone raw .bin or .img) is now installed
like a zipped ISO. A disc of several files goes into a folder of its own,
named after the cue sheet or .ccd. An ISO in the archive still takes
priority, and a lone .bin or .img only counts when nothing else does.
The game screen offers Play for a PS1 disc image only when the installed
firmware's POPS can run it: 3.95 to 6.61, with popsman.prx.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A cue sheet, a CloneCD .ccd, or a bare .bin, .img or .iso of a PS1 disc
now boots on the firmware's POPS, as the converted EBOOT.PBP popstation
would make from it. PS1EbootFileLoader builds the PBP header, PARAM.SFO
(the disc ID from SYSTEM.CNF) and the PSISOIMG header, TOC and block
index, and serves the disc image from the image's own files. Each block
of 16 sectors is a stored DEFLATE block: the manager won't read an image
of uncompressed blocks in its own format, but the converted PBP's inflate
takes stored ones, and with every block the same size, reads map straight
onto the files. Nothing is compressed or written anywhere.
Cue sheets can have any number of files, AUDIO tracks, INDEX 00 and
PREGAP, and a missing FILE falls back to the cue sheet's own name. A
2048-byte .iso gets Mode 2 Form 1 sectors made around its user data.
The EBOOT is mounted as the only file in host0:, through BlobFileSystem,
which now saves its open handles in savestates. The game browser lists
.cue, .ccd, .bin and .img, leaving out the .bin and .img files a cue
sheet or .ccd next to them describes, and a bare image opened directly
goes through the cue sheet that names it. GameInfoCache reads the title
and ID of these, and of real PS1 EBOOTs, from their PARAM.SFO.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A disc game or memory stick EBOOT started from the XMB used to be swapped in under the XMB's session,
so it ran without its PARAM.SFO, compat flags, game config, window title, savestates and so on.
Much of that is decided once, at boot (the memory map, the GPU backend, the shader cache), so
sceKernelLoadExec now asks the frontend for a reboot into the disc image or EBOOT (PSP_RequestBoot)
and stops. The app and headless boot it like a game from the game list.
The game keeps the XMB's launch args (CoreParameter::launchedFromVSH), so exiting boots the XMB
again, with the disc that's in the drive and those args, and the XMB comes back where it was without
auto-starting the disc. Homebrew keeps the disc that was in the drive. A disc whose executables are
all zeroes (the EUR Video Demo Disc's "game") still loads in place and fails back to the XMB.
In headless, the run carries on through the reboot, starting stopped under the debugger like the
first boot, since the reboot loses a pending cpu.runUntilTime.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Text and the SVG button images are mixed on screen, so they now share
UIDropShadowParams (3 UI units of blur, 0.66 intensity, no offset, the
atlas's existing look) instead of the text having its own.
draw_text.cpp included <windows.h> directly, whose min/max macros broke
the std::max calls added here. Include CommonWindows.h instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The game list already did, but booting a video disc any other way (the command line, a file
association, dropping it on a running game, headless) tried it as a game and failed. The loader now
swaps in vshmain.prx with the disc mounted and auto-start on whenever it identifies a UMD Video
disc and the installed firmware can play it. GetUmdVideoVSHPath() moves from the UI to
Core/Util/PSARUnpack.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Without firmware that can play UMD Video (2.71 or later), the info screen and the boot error said
"PPSSPP doesn't support UMD Video", which is no longer true. They now say the firmware version
needed, from UMD_VIDEO_MIN_FIRMWARE, the same constant the check uses.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The EUR Video Demo Disc's UMD_DATA.BIN says GVA, but its EBOOT.BIN and BOOT.BIN are all zeroes, so
"Play game" could only fail. It now goes straight to the XMB, like "Watch video". What the disc
reports to the emulated PSP doesn't change.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
UMD Video discs ship an updater PARAM.SFO next to a 0-byte DATA.BIN, and PSN releases zero-fill it.
MountedDiscHasUpdater() only checked that DATA.BIN existed, so booting the EUR Video Demo Disc's
game with no firmware installed started an auto-install of "2.00" that failed with an OSD error.
It now wants a PSAR archive, like ReadBundledUpdateInfo() already did. The game info screen no
longer looks for an updater on UMD Video discs at all.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
UMD Video discs, PS1 EBOOTs and frame dumps have both a picture and a type icon, and the icon and
title were drawn over the picture, showing up as a square in the middle of it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With firmware 2.71 or later installed, picking a UMD Video disc boots the XMB with the disc in the
drive and UMD auto-start forced on for that boot, so the disc's menu comes up. Without one, the
info screen saying UMD Video isn't supported shows as before. The info screen's Play does the same.
A disc with both a game and video asks which to start. For the video, the XMB comes up with
auto-start forced off, since auto-start runs the game.
Older firmware can't play these: 1.50, 2.00 and 2.60 crash in video_plugin or paf with a video disc
in the drive, and 2.50 refuses it. 2.71 through 6.61 reach Bourne Supremacy's menu.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
nanosvg walks every scanline of the target for each visible shape, so
rasterizing the whole SVG and cropping the images out of it cost
shapes * canvas area. Each image is now rasterized into a buffer the size
of its bounding box, from a list of copies of its own shapes, so the
images can be rasterized on the thread pool.
Atlas generation on a Redmi 9A (32-bit) goes from about 165 ms to about
95 ms, and on a desktop from about 10 ms to 6 ms. Parsing the SVG is now
the largest part. The atlas layout is unchanged; a few edge pixels differ
by at most 2/255 from the different subpixel offsets.
Also copy whole rows in Image::copyfrom, and fix a leak of the SVG file
data when parsing fails.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Metal: when a layer has no drawable to give (it can take a second to
say so), don't ask again in the same frame for every pass to it.
- Swapchains other than the primary are created, resized and destroyed
between frames (Metal asserts it), and destroyed with the last
reference to them (ReleaseAssertLast, Metal and Vulkan).
- BindFramebufferSamplesAsTexture asserts it isn't given a swapchain,
like BindFramebufferAsTexture.
- An offscreen Metal swapchain can't be resized, asserted.
- VulkanRenderManager asserts that no additional swapchain outlives it.
- Native_RemoveOutputAndWait asserts it isn't called on the thread that
handles the requests, where it would always time out.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With the game on a TV (landscape) and the device in portrait, the display
layout screen edited the portrait layout, which the TV doesn't use, so
nothing seemed to change. Its controls are now disabled in that case, and
work as before with the device held the same way as the TV.
The screen rotation hotkeys change the layout the game is shown with, the
TV's, instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The outputs were only marked live after deviceRestored(), so a
Native_RemoveOutputAndWait() that came in while it was recreating their
swapchains returned at once. On Android the surface could then be freed
while a swapchain was being made on it (unplugging the TV as the app
returns to the foreground). Mark them live before the queue is
processed, so such a removal waits for the next frame.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>