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>
A RoundCorners mask picks the corners to round. A square corner is drawn
from the corner image's edge profiles, split along the diagonal, so its
antialiasing matches the straight edges and it needs no extra draw call.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
It stays for a second after the last activity, then fades out over half
a second. Any scrolling, touch, bob drag, or the mouse over the bob's
strip brings it back within a tenth of a second. It also shows briefly
when the view appears, so it's clear that it scrolls.
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>
chdman createcd makes CD CHDs of PSP discs too, which were taken for PS1 games. A CHD is now a PS1
disc only when its volume descriptor's system ID is PLAYSTATION, and otherwise goes on to the PSP
disc checks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Windows also gets a filter of its own for them. iOS's picker takes any
file and Android's doesn't filter by extension.
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>
NativeInit falls back to the default memory stick when the one in memstick_dir.txt can't be read (on
Android 9 and older, /sdcard), and an old ppsspp.ini there would make the user's grants look unused.
Also release before re-taking folders, since a take can make the system prune, keep going when one
release fails, save the shortcut list when only its order changed, and re-take the Remote ISO shared
folder.
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>
On a PSP, jr and jalr to an address with its low two bits set raise no
address error: they run from the aligned address, the PC keeps no low
bits, and jalr's link is unaffected (cpu/jump/unaligned, recorded on
firmware 6.61). This was done only for the Media Engine.
The firmware's PS1 emulator returns through such an address. The
interpreters stopped there with an invalid jump, and the JITs found the
block at the aligned address only if it was already compiled, and
otherwise compiled one at the unaligned address.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pruning now only runs when the config was really read: when the memory
stick isn't reachable at startup (an unmounted SD card, or right after
boot), NativeInit falls back to defaults and the recent list is empty, so
every recent game's grant looked unused and was released.
Grants for the memory stick, the Games tab folder and pinned folders are
re-taken at every startup. That moves their persisted time up, so they're
the last the system drops if we ever reach its limit. The new time is
written out with the next grant change, which is also the only time the
system prunes.
Home screen shortcuts can't be enumerated, so the 64 most recently
created or launched are remembered in a new [Shortcuts] section of
ppsspp.ini, and their grants are kept. ShortcutActivity runs without the
config loaded, so it queues them in SharedPreferences until the next
startup saves them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Load() only queued the list for the worker thread, so reading it right
after loading the config could still see an empty list. Android's grant
pruning does exactly that, and would then treat recent games as
unreferenced, release their grants, and the queued clean would drop them
from the list as missing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An app can hold 512 persisted URI grants (128 before Android 11). Over
that, the system drops the oldest, and ours is usually the memory stick
folder. We take a grant for every file picked one at a time - games loaded
through the file picker, shortcut targets, sound effects - and never
released any.
At startup, log what we hold, and past half the limit release the oldest
single-file grants that neither the recent list nor a setting points at.
Folder grants are always kept. Home screen shortcuts need their file's
grant and can't be enumerated, which is why this only acts when it has to
and goes oldest first: the files it takes are ones the system would have
taken too, after the folder.
Co-Authored-By: Claude Fable 5.1 <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>
CommonWindows.h defines WIN32_LEAN_AND_MEAN, which leaves out what the
Direct2D/DirectWrite headers in draw_text_uwp.h need. Include
<windows.h> with just NOMINMAX instead, and keep std::max out of
DropShadow.h so it doesn't depend on which order windows.h comes in.
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>
CompositeDropShadow in Common/Render/DropShadow.cpp blurs a premultiplied
RGBA image's alpha (with an optional offset) and composites the image
over it. The atlas's AddDropShadow and TextDrawer::AddDropShadow are now
thin wrappers that pick the parameters.
The blur scatters only the non-transparent texels, which makes atlas
shadows 3-7x faster than the old gather-with-clamping version. Atlas
output is unchanged apart from rounding (alpha differs by at most 1).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The backend rasterizes coverage (or color, for emoji) as usual, then a
shared CPU pass pads the bitmap, blurs an offset copy of the coverage
and composites the text over it as RGBA8888. Blur and offset scale with
the font size and with DPI. The flag is part of the string cache key,
and the text is still aligned by its own size, with the quad grown to
fit the shadow.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Picking one boots vshmain.prx with the disc mounted, and only the file booted went into Recents
(minus vshmain.prx itself), so the disc never did.
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>
- Fixes#22737
Some video discs (Spider-Man) have a PSP_GAME holding only a firmware update. We reported them as
game discs too, and the XMB said "The disc could not be read". The type now comes from the fourth
field of UMD_DATA.BIN (G, V, A; the EUR Video Demo Disc says GVA), with the directories as a
fallback, and PSP_GAME without SYSDIR/EBOOT.BIN doesn't make a mixed disc a game. Identify_File uses
the same check, so these discs list as UMD Video.
Ejecting and inserting a disc from the pause menu without resuming in between left the old disc in
the XMB: callback notifications don't queue, so the insert's argument replaced the eject's before
the drive callback ran. The insert now waits until the callback has seen the drive empty.
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>
Key ups go to every screen, so keys don't get stuck, and that included
events that are a press and a release at once, like iOS's back swipe. So
with settings open from the pause menu, a back swipe made the pause
screen try to close too. It isn't on top, so finishDialog refused, but
the screen had already stopped taking input, and once settings closed it
was left on top, ignoring everything.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>