Files
overdrive/ps5-react/CHANGELOG.md
T

59 KiB
Raw Blame History

Changelog

Unreleased — Buzzheavier's file server

  • Anchor mode accepts Buzzheavier's file server, <server>.buzzheavier.com/d/<id>?v=<signed token>, which its Download button's hx-redirect answer names and which carries no file name (observed 2026-10-17; the file's name comes in its Content-Disposition). Its pages stay on the bare domain.
  • A captured link may be up to 2047 bytes (was 543, Orbit Store's limit): FileKeeper hands its file out through Datanodes' tunnel<n>.dlproxy.uk gateway as /download/<598-character token>?sig=..., about 675 bytes, which the scan cut short and so never returned. rejected links follow.

Unreleased — a vault's Vikingfile page, and a PKG's own title id

  • Page mode: prefix: 'https://vikingfile.com/f/' with the suffix / returns the Vikingfile file page, /f/<10-character id>, that a LinkVault vault opens. The app reads that page's public metadata and captures the file it names next, so a vault's Vikingfile download is bound to its exact filename and size, as a direct Vikingfile source's is.
  • Archives.inspect also returns title: for a PKG, the title id its content id names (the header's UP0001-CUSA01234_00-... at 0x40), so the app can tell another game's file early.
  • The browser's network process keeps the addresses of files it fetched earlier in memory it never clears, and a capture took one of them, another game's file, the moment it started. A capture now first fingerprints every link already in memory that names no game, before the app opens the page (the task stays queued until that pass ends and logs what it found), and only a new such link is taken after it. A link bound to the game, by its title id or Vikingfile's expected filename, is accepted however old. resume: true restarts after a refused link with those fingerprints.

Unreleased — 1fichier and Datanodes routes, and no stop on a host's own page

  • Anchor mode accepts the generated routes that carry no file name, adapted from Orbit Store 1.3.5's backend/providers.c: 1fichier's a-<n>.1fichier.com/<token> and Datanodes' tunnel<n>.dlproxy.uk/<8>/<name>?sig=<signature> gateway.
  • A host's own page is never taken for its file, even when it names the archive: LinkVault, MediaFire's www page, an AkiraBox path outside /download/, Vikingfile's /f/ and the /<12-character id> pages of Datanodes, Filekeeper and Datavaults. A LinkVault vault lists those pages, so the capture used to stop on one before the player picked a mirror.
  • BrowserCapture.capture takes rejected, up to six links the app already probed and refused; the payload scans past them, so a wrong candidate no longer ends the session (Orbit Store 1.3.5 keeps six). The deadline may be up to 300 seconds and the read budget is 3 GiB, enough for a LinkVault page's own check and mirror before the host's.

Unreleased — one capture for every browser-only provider

  • Browser capture also takes the file link from the browser for the hosts whose check only the console browser passes (LinkVault's mirror pages, Akirabox, Datanodes, Filekeeper), not just Vikingfile: prefix: 'https://' with the game's title id as the suffix returns the file link that carries that id in its own name, or, on a host known to serve files, one that names its archive freely. The payload's name check reads the last path segment only, so an advertisement that hides the real address in its query is not the file, and the app's one-byte range probe still decides whether the captured URL is the game's own file.

Unreleased — Vikingfile capture without the Download button

  • Browser capture finds the verified Vikingfile link as the page holds it once the verification passes, a JSON string with escaped slashes, so the user no longer selects Download and the advertisements around it. The scanner reads at about 8 MiB/s with a 1 GiB session budget, so it keeps looking through a slow verification; both changes follow Orbit Store 1.3.5.

Unreleased — download speed limit

  • Add Downloads.setRateLimit(bytesPerSecond) (native ABI v17, network.setRateLimit): every file download shares the cap, and a new value applies at once, to running downloads too. A capped download opens about one connection per MB/s and resumes its paused connections in turn, so none sits long enough to hit the 30-second low-speed cutoff. unlimited: true exempts a download.

Unreleased — comparing upscalers

  • <Video compare={{upscale, split}}> (native ABI v15, video.setCompare): right of split (0 to 1 across the picture) the picture goes through a second model, or none, so two upscalers show side by side on the same frame. The second model runs whole in the display frame its picture first shows in, on top of the element's own: a heavy pair drops frames while playing, a paused picture costs one run per model.

  • A model picked while the video was paused never ran: an upscaler started only on a new picture, so the paused one went on showing as it came. A model picked over a paused picture now runs on it once.

Unreleased — focus returns through stacked pages

  • Add useFocusActions(): focus, blur and focusedRect without subscribing to focus changes. A component that called useFocus() only to move focus re-rendered, with everything under it, on every D-pad move.

  • A page that had another opened over it (a detail page under a player) forgot where it was opened from once that one closed: focus coming back overwrote the page's return target with the closed layer, so closing the page too sent focus to whatever lay nearest, often a tab. Focus that goes back to where a closed layer was opened from now leaves the return targets of the scopes it lands in alone.

Unreleased — local time

  • Add DeviceInfo.utcOffset(time) (native ABI v14, device.utcOffset): the system time zone's offset from UTC in minutes at an instant, from localtime_r in the shared host. The runtime has no Date objects, so apps had no way to show a local clock short of hard-coding a zone. The PS5's libc gives the console's zone in the calendar fields but leaves tm_gmtoff 0, so the offset is the fields less the instant.

Unreleased — HDMI audio passthrough

  • <Video audioOutput>: 'pcm' (the default, as before) decodes the track; 'auto' sends AC-3, E-AC-3 and DTS (the core) at 48 kHz to the TV or receiver undecoded when its EDID lists the codec; 'passthrough' does so whatever the EDID says. Frames are packed as IEC 61937 bursts (ported from SlopFin, checked byte for byte against FFmpeg's spdif muxer) and sent on the HDMI output the console's disc player uses. The bursts are the clock, so pause, seek and buffering work as with decoded audio; volume has no effect on a bitstream, muted sends silence. The status has audioPassthrough. TrueHD and multichannel PCM output are not supported: decoded audio stays stereo.
  • AudioOutput.capabilities() (native ABI v13): the connected sink's audio formats from its EDID, the codecs it can be passed, and its most PCM channels, for an app to decide what to ask a media server for. The desktop preview reports known: false.

Unreleased — light on glass

  • Add the View styles roundedClip and mixBlendMode: 'plus-lighter' (patches/embeddedReact-rounded-clip-blend.patch). roundedClip: true clips children to the view's rounded border box, anti-aliased like a rounded background of that box; overflow: 'hidden' alone is unchanged. plus-lighter adds a subtree's drawing to what is beneath it, through the new optional backend add_rect, which the software backend implements and the damage tracker wraps. Neither has a Tailwind class; mix-blend-* now names the style in its error. Desktop tests only; not yet run on a PS5.

Unreleased — rows and the touchpad

  • FocusScope row: Left and Right stop at a row's ends instead of stepping into the rows around it (a blocked move), and a row entered for the first time starts at its selected element or its first, later at the one it had.
  • The touchpad navigates (input protocol v5): hosts report the DualSense's first finger to __ps5ReactTouch, and the focus runtime steps focus once per stretch of a swipe, locked to the axis it started on. Its moves carry source: 'touchpad' in navigation events. The PS5 reads the touch data beside the pad's sample queue with scePadReadState; not yet validated on hardware.
  • useFocus().focusedRect(): the focused element's rectangle on screen, to place a menu beside it.
  • Input scripts and ps5_drive.py script take swipe:DIRECTION:TRAVEL.

Unreleased — controller events and trigger effects

  • Add useControllerEvents(handler) and Controller.subscribe(listener): once per frame, from one shared read of the controller state, press and release (with heldMs) for every GamepadState button, L2/R2 at the host's half-travel press and the touchpad included, and trigger when L2's or R2's travel changes; {frames: true} adds a frame event for work that steps while something is held. Apps no longer poll getState() on a timer to see a release or a trigger's depth. useGamepad reads on the same per-frame reader instead of a 16 ms timer; intervalMs now defaults to every frame and, when given, throttles by frame time.
  • Add Controller.setTriggerEffect(trigger, effect) (native ABI v12, pad.setTriggerEffect, which replaces pad.setTriggerResistance): off, resistance, weapon (resists over part of the travel, then gives way) and vibration, through the generalized Pad::set_trigger_effect of patches/platform-trigger-effect.patch. setTriggerResistance is now a wrapper for resistance. The PS5 host logs each mode's first scePadSetTriggerEffect result. The PS5 host builds; neither the new modes nor the events have been run on a PS5 yet.
  • The desktop preview puts every key's button into Controller.getState(), not only the first six: Z and C hold L2 and R2 at full travel, or a light 0.4 with Shift, and fire l2/r2 as the travel crosses half, like an analog trigger; P is the touchpad.
  • Tools hold buttons and set trigger travel: ps5_drive.py hold BUTTON [ms], release BUTTON and trigger l2|r2 VALUE [ms], and the input-script steps hold:, release: and trigger: on both hosts. A held button with an action dispatches it as it goes down.

Unreleased — Options for apps

  • Options reaches every app as the options action, as upstream now delivers it (O and a controller's Start on the desktop); the PS button leaves, Escape on the desktop. The "optionsButton" app.json setting this fork added for the same is gone: an app that sets it builds as before, and the key is ignored.

Unreleased — resolution auto

  • The starter, and so every npm run create scaffold, uses "resolution": "auto" at 60 Hz. It rendered at 2560×1440 into a 1920×1080 surface, which the presenter scaled down 0.75× and a 4K TV upscaled again, softening text. Its pinned font sizes fit only the 2560-wide layout, so the bundler now picks them per layout.
  • "resolution": "auto" in app.json builds an app at 1920×1080 and 3840×2160, and the title picks the layout matching the console's video output at launch (4K and 1440p get 3840×2160). The output mode comes from sceVideoOutGetCurrentOutputMode_ on the main AvSetting bus. Generated bundles and assets moved to .build/<app>/generated/<width>x<height>/, and --resolution 1080p previews the small layout.

Unreleased — runtime refresh rate

  • Add Display.refreshRate() and Display.setRefreshRate(hz) (native ABI v11): an app switches between 60 and 120 Hz while it runs, among the rates app.json "refreshRates" lists. The PS5 host reopens the display at the new rate between frames and rebuilds the presenter; app state is kept, the TV resyncs. The desktop preview rebuilds its presenter and keeps the monitor's rate. Not yet validated on a PS5.

Unreleased — fixes

  • The glyph cache unlinked its oldest glyph from the wrong end of its recently-used list, so evicting it once the cache was full left the freed glyph listed as the oldest.
  • <Video resizeMode="contain"> left its letterbox bars undrawn, so layers under the element (a page's backdrop) showed through them on screen. The presenter now paints the bars black. shot: screenshots already composited them black, which hid it.

Unreleased — video upscalers

  • <Video upscale> doubles a picture shown bigger than its source with a network for animation before it is scaled to its box: 'artcnn' (ArtCNN C4F16, the luma) or 'anime4k-s', -m, -l, -vl, -ul (Anime4K Upscale CNN x2). Their mpv user shaders are embedded unchanged and run as GL compute passes, split over two display frames; needs compute shaders (the PS5).
  • 10-bit pictures from the PS5 decoder are read as low-aligned samples: HEVC Main 10 showed green.

Unreleased — less CPU per frame

  • The presenter keeps the framebuffer in a GPU texture: moved rects are blitted there, damaged rows are uploaded in merged bands through small buffer textures, and one draw composites the layers and the framebuffer on screen. On the PS5 the render thread's present went from 3.3 to 0.9 ms of CPU per frame and the process from 0.90 to 0.75 cores on a tour of a media browser app, at 59 fps. Desktop snapshots are unchanged; PS5 readbacks match the software screenshots within 1/255.
  • Hosts neither draw nor swap a frame that changed nothing, and sleep to the next vertical sync.
  • Patch Embedded React (patches/embeddedReact-slice-by-area.patch) so a repaint forks across workers once per commit, in bands of about equal damaged area (at least 128 Ki pixels each), instead of once per damage rect across every worker, and wakes only the workers with a band: the same tour went from 0.75 to 0.66 cores with as many slow frames.
  • Patch Embedded React (patches/embeddedReact-layer-radius.patch) so a rounded <Image layer> at full opacity is drawn by the host too, except a square of its radius at each rounded corner, and an image scrolled out of view no longer takes a layer slot from one in view. Making the cards of a media browser app layers saved no CPU on the PS5: their repaints are mostly other content drawn over and under the image.
  • The frame-time log reports cpu= cores and the render thread's CPU ms per frame (docs/ANIMATION.md).

Unreleased — cheaper mounts

  • motion elements run two effects each instead of six and add a context provider only when they pass new variant labels down. Their animations now start in a microtask after the commit, still before the frame paints, instead of in a passive effect each. Mounting a motion.View costs about a fifth less JavaScript.
  • Patch Embedded React's QuickJS bridge (patches/embeddedReact-host-props-cost.patch) so a host element copies only the props it was given and a plain style object is not re-flattened.
  • The frame-time log splits present into upload and draw and reports the kilobytes uploaded per frame (docs/ANIMATION.md).

Unreleased — render thread freed during downloads, RAR sets checked before decoding

  • Archives.inspect reads a RAR's first file header, then walks the next ones for about 100 ms (plus the header under way), instead of up to 4096 of them. Each header sits after the previous file's data, so on a large volume each is a disk read of its own: on a tester's console, inspecting a game archive while it downloaded at ~105 MB/s held the render thread for over 40 s. A header the walk misses is still refused before anything is extracted: by the preflight, or in a set extracted while it downloads (also checked with inspect), by the RAR worker.
  • FileSystem.removeAsync(path) removes a file, or a directory and everything in it, on a worker thread and returns a promise: a path already gone resolves, a failure rejects with the path and the system error. On a tester's console, fs.remove of a large download held the screen for 5 s.
  • rar-extract reads the end block of a set's last volume and the header of its first before decoding, headers decrypted with the set's password. A whole last volume that says more follow (or, in RAR5, has no end) fails at once with Incomplete RAR archive or missing volume., a first one that is not the first with Missing first RAR volume., both without the volume suffix. A set lacking its last volume decoded every other one first: hours on the console. While a set downloads, its first volume is checked at once and its last as soon as it arrives. Headers that cannot be read (no password, a wrong one) leave the reason to the extraction, as before. A last volume cut short (a download stopped early) fails as before, now always named: ... Volume 4 of 4 looks incomplete.

Unreleased — extracted file permissions

  • Archives extracted in the app (ZIP, 7z, TAR, and RAR when rar-extract cannot start) write folders 0777 and files 0666, and every extraction root is 0777, whatever the app's umask. They were 0700 and 0600: a game extracted that way and installed by ShadowMount froze the console at launch until its tree was set to 0777 by hand. rar-extract already wrote its files open.

Unreleased — verified sandbox roots

  • After filesystem elevation, find /app0, /download0 and /temp0 from a descriptor opened on each before the root change (the helper replaces only the root and jail), checked by device and inode, else by that identity among the title's /mnt/sandbox/