mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
fe32ef2c27cbfe43bb598fd82b425ec6658cf30e
24403
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fe32ef2c27 |
EE: flush the source pins before QFSRV's raw adjacent-source load
recQFSRV has a fast path for Rs == Rt+1 that reads the contiguous 256-bit
{Rt:Rs} window straight out of cpuRegs.GPR with an unaligned raw Ldr. Its
comment claimed the window was "memory-coherent after the flushes above",
but those flushes are mmiFlushReg -> _deleteEEreg, which reconciles
const-prop and the scalar/NEON slots and never touches the pins.
Under lazy-dirty the pin is authoritative for UD[0] and armStoreEERegPtrRaw
elides the canonical store entirely for a pinned lane-0 write, so a pinned
source's lower half in memory is routinely stale mid-block. Nine GPRs are
pinned, which makes four adjacent pairs both-pinned -- ($at,$v0) ($v0,$v1)
($v1,$a0) ($a0,$a1) -- plus eight more with one pinned operand: exactly the
register range a funnel-shift memcpy loop uses. Failure mode is wrong data,
not a fault.
Every other raw quad-load site fixes this by merging the pin into lane 0
after the load, which cannot work here because the read straddles two guest
registers. Flush the two pins the window covers instead -- it covers exactly
r[Rt] and r[Rt+1], since sa <= 15 over their 32 bytes -- via a new
armFlushEEGPRPin. That keeps the fast path (0-2 extra Str) rather than
falling back to the ~10-instruction temp-buffer path, and the flushed pins
stay authoritative.
This was the last raw address-of-GPR read in pcsx2/arm64/; the GE-M2e sweep
in
|
||
|
|
77a4a2366a |
Merge pull request #435 from pstef/tests
Add more tests
Console-conformance suites for EE MMI / FPU control registers / loads and
stores / SA and the performance counters / the data and instruction
caches, IOP loads, stores and branches, VU0 COP2 macro mode, VU1 EFU, and
VU sticky flags. Each case is scored against a PS2 hardware capture on the
interpreter and the JIT separately rather than against the other engine,
so a defect the two share is still visible. 1439 -> 1509 cases.
Two fixes ride along, each confirmed load-bearing by reverting it:
* psxJALR read its branch target out of Rs after writing the link, so
`jalr $t0, $t0` jumped to the link address instead of the old Rs.
Reverting fails BranchDelaySlotOrderingMatchesConsole alone.
* MTSA masks to four bits. The console says `mtsa 0x10` leaves SA at 0
and `mtsa 0xFFFFFFFF` leaves 0xF, and the x86 recompiler already
masked on both of its paths, so this aligns the interpreter with what
the JIT had been doing. Reverting fails four cases.
Twenty-seven DISABLED cases record console divergences PCSX2 has not
closed yet, each a tripwire that starts passing when the gap does. None of
them disables a case that used to pass. One is ours: cop2EmitFlagUpdate
builds the MAC flag from sign and zero only and clears U/O outright, so
arm64 COP2 macro mode raises no underflow or overflow flag.
|
||
|
|
539f4f7247 |
Tests: guard the EE cache2 host mapping behind MAP_FIXED_NOREPLACE
MAP_FIXED_NOREPLACE is Linux 4.17+; Darwin's <sys/mman.h> has no such macro and Windows has no such header, so the unguarded include plus bare use broke the macOS CI job outright — macos_build.yml builds `unittests` and hard-fails when the recompiler_tests binary is missing, making this a compile error there rather than a skipped test. __has_include for the header, #if defined for the flag, and MapAt returns nullptr when neither is available. Both callers already GTEST_SKIP on a null return, so the two tests that need a page at a chosen host address skip off Linux and nothing else moves. Verified by compiling this TU with the macro #undef'd: clean build, those two skip, the other eight pass. Also records why they skip on a 16K-page kernel: all four candidate addresses are 4K-aligned but none is 16K-aligned, so Asahi, Apple Silicon and some Android reject every one. That is not the loader collision the comment assumed. |
||
|
|
94d2e3f618 |
Android: note that background recolouring skips the fallback background
The animated library background is a GL displaced-grid mesh, and where EGL/GLES3 init fails (older Mali without float-texture filtering) the app drops to a simpler backdrop that carries no colour uniform. Recolouring therefore does nothing on those devices, silently, which reads as a broken setting rather than an unsupported one. Said so in both descriptions rather than one: RGB Cycle is its own row, and somebody who only enables that would never see a warning attached to the fixed-colour row above it. |
||
|
|
dd5235d74b |
Android: let the texture catalog collapse, and make it searchable
The catalog is 113 packs today and only grows, so the whole list folds away once someone has what they came for - the header shows the pack count and toggles, and the state is saveable so a rotation does not spring it back open. Search covers pack name, game title, serial and author, since any of those is a plausible thing to type. It uses the in-app keyboard like the library search rather than a raw TextField, so it works on a handheld with no touchscreen keyboard and still honours the use-system-IME preference. Requested by jpolo1224 after seeing the list at full length.2.6.5.4 |
||
|
|
9a6db38395 |
GS: stop injecting replacement textures whose upload failed
CreateReplacementTexture discarded the bool from every tex->Update(). The texture object exists either way, so a failed upload was reported as a successful replacement and the game was handed a texture with UNDEFINED contents. On screen that is indistinguishable from a replacement that never loaded — missing cursors, letters cut in half — which is exactly how #442 presents. Update can genuinely fail. On Vulkan an upload needs either room in the shared streaming buffer or a dedicated staging allocation (GSTextureVK::DoUpdate), and under memory pressure either can fail; OpenGL has no equivalent staging step, which is consistent with #442 reproducing on Vulkan and not on OpenGL. It is worse here than upstream because of our CPU BC decode: a BC7 texture becomes RGBA8 at four times the size, so the allocation that has to succeed is four times larger, on exactly the packs most likely to be huge already. Drop the texture on failure so the game falls back to its original, and log once with the settings that actually help (Precache off, lower render resolution). A pack that does not fit now degrades to "not replaced" rather than "corrupt", and the failure is visible in the log instead of silent. Mip failures are treated the same way: a garbage mip is still garbage, it just only shows at distance. Defect identified by bmdhacks. |
||
|
|
ffe161a576 |
Android: download texture packs from the shared catalog
The community texture catalog maintained by sashkinbro, browsable and installable in the Texture Packs screen, shared with us at his suggestion. No game needs to be running. Each pack names the serials it belongs to, so the install target comes from the pack itself rather than from whatever happens to be loaded - packs for a game you own sort to the top, the rest stay visible so you can fetch one before you own the disc. Files land in textures/<SERIAL>/replacements, which is where the core already looks. Deliberately not a port of upstream's download service. That buys pause and resume across a reboot at the cost of WorkManager, kotlinx-serialization, three manifest permissions, a receiver and a notification channel; the streaming download and zip handling here follow ShaderRepo, which already does this shape, and add no dependency at all. The catalog parser is rewritten against org.json for the same reason. Sizes drive the rest. The largest published pack is 2 GB where a controller skin is a few hundred KB, so: free space is checked as 2x the archive plus slack (peak is the zip plus its expansion), the SHA-256 is computed while streaming rather than in a second pass over gigabytes, and the install swaps into place atomically with the previous pack retained until the new one lands. Texture replacement is switched on after a successful install, since it defaults off and the files would otherwise sit on disk changing nothing - the most confusing possible outcome. The panel links the catalog repository so people know where the packs come from and where to contribute their own. Verified against the live catalog: all 113 entries parse, and the archive layout handling covers flat, <SERIAL>/, <SERIAL>/replacements/ and GitHub's own archive root. |
||
|
|
4d8701a49d |
GS: driver-bug database and per-driver shader workarounds
Ported from EmuCoreX with sashkinbro's approval. The GPU family alone was never enough to decide behaviour: the same Mali part behaves differently under ARM's proprietary driver than under Mesa PanVK, a lesson this tree learned twice the expensive way - the r44p1 DEVICE_LOST fix had to be gated on driverID rather than vendorID, and the 8 Elite push-descriptor disable likewise. Driver identity, version and a bug set are now recorded in a table, so the next device quirk is an entry rather than another bespoke branch. Twenty-five rules, with match confidence so a vendor-wide rule never overrides a driver-version-specific one. The shaders gain gpu_bitwise_and / gpu_bitwise_not / gpu_boolean_not, whose bodies the device emits: plain a & b normally, scalarized where the database says the driver miscompiles vector bitwise ops. A device matching no rule renders exactly as before. opengl/tfx_fs.glsl already carried this fix as IAND3/UAND2/UAND4 macros gated on GPU_PROFILE_MALI. Those are replaced by the shared helpers, but the GL macro is deliberately (workaround || IsMaliGPUProfile()): Mali reached through ANGLE or Panfrost resolves a non-ARM driver and matches no rule, so a database-only gate would have silently removed a fix those users have today. Widening only. SHADER_CACHE_VERSION 108 -> 109. Every TFX and convert shader's source text changed, so a blob cached from 108 no longer matches the source that produced it; without the bump users would get stale binaries and garbage rendering after updating. RewriteConstantLoads is deliberately absent - the database records BrokenConstantLoad with no workaround bits, so the macro would be permanently zero and the shader code dead. Shaders validated offline with glslc across 136 Vulkan and 296 GL/GLES permutations, every macro on and off; the failure set is byte-identical to the pre-change baseline. |
||
|
|
7c05cfd117 |
Android: make the settings usable again, and declutter the library menus
CollapsibleSection had stopped collapsing. Its own comment describes "a tappable header that shows or hides its content ... to de-bloat long settings lists", but the body rendered a plain title and called content() unconditionally, with initiallyExpanded marked UNUSED_PARAMETER. All 20 sections across Pad, Renderer, Performance and Fixes were therefore permanently open, which is why the settings became unusable in landscape: not too many options, but every option in a tab on screen at once. Restoring it shortens four tabs at a stroke. State is rememberSaveable so a rotation no longer re-collapses what was just opened, landscape being where the long lists hurt most; Renderer opens on Display & Resolution and Performance on Speedhacks so neither looks empty. The Fixes tab is now Advanced, and absorbed the GameDB fixes from Performance and the whole Recompiler tab. Both are debugging controls that the GameDB already applies per game, so they do not belong in the tab people open to change speed. Fourteen tabs become twelve; the search index and the per-tab reset ownership moved with the settings, so searching "Skip MPEG" lands on Advanced and resetting Performance no longer resets fixes it does not show. Skip BIOS now defaults on. "How do I skip the boot animation" is one of the most-asked questions in the Discord and desktop PCSX2 fast-boots too. Only fresh installs change: the saved JSON always carries the key, so nobody's boot behaviour is altered by an update. The library overflow menu drops from eleven rows to eight with separators. Open navigation goes entirely (the hamburger is on the same toolbar) and Game names in grid moves to App settings beside cover size and opacity, where every other library-appearance preference already lives. Setup / Change Folders and Exit MOVE to the drawer rather than being deleted - each had exactly one entry point, so removing them would have stranded anyone needing to change their ROM folder and left no in-app quit. Exit keeps its confirmation and gets a real power-symbol drawable: the "⏻" glyph is absent from most Android system fonts and rendered as tofu. About leaves the settings tab strip for the drawer, next to the other links. It is a read-only page that was costing a tab slot on every visit. Installed patches and cheats can be collapsed, so a pnach carrying hundreds of downloaded codes can be folded away. Reported by a community member on Discord. |
||
|
|
2c4f5500c7 |
iOS: give bottom-anchored OSD content the bottom inset
RenderOverlays fetched the safe-area insets with a null pointer for the bottom one, so it was written and never read. Only a single margin gets threaded down to the draw functions, and it carries the horizontal clearance, so anything anchored to the bottom edge — the settings string, the shader-compile spinner, the inputs overlay, the Bottom* performance positions — was being spaced off the screen edge by the notch inset instead of the home indicator one. In portrait the horizontal insets are zero, so in practice they got nothing at all and sat under the indicator. Threads a bottom margin alongside the existing one. Off iOS every inset is zero and the two are the same number, so nothing changes there. The message toasts were skipping the insets entirely while the performance overlay honoured them, which looked odd given they share the same four corners. They read them now too. |
||
|
|
0bb396793c |
iOS: take the OSD safe-area insets from the view, and actually deliver them
Two things were wrong with the clearance the OSD gets on iOS. It was pushed through Host::RunOnGSThread, which quietly does nothing when the GS thread isn't open. Every layout pass before a game boots hits that, and UIKit doesn't call -layoutSubviews again unless the bounds change, so the insets sat at zero for the whole session until the first rotation finally landed one. That is why two portrait screenshots of the same size showed the OSD block 36px apart: one was drawn before the push got through and one after. Nothing else re-pushes, so there was no way back. The insets are four floats read by the GS thread while it draws, so they're atomics now and -layoutSubviews sets them directly; only the window resize still needs the hop. And the value itself was a flat 18pt on all four edges of every device. A 750px iPhone has square corners and no cut-out, so that was 36 physical pixels of nothing; a phone with a notch wants better than twice that at the top in portrait. UIKit already tracks this per orientation, so use safeAreaInsets. It also gets the split portrait layout right for free — the Metal surface there only covers the top of the screen, so it reports the notch inset but no home indicator inset, which is what you want. |
||
|
|
2163f71671 |
iOS: stop the performance overlay resizing itself every frame
The shrink-to-fit added for portrait recomputed its factor on every frame from whatever the widest line happened to measure last frame. That makes the font size a function of the live text, and the live text never stops moving: the GS stats line gains and loses digits constantly, the resolution string flips from 0x0 Unknown to 512x448 NTSC during startup, the EE/GS/VU percentages churn. Each of those nudged the factor across an integer font-size boundary, and since the overlay is right-aligned, every row's left edge then moved by its own share of the rescale. Rows that hadn't changed a character were sliding around by ten to twenty pixels. Now the size is only reconsidered when the space it has to fit into changes, or when the set of enabled lines changes. In between it ratchets down once, far enough for the widest line to fit, and holds there. Rotating gets you the full size back because the available width changed; switching presets does too. With the Full preset in portrait on a 750px-wide screen it steps 24 -> 23 about three frames after boot and then never moves again. The frame-time graph was also scaling itself by the factor we asked for while reporting its width normalised by the size the text actually got. Those two disagree by up to a whole step, so with the graph as the widest element the whole thing never converged at all — it oscillated forever. Both now come off the realised size. Also folds the eleven-way flag test at the top into a bitfield, since the fit needs to compare line sets rather than just check for an empty one. |
||
|
|
ab4d9f9a16 |
iOS: bring back the device stats overlay line
The battery / heat / RAM line used to sit under FPS and Speed and went missing when the frontend moved onto the shared core: that commit dropped the fork's private copy of ImGuiOverlays.cpp for upstream's, and the draw went with it. The producer in ios_main.mm survived intact, so this is a missing consumer rather than a missing feature. The Should- getter still had its logging caller; it was the line and severity getters that were left with nothing reading them. Draws in both the refresh and the cached branch. The cache only rebuilds every tenth of a second, so leaving the cached branch out would flash the line once and drop it for the next nine frames. Severity 2 is the same red as a sub-95% speed reading and severity 1 an amber; a healthy device uses the normal OSD colour rather than a hardcoded white, so the line follows OsdColor like everything else does now. Two things had to be fixed to make it work. The early return at the top of the performance overlay bails out when all eleven perf flags are off, which is exactly what a Custom preset with only Device Stats ticked looks like, so the line would never have drawn there. And the line getter was returning a pointer into the cached std::string after the lock had gone, which another thread could reallocate mid-read; it copies into thread-local storage now. The C++ side also defaulted the flag to on when the INI key is missing, so a fresh install at preset Off showed the line anyway. It reads the preset the same way Swift does instead. |
||
|
|
434cbdda6f |
iOS: honour the OSD safe-area insets
The view has been computing a corner clearance on every rotation and handing it to ImGuiManager::SetOSDSafeAreaInsets since the Metal layer went in, and the setter has been an empty body with a "Phase 5" note on it the whole time. So the OSD only ever cleared OsdMargin, ten logical points, which is less than the rounded-corner radius on any recent iPhone and nowhere near the Dynamic Island. Top-Right is the default position on iOS and is exactly where the cut-out sits in portrait. Store the four values and let the overlay read them back. RenderOverlays now keeps the plain margin and the inset one separate: the horizontal margin picks up the worse of the left and right inset, and the first line starts below the top one. Only one horizontal margin is threaded through the six draw functions and widening that signature for this is not worth it, so taking the larger side is the honest approximation. Nothing on desktop calls the setter, so the insets stay zero there and this is a no-op. |
||
|
|
0f48358fda |
iOS: shrink the performance overlay to fit the window
Every line in the performance overlay is right-aligned as x = window_width - text_size.x - margin, with nothing stopping x going negative. A line wider than the window therefore starts off-screen and runs off the left edge instead of being clipped on the right, which is why the OSD looked chopped rather than cut off. Portrait on a phone is less than half the width of landscape, so anything that only just fitted in landscape breaks the moment you rotate. Measure the widest line each frame and, if it does not fit, draw the whole block at a smaller font next frame. The factor is clamped to 1.0 at the top so it can only ever shrink below the size the user picked with OsdScale, never grow past it, and it is floored to 0.5 so a very long line does not disappear. The accumulated width is normalised back to what it would have been unshrunk, otherwise each frame measures the previous frame's shrink and the overlay flips between two sizes forever. The drawn font size is floored to an integer so ImGui only ever has a couple of bakes alive. The frame-time graph gets the same factor for its padding, plot size and label gutters, or it would overhang a shrunken text block. The settings string is a different problem: at a couple of hundred characters it would need about a quarter size to fit on one line, so it shrinks a little and then wraps, and its baseline is anchored off the wrapped height so a two-line string does not hang off the bottom. There is also a hard floor on the x position now, which catches anything still too wide while the factor is catching up. None of this is behind a platform guard. It is plain layout correctness, and a desktop window narrow enough to overflow today gets the same fix; an iOS-guarded copy of identical logic would just read as a fork hack. |
||
|
|
caedd7d9ae |
Achievements: link the RA control panel from the library-progress setting
The web API key lives under "Keys" on the RetroAchievements control panel, which the old copy described in prose and left the user to find. Link it instead, with the site's own wording for where to look. The link is activatable from a gamepad as well as by touch: this screen is fully pad-navigable, so a link that only answered to a finger would be unreachable on a handheld. The label is a separate translatable string substituted into the sentence, and a translation that loses the placeholder still renders its own text with the link appended rather than dropping it. Requested by jpolo1224.2.6.5.3 |
||
|
|
05b52de95e |
Achievements: library-wide RA progress, including games never played
Progress could only ever be shown for a game that had been loaded, because
the core is only able to report on the game it currently has: set sizes for
everything else are not on the device at all. So a game you own but have
never launched showed nothing, which is most of a library.
Fetch it from RetroAchievements instead — and this needs two requests for
an entire library, not one per game:
- API_GetGameList (i=21 PS2, f=1, h=1) returns every PS2 set's size
together with its MD5 hashes. Cached on disk for a week; set sizes
change on the order of months.
- API_GetUserCompletionProgress returns NumAwarded / NumAwardedHardcore
/ MaxPossible per game id, paginated 500 at a time.
Matching is by disc hash, the only reliable key: RA carries no PS2 serials
and title matching would confuse regional variants and multi-disc sets.
Achievements::GetGameHashForImage computes it without booting, mirroring
the game-list scanner's open/detect/read/close over CDVDapi_Iso. It refuses
while a VM is valid — CDVD is a global, so repointing it mid-session would
swap the disc out from under the running game.
A game with a set and no unlocks now correctly reads 0/N rather than
nothing. Automatic syncs are limited to one a day and only run once a web
API key is present; the RA panel has a manual "Sync library" button that
ignores the interval. The web API key is a separate credential from the
login token, so it is entered once in the panel and trimmed on the way in
(the site's copy button brings whitespace, which would otherwise look like
the feature silently failing).
Also captures progress on every RA sound, so the figure moves as
achievements are earned instead of waiting for the slow poll.
Requested by Isshin.
|
||
|
|
1f949b6b0a |
Android: record achievement progress without the panel, split hardcore
The library showed no achievement progress for anything, because the only code that ever recorded it was AchievementsViewModel.refresh() — open the in-game achievements panel and the numbers were stored, never open it and nothing was. That is every game, for most people. Capture it from the play-session effect instead, where a live VM is guaranteed: once ~15s after the game starts running (RetroAchievements resolves the set over the network, so it is not there at boot), then on a two-minute poll so a killed process still leaves a recent figure behind, and once more on the way out of RUNNING to bank whatever was earned. The core can only be asked about the game it currently has loaded, so these are the only moments the numbers exist. Hardcore and softcore are now tracked and shown separately. They are different accomplishments — hardcore forbids save states, cheats and slow motion — so merging them would overstate what was earned. rc_client already reports both in the "unlockedMask" bits that Achievements.cpp emits; nothing on the Kotlin side was reading them. The list row leads with the hardcore count and a gold HC marker when any hardcore unlock exists, appends "+n SC" when softcore is ahead, and colours a completed set gold for hardcore against silver for softcore. Two traps worth noting for anyone touching the mask: -1 (field absent) has every bit set, so an unguarded test reports an unearned hardcore unlock, and Kotlin's infix `and` binds looser than `!=`, so the bit test needs its own parentheses or it does not compile. Reported by Isshin. |
||
|
|
e0e523dc98 |
Android: data backup, screenshot hotkey, disc CRC without booting
Backup/restore (App tab): one .zip carrying save states, memory cards,
artwork, per-game settings, controller profiles and patches, plus the
preference file — everything a reinstall destroys. Games and BIOS are
left out (they live outside the app and survive on their own), as are
texture packs, shader caches and logs: gigabytes of regenerable data
would make the archive impossible to hand around. Archives are portable
between the sideload and Play builds, since the preference file has a
fixed name and data-root entries are stored relative to whatever the
asset-copy root resolves to. Restore merges, then restarts, because
preferences are read once at startup and a live process would overwrite
what was just restored. Requested by Rei Ayanami; AetherSX2 has the
equivalent feature.
Screenshot hotkey: bindable to any controller button or stick direction,
capturing the emulated frame at internal resolution with no touch
overlay or OSD — which is precisely what the system screenshot button
cannot do. The PNG is then published to Pictures/ARMSX2 via MediaStore,
because snaps/ is app-private and Android 11+ hides Android/data from
the Files app, so a screenshot the user cannot find may as well not
exist. Requested by Rei Ayanami. Video recording is deliberately absent:
GSCapture dlopens versioned FFmpeg libraries that do not exist on
Android, and MediaProjection forces a consent dialog on every capture
start, so it could never be a one-press hotkey.
Disc CRC without booting: GameList::PopulateEntryFromPath already
computes serial and CRC from the image, so the Game Properties info tab
and the patch installer no longer demand the game be launched first.
Reported by GBSUPREMO, confirmed by Rei Ayanami.
Patch import naming: an imported "F0A6D880.txt" became
"F0A6D880.txt.pnach", which matches neither glob the core searches, so
the file listed as installed and could never load. Build the name from
the stem and uppercase the canonical prefix — the template is formatted
with {:08X} and WildcardMatch is case-sensitive, so a lowercase CRC is
just as invisible. Reported by Rei Ayanami.
Library list view now shows play time and last-known achievement
progress per game, keyed on a revision counter so the figures refresh
after a session instead of showing what they showed on the way in.
Requested by Isshin.
|
||
|
|
7fc83fd8cd |
iOS: flesh out the skin browser rows and states
The rows only ever redrew because the installer's single installingName was published and went nil at the end of an install. That was luck, not design, and it goes away now that the tracker is a set. The browser holds the skin library in @State like every other store consumer in the app, and the installed set is read once per body pass rather than from inside a lazily evaluated row closure. Pull-to-refresh forces a network fetch and an "Updated ... ago" line above the list makes it obvious whether one landed. The spinner and the fetch error were both gated on the list being empty, so once a first load succeeded neither could ever show again: a refresh looked like nothing was happening and a failed one looked like it worked. Both are ungated, and there are proper lines for an empty catalog, a search that matches nothing, and no connection. Rows carry the author and the download size, which were already in the manifest and unused, and search matches the author too. Installed skins sort first. Skins without a recommended layout say so rather than being hidden. An installed row's checkmark is a menu now with Reinstall and Remove, and Remove asks first — swiping was the only way to remove and there was no way at all to replace a skin with a newer copy. Both the swipe and the menu go through the same confirmation. The preview thumbnail and the error triangle have accessibility labels; they were unlabelled buttons before. |
||
|
|
0d6ac5c689 |
iOS: make the skin catalog refresh and the installer match on identity
Pull-to-refresh went through URLSession's default cache policy, and the manifest is served from raw.githubusercontent.com with max-age=300. Refreshing inside that window returned the cached copy without touching the network, so a skin published in the last five minutes simply was not there however many times you pulled. fetch takes a force flag now and the refresh gesture passes it, which is the literal "the menu doesn't update the skins" the report was about. A lastUpdated date comes back with it so the screen can say whether a refresh landed. The .task load and a refresh could also overlap: both raise the spinner, the first to finish lowers it while the other is still running, and a slow stale answer could land on top of a fresh one. Fetches are held in one task now, a new one cancels the old, and a cancelled load neither writes its result nor touches the spinner. Half the catalog was hidden. The list was filtered to skins carrying an iosLayout, which is 13 of the 26 published. The other 13 import fine, they just bring no recommended layout, so they are listed and the row says so. The installer decided a skin was installed by comparing the catalog name against the descriptor's displayName. It matches on catalogID now, in isInstalled and in uninstall, and uninstall stops swallowing its error with try?. Errors are keyed the same way and clear when the skin installs or goes away, instead of leaving a warning triangle next to a skin that installed fine. The single installingName became a set: with one slot, two taps meant the first to finish cleared the tracker while the second was still downloading, reverting that row to Get and inviting another copy. Uninstall and reinstall both push the library's selection back into SettingsStore.virtualPadSkin. Removing the selected skin used to leave that at .custom, which sends the pad at the old ControllerSkins/Custom folder rather than the stock art. zipURL and previewURL are static, return an Optional, and refuse a path that is not a safe relative reference — .. and / both survive percent-encoding, so a hostile manifest entry could point the download off the repo. The force-unwrap they had is gone with them, and so is the installer's catalog reference. |
||
|
|
6e0797da13 |
iOS: give imported skins a catalog id and reinstall in place
The downloader had no way to tell that a skin it installed is the one already in the library. It compared the catalog name against the descriptor's displayName, but importSkin takes the name from the package's own manifest.json and then runs it through uniqueDisplayName, so the two drift apart on any package whose embedded name differs from the catalog entry -- and several in the live catalog do. Descriptors carry a catalogID now, set to the repo-relative zip path rather than the display name, since the name is copy and the path is the resource. The field is Optional and the struct keeps its synthesized Codable. It has to stay that way: the library decodes all-or-nothing, loadLibrary swallows a decode failure with try?, and the next persist() would then write an empty library over the user's skins. importSkin takes a replacingSkinID so the downloader can offer Reinstall without piling up "Neon 2", "Neon 3". Order matters -- the new copy is imported first and the old one deleted only once that succeeded, so a dead download or a corrupt zip leaves the install the user has alone. The old id is passed to uniqueDisplayName as the name to ignore, so the reinstall keeps the plain name. Per-game skin picks are re-pointed at the new descriptor, not cleared. deleteImportedSkin nils them, which is right when a skin goes away for good and wrong when it is being replaced, so the repoint runs first and the clear then finds nothing to do. The old skin's linked layout preset is removed on reinstall too, because createPreset does not uniquify and every reinstall of Black would otherwise leave another preset called "Black Layout" in the picker. Anything still pointing at that preset -- the global one included -- moves to the new skin's layout before the delete. Installing a skin also stopped freezing the pad settings screen. importSkin decoded and re-encoded every PNG inline, 18 to 21 of them for a real skin, on whatever thread called it, which for the downloader is the main one. That loop now runs detached and hands back finished bytes; the writes stay put. Callers become async as a result. |
||
|
|
d4296f9fd3 |
iOS: repair the dead guard tests and stop two UI-thread config writes
Three follow-ups from the graphics live-apply work. The two iOS guard tests had been failing with FileNotFoundError since the core moved out of platforms/ios -- both still pointed at app/src/main/cpp/pcsx2 and app/src/main/assets. Dead tests read as coverage, so repoint them. Repointing the True Crime one turned up a real regression. It guards "mvuFlag: 0 # Fixes texture flickering caused by the VU Flag Hack" on the six New York City serials, and that fix is gone: the iOS build used to bundle its own GameIndex.yaml carrying it, and when we converged on the overlay only the gameFixes came across. The speedHack has been missing ever since, unnoticed because the test that would have caught it broke in the same move. Restored in the overlay, which is where our GameDB changes live now, and the test follows it there. The renderer picker stayed live with a game loaded. Returning to the menu only pauses the VM, so you could switch renderer, change any other graphics setting, and have the next apply take GSUpdateConfig down the reopen path -- a full Metal teardown under a loaded game. The picker already said "Requires restart"; now it means it. applyOsdPreset and setPerformanceOverlayVisible wrote GSConfig and EmuConfig.GS from the UI thread. Those flags are bitfield members sharing storage with the GS device-restart flags, so a read-modify-write off-thread can write back a stale neighbour and flip RestartOptionsAreEqual -- the same teardown, from a stats toggle. Both compute their values up front and hand the writes to the CPU thread. isPerformanceOverlayVisible reads the INI now rather than GSConfig, which the setter no longer updates synchronously. |
||
|
|
538421f65b |
iOS: fix graphics settings that never reach the running game
Align Sprite and Merge Sprite look stuck: turn one on and it stays on after you turn it off. The INI write is fine in both directions -- nothing ever tells the VM about it. A graphics setting only live-applies if its Setting<T> declares an onSet closure calling requestGraphicsApplyGuarded(), and most declare nothing. The C++ fallback in setINIBool/setINIInt covers a hardcoded allow-list with none of the user hacks on it. 33 keys are in neither and only take effect once some unrelated setting forces a VMManager::ApplySettings, or on reboot. The allow-listed ones were not really working either. The poke writes GSConfig from the UI thread, so when GSUpdateConfig runs it takes old_config from that already-updated GSConfig and every new != old guard is false: hw_mipmap and texture_preloading skip the texture-cache purge they are listed for, LoadTextureReplacements skips UpdateConfig and the hash purge, OsdShowGPU never reaches SetGPUTimingEnabled. So make it structural rather than extend the list, since the list is what rotted. Setting<T> now gives every EmuCore/GS key the hook by default and the 23 hand-written copies are gone; both poke helpers go with them, leaving one apply per key and nothing writing GS state off the CPU thread. Fourteen keys are not Setting<T> instances -- five plain didSets, and the nine homogeneous hardware fixes that live in a [String: Bool] dictionary -- so those call it from their write funnel, and resetGraphicsDefaults goes through that funnel instead of writing the INI beside it. Renderer keeps no hook: it is a restart option, so applying it live tears the Metal device down under the running game. Native scaling joins the texture-cache purge, being the one geometry hack that outlives the draw -- it swaps a target's texture for a downscaled one and pins m_scale to 1, so those targets stay downscaled after it is off. Also: MaskUpscalingHacks zeroes the sprite hacks below 2x while the UI only gated them on Manual Advanced Hacks, so at 1x they read on and did nothing. Say so. The Skipdraw footer claimed its changes need a reset; they do not. |
||
|
|
5031a4d2ba |
iOS: gate the JIT keepalive canary on the VM being parked
The canary flip from the keepalive fix drops execute on the arena's first page, but the didBecomeActive prewarm re-runs it on every app switch while the CPU thread is executing the dispatcher on that exact page -> Instruction Abort. The old 'every caller runs parked' claim was only a comment; now ValidateJITAlive asks the scene layer for real VM/worker state and skips the probe (canary=skipped-vm-active) while anything JIT is running or still initializing. A live VM is its own proof the grant works. |