46 Commits
Author SHA1 Message Date
Knutwurst 73d317a04c Continue failed pieces where they stopped
A piece that failed was fetched again from its first byte. With pieces
of several GB, a connection that drops every few minutes never got one
finished. A retry now asks for the rest with a Range request; with
verification on, the part on disk goes into the hash first (pause and
cancel interrupt that). A server that sends the whole piece instead
overwrites it in place; one that sends another range, or 416, gets the
piece from the start without spending an attempt. A continued piece
that fails its hash is fetched whole once more before the package
counts as corrupt.

Also:
- A reply longer than its piece no longer writes into the next piece.
- Oversized replies and redirect loops are not retried as network errors.
- The diagnosis shows the no-space text for a job that ran out of space.
- README names Quad9 and Orbis Patches.
2026-10-17 20:40:40 +02:00
Knutwurst c5c0019438 Retry update lists and manifests; harden DNS and the version list
- Update lists and manifests get two more tries after a network failure
  (2 s, 5 s pauses), so one dropped connection no longer fails a
  download before its first byte.
- While an aged-out DNS answer is renewed, workers keep using it instead
  of waiting for the renewal.
- DNS replies must repeat the question that was asked.
- Waits between piece retries are re-checked every second, so a console
  clock set back during a download can't park them.
- A PS5 version without a readable firmware requirement is never offered
  as compatible.
- The card says when a download waits to retry after a lost connection.
- A cover whose icon fails falls back to initials without an inline
  handler; a malformed #get link no longer throws.
2026-10-17 20:14:10 +02:00
Knutwurst 23e4dda1e2 Redesign game cards and the Other versions dialog
Cards show installed, update and newest version side by side, and
problems as notes with their action. Other versions became a button.
The dialog groups versions into available, needs newer firmware,
installed and older (folded), with larger type throughout. Text is at
least 13px, buttons 44px tall, and every control has a focus ring.
Phones get two status tiles per row and full-width card content.
2026-10-17 15:19:00 +02:00
Knutwurst 4dd254c20d Accept single-piece PS4 links, Orbis error text, natural UI terminology
A PS4 patch that fits in one piece is listed by Orbis as "<name>.pkg"
without a number; its manifest is "<name>.json" in the same gs2 folder,
like split packages. A wrong link on a PS4 game now explains Orbis' "Copy
all links!" instead of asking for Prospero's SC link.

All translations were reviewed against per-language glossaries (PS5 and
common app wording): e.g. German "Herunterladen" instead of "Holen",
status texts as "Wird ... ", requirements as "erfordert FW".
2026-10-17 11:43:05 +02:00
Knutwurst 76d40a5327 Translate the UI into ten languages
The UI follows the browser language, which on the PS5 is the system
language, and Settings has a selector to override it per device (kept in
localStorage). All UI text moved into web/i18n.js (306 keys) with English,
German, French, Spanish, Italian, Portuguese (Brazil), Japanese, Simplified
Chinese, Korean and Russian; missing keys fall back to English.

Log entries keep their key and values, so the Logs view shows them in the
chosen language while the diagnostic report stays English for issues.
2026-10-17 11:33:21 +02:00
Knutwurst a8aace1e55 Pick another patch version from Prospero (PS5) or Orbis (PS4)
An optional "Other versions" link on a game card lists every version the
patch site knows: Prospero Patches for PS5, its sister site Orbis Patches
for PS4 (same API). Versions newer than the installed one that run on this
firmware can be picked; older ones are shown but not offered, since
installing an older patch over a newer one is untested.

Picking a version opens the existing guide for exactly that version: QR
code to the phone, copy the link on the site, paste it. Orbis offers the
piece links of a PS4 patch ("Copy all links!"); they sit in the same gs2
folder as the manifest, so PatchDL derives "<name>.json" from any
"<name>_<n>.pkg" and fetches it over http like other PS4 manifests. The
size check uses the size the list showed for the version picked.

The Prospero lookup behind "Old FW patch" now uses the same list.
2026-10-17 11:18:38 +02:00
Knutwurst 8053f5395b Mark sideloaded apps, explain failed downloads, diagnose from the UI
Sideloaded apps
- A Sony-signed package installed with another tool ("fake": true in
  app.json, but no PatchDL loopback URL) stays Official but is marked
  Sideloaded, and its Updates switch starts off, so Update all skips it.
  Switching it on is saved separately, so a switch saved earlier can't
  turn it on. The card says an update replaces this exact version.

Failed downloads
- When the package list can't be fetched, the download job records why
  (DNS, connection, timeout, TLS, redirect, HTTP status, not a manifest)
  with the curl and HTTP codes; the card says so with a Details link.
- Details runs /api/netcheck?title=<id>, which walks the game's update
  list and manifest the way a download does and reports PatchDL's own
  DNS answer for each host. The dialog shows a checklist and a report to
  copy into an issue; on the PS5 a QR code opens it on a phone.
2026-10-17 10:39:49 +02:00
Knutwurst bde585eab7 Add a Buy me a coffee link with a QR code to the UI
The sidebar footer and the end of Settings open a dialog with a QR code
for PayPal, so it works from the TV, plus a direct link for phones.
2026-10-16 19:04:03 +02:00
Knutwurst 72b9136ff5 Say PS5 and PS4 games in the README and the UI header 2026-10-16 14:25:39 +02:00
Knutwurst e72c25a56e Update PS4 games too
PS4 patches install on the PS5 through the same loopback route as PS5
updates; the console puts them in /user/patch/<id>/ next to the game.
Tested with Bloodborne (CUSA00207, 01.00 to 01.09) on FW 11.60.

- Installed version: PS4 titles have no CONTENT_VERSION in app.db; use
  APP_VER, which includes an installed patch.
- Update check: Sony lists PS4 patches at a gs-sec URL derived from the
  title id with HMAC-SHA256 (the well-known PS4 update key). A PS4
  package's system_ver is a PS4 firmware in decimal and never blocks it.
- Download: PS4 manifests are served over plain http on gs2 only (its
  https edge has no Sony certificate); allow http for that host's
  manifests, keep everything else https. Piece hashes are SHA-1 there, so
  verification picks SHA-1 or SHA-256 by length.
- After an install, check /user/patch/<id>/patch.json instead of app.json.
  PS4 installs fetch no .crc, so a minute of quiet after reading ends one.
- The UI shows a PS4 badge instead of "not supported".

Also: a list reload no longer ends an install on the card early. The
console writes the new version during the install, and the card briefly
showed Delete before the install poll put it back.
2026-10-16 13:56:41 +02:00
Knutwurst bd08ad70c4 Keep the indeterminate install bar moving across card redraws
Cards are rebuilt on list updates, and each rebuild restarted the bar's
animation. A negative animation-delay taken from the clock lets a new bar
continue where the old one was. The sweep is linear now instead of easing
in from a standstill, and it stops for prefers-reduced-motion.
2026-10-16 12:08:41 +02:00
Knutwurst 289688f727 Rescan on its own, track installs, verify and clean up packages
Library
- Rescan when /user/app or Sony's app.db changes, after an install, and on
  the Refresh button (which also asks Sony for updates again). Later scans
  keep a handle to the system root and use relative paths instead of
  swapping the process root while other threads run; app.db is copied out
  first because SQLite resolves relative paths with getcwd(). The scan
  thread gets a 1 MB stack (the default made reads fail with EFAULT).
- Take the newer of app.db and appmeta param.json as installed version:
  app.db lags behind until the game is started.
- A package header decides "fake"; the installer's "fake": true marker
  only says the package didn't come from Sony's CDN, and PatchDL's own
  installs set it too.
- PS4 titles are listed as not supported.

Downloads
- Fetch the manifest's .crc next to the package; the console asks for it.
- Keep the sidecar (with the version) after a download completes, so a
  restart still knows the package is whole.
- A package already on disk without a matching sidecar is hashed against
  the manifest piece by piece; only pieces that don't match are fetched.
  Install refuses an unverified package.

Installs
- One install per title: a second request (another tab, a double click)
  gets install_running; an installed version gets already_installed.
  Download, Cancel and Delete wait until the console is done reading.
- Progress comes from the bytes PatchDL serves to the console (distinct
  ranges), since Sony's status query fails for these installs. Phases:
  waiting, reading with percent, finishing, done or stalled. Each title
  is tracked on its own.
- After an install: check that the installed version matches and the new
  app.json names this package and its size. With "Delete PKG after
  install" on, delete the package only then. The setting existed but was
  never read; the old cleanup trusted app.db alone and deleted packages
  that were never applied.

UI
- Settings list every package on the console with size, version and
  whether it is installed, newer or for a game that's gone, with Delete.
  The storage tile links there.
- The list reloads quietly when the server reports a change.
- Bigger covers on wide screens, "Updates on/off" on the switch, no
  "Shared master" badge, no endless auto-install retries.
2026-10-16 12:04:49 +02:00
Knutwurst 70863fb54e Show the newest version and its firmware on every card again
Since the version line rework, "Newest … · needs FW …" only appeared when
the newest patch was newer than the installed one, so up-to-date games,
fake packages and Shadowmount titles lost the firmware information that
0.0.8 showed. It is back whenever Sony reports a newest version, unless
the Update entry already names the same version.
2026-10-15 22:16:20 +02:00
Knutwurst b115c0fd4b Drop leftovers of the "shared master can't install" assumption
/api/pkgmeta now calls a package installable when its embedded title id
matches the installed game, which is what AppInstallPkg goes by, instead
of flagging every shared-master package. The unused UI message for the
old refusal is gone, and web/README.md describes the current policy:
Shadowmount, unknown and fake titles get neither download nor install,
and shared-master packages install like any other.
2026-10-15 22:05:44 +02:00
Knutwurst 7ae09ce571 Allow installing shared-master patches again
The UI hid Install for games whose patch pieces live under another
region's title id. That block predates the switch to AppInstallPkg on the
manifest-assembled package, which carries the installed game's own
content id; Dead Island 2 (EU disc, US master pieces) updated fine that
way. The server has a branch for exactly this case, so the UI now offers
Install again and only keeps the "Shared master" label.
2026-10-15 21:58:06 +02:00
Knutwurst 1739096aa1 Treat Shadowmount titles like fake packages: no download
A mounted image can't take a patch, so a download leads nowhere. The
server now refuses downloads and Prospero links for Shadowmount titles
and skips the Prospero lookup for them; the UI lists them under Not
supported without a button, the same as fake packages.
2026-10-15 21:53:46 +02:00
Knutwurst 4379ac3f59 List download-only titles under Not supported, soften the human-check hint
Shadowmount titles can't take a patch, so they no longer sit under Updates.
They move to Not supported and keep their download: Download for Sony
updates, Old FW download for patches from Prospero. Fake packages stay
without any download.

Prospero's human check usually passes without the user noticing, so the
guide now only asks to confirm it if one shows up.
2026-10-15 21:48:47 +02:00
Knutwurst 91cfb2cd02 Flag region patches, check free space, slim the console guide
Disc and store installs whose update Sony serves from another region's
master package can't be installed from homebrew; Sony's installer refused
them in testing. They now sit under Not supported with a short reason
instead of offering a download that leads nowhere.

Before preallocating a package, PatchDL now checks that it fits on /data
with 2 GB to spare and reports how much it needs. The Prospero link check
runs the same test, so the phone gets the answer right away.

The PS5 browser can't copy links, so on the console the old-FW guide only
shows a large QR code and the link for the phone.
2026-10-15 21:31:00 +02:00
Knutwurst fbe984da87 Offer the newest patch that fits the firmware via Prospero Patches
Sony's version.xml only lists the newest patch. When that one needs a
newer firmware, PatchDL now asks Prospero Patches for the version history
and offers the newest patch that still runs on the console. Prospero only
hands out download links after a human check, so a guide with a QR code
moves the user to a phone, where they copy the SC link. PatchDL derives
Sony's manifest from that link and downloads like an official update.

The game list now groups by what the user can do: Updates, Up to date,
Not supported. Update all read a nonexistent state.games and did nothing;
it now uses state.titles.
2026-10-15 21:21:51 +02:00
Knutwurst f6b0bba49a Show game icons and label the firmware a patch needs
- The scan opens each game's icon0.png (app.db icon0Info first, then
  /user/appmeta and the title's sce_sys) while it can still see the
  whole filesystem, and keeps the fd. GET /api/icon/<title_id> serves
  it with a one-day cache. Cards show the icon and fall back to the
  initials; loaded icons are reused across re-renders.
- "Latest x · FW y" read like the console firmware. It now says
  "needs FW y".
2026-10-15 11:08:49 +02:00
Knutwurst ca5977f872 Show the latest official version for fake packages
The update check only reads Sony's version.xml, so it is safe for fake
packages too. Their cards now show the latest version and the firmware
it needs. Download and install stay blocked.
2026-10-15 10:58:42 +02:00
Knutwurst 04d27a9fd5 List only games, keep fake packages visible, sort by name
- Scan only PPSA and CUSA title ids. Homebrew with made-up ids (the
  PatchDL tile, loaders, file managers) no longer shows up as a game.
- Installed fake packages carry "fake": true in app.json, the same
  marker homebrew uses, so they vanished from the list. Titles with an
  app.pkg now stay listed and count as fake when either the marker or
  the package header says so.
- Sort the game list by name.
- "Updatable" only holds titles PatchDL can install. Download-only
  titles (shadowmounts, shared-master patches) with an update move to
  "Can't update" and show "Update · download only".
2026-10-15 10:53:29 +02:00
Knutwurst 9253235a0d Classify installs by package signature, block fake packages
The scanner treated every title with an app.pkg as official unless its
app.json mentioned a URL. That let fake packages through as official, so
PatchDL offered and tried to install Sony patches on them, while disc and
store installs whose app.json lists CDN pieces ended up as unknown.

Read the app.pkg header instead. A PS5 finalized image (\x7FFIH) is
retail when byte 0x05 has bit 0x80 set; a PS4 container (\x7FCNT) is
retail when bit 0 of byte 4 is set. Retail installs count as official,
fake-signed ones as the new "fake" source, unreadable headers stay
unknown.

Fake packages are listed with a "Not supported" label. PatchDL skips
their update check, and the server refuses download, install and the
enable toggle for them. The titles JSON reports pkg_sign for diagnostics.
2026-10-15 10:21:36 +02:00
Knutwurst 664ad3d4fd Show "Needs FW" when a newer patch exceeds the console firmware
A title whose installed version matched the newest compatible patch was
reported as up to date even when Sony offered a newer patch that needs a
higher firmware. It now reports incompatible_fw, so the UI shows
"Needs FW x.xx". The UI drops stale job flags for that state too.

Fixes the Final Fantasy VII Rebirth case in #4.
2026-10-15 10:12:20 +02:00
Knutwurst 74ec23aa7e UI: label queued downloads as Queued, plainer header and DNS wording
Jobs waiting for a free slot showed the green Downloading pill, same as
the one that was actually transferring. They now show a neutral Queued
pill. The header eyebrow reads "PS5 update manager" and the DNS tile
says the system DNS is left untouched.
2026-10-05 21:15:26 +02:00
Knutwurst 2bc15cbaa2 UI: Updating now means only the queue; active downloads stay in Updatable
Previous behaviour moved a game out of Updatable the moment it entered
the pool, which made it disappear from the list you came to watch. The
queue chip now means literally that: jobs in state="queued" waiting
for a free pool slot. Active, paused and installing jobs stay under
Updatable so you don't have to switch tabs to see the thing you just
told to download.

Implementation: reconcileFromJobs stamps the raw j.state on the title
as g._jobState; gameCategory routes only "queued" to Updating, every
other live state falls through to the existing Updatable branch.
2026-06-24 22:00:42 +02:00
Knutwurst 81d6f0e332 UI: global download banner above the status strip
A sticky-feeling banner appears whenever any job is queued or active.
It shows the eyebrow (Downloading / Queued), the current title's name,
a "3 of 9" chip when more than one job is in this batch, plus
percent, speed, and ETA. A thin gradient bar runs edge-to-edge along
the bottom so the at-a-glance state matches the per-card progress.

Batch counting: jobs with state in {queued, active, done} make up the
batch; done count + 1 is the current position. When all queued jobs
finish and roll out of the pool list the banner hides on the next poll.

While the active job's manifest is still being fetched (no total_bytes
yet) the bar runs an indeterminate sweep so the user isn't staring at
a frozen 0%. Speed comes from the existing per-job smoothed estimate
in reconcileFromJobs so the banner shares one source of truth with
the per-game tiles.

Render hooks: renderGames() and applyDownloadProgress() both call
renderGlobalStatus(), so every refresh path keeps it in sync without
adding a separate timer.
2026-06-24 21:55:28 +02:00
Knutwurst d0ca22d42d UI: pull version from /api/status instead of hardcoding it
The sidebar tag was a literal "v0.0.3" string in index.html, so a
release bump left the running UI lying about which build it was. Server
now publishes PATCHDL_VERSION via /api/status; renderStatus() drops it
into a #brandVersion span. No more chasing a hardcoded version on every
release.
2026-06-24 21:19:38 +02:00
Knutwurst a1d035313e Release 0.0.4
Update all skips shadowmount titles. They pass the download policy but
not the install policy (their app slot has no real source medium), so
sweeping them in would burn tens of GB on a download AppInstUtil would
refuse. Single shadowmount downloads via the per-title button still work
for the "pop the disc in later" workflow.
2026-06-24 20:59:47 +02:00
Knutwurst bffa9b2a3f UI: separate Updating filter for queued/active/paused downloads
Updating gets its own chip so Updatable shows only games the user could
still trigger. Anything mid-flight — queued, actively downloading,
paused with a partial on disk, or installing — moves to the new bucket.

Implemented in gameCategory(): downloading is true for both queued and
active jobs, so wartende Downloads (the user's words) show up in the
same list as the one actively transferring.
2026-06-24 20:57:17 +02:00
Knutwurst 3f4c4a15f5 UI: default to Updatable filter, add Update All, move All to the right
The Updatable chip is now the first segment and selected on load — the
common case (looking at what needs an update) doesn't need a click. All
moves to the right end of the strip.

Update All queues a download for every game whose status is "available"
and whose source/policy allow it (skips downloading/downloaded jobs).
With install_after_download on, the existing auto-install pipeline
picks each finished download up automatically.
2026-06-24 20:51:11 +02:00
Knutwurst 983f39fa89 Harden AppInstUtil install path and status tracking 2026-06-24 17:19:34 +02:00
Knutwurst 986ff00c36 Persist resume state every piece; replace conn field with a stepper
Resume: write the sidecar after every completed piece instead of batching
every 8. Each piece's bytes are already fdatasync'd and the sidecar write is
a tiny atomic tmp+rename, so an unclean kill now re-downloads only the pieces
still in flight, not a batch of up-to-8 already-finished ones. Drops the now
-unused 'unpersisted' counter.

UI: the "parallel download connections" control is now a stepper — two large
54px -/+ buttons around a tabular value, in a row beside its label, instead of
a full-width number field for a 1-16 value. Big targets and a clear green focus
ring suit controller navigation (the UI is driven by the PS5 pad via the home
tile). Tapping -/+ updates and auto-saves that field alone (debounced), applying
live on the server.
2026-06-24 12:10:29 +02:00
Knutwurst 6024dbfc5d Apply the connection-count setting live, without a payload restart
The pool now spawns the full worker set at startup and gates each worker by
its slot against a live active_conns limit, instead of spawning exactly
max_connections threads once. Saving a new value in Settings updates the
limit and broadcasts: idle workers wake to pull pieces, and a lowered limit
parks the extra workers after they finish their current piece. No restart,
and no thread creation/teardown at runtime.

Verified on device: max_connections changed 4 -> 8 -> 16 -> 4 through the API
while a download stayed active throughout. (Throughput did not scale with
connections on this CDN, which caps aggregate bandwidth per source IP; 4 is a
sensible default.)
2026-06-24 11:36:48 +02:00
Knutwurst 21f6bd61ad Download patches over a connection pool with a queue and resume
Replace the single sequential transfer with a pool of N worker threads
that pull pieces of one manifest in parallel, lifting the per-connection
~7 MB/s ceiling. One job runs at a time; the rest queue. The connection
count is configurable (1-16, default 4) and applies on the next start.

Resume is tracked per piece in a sidecar bitmap that survives a reboot,
and a one-time migration recognises a partial written by the old
sequential build (a piece-aligned contiguous prefix on disk) and marks
those pieces done so an in-progress download is not restarted from zero.

Pause keeps the partial; Cancel deletes it. Both, plus Resume, are
available at any point in a download's life.

Concurrency review fixes folded in:
- a job is published as the active (claimable) job only after its
  manifest/state/fd are attached, so a half-built job can no longer be
  settled to "done" before any bytes are fetched
- cancel/pause during the admit I/O window only flag the job; admit_next
  is the sole finalizer, closing a use-after-free and a lost-pause race
- resuming a paused job frees the stale per-job buffers and zeroes the
  committed counters before re-seeding, fixing a leak and a double-count
- the background version.xml thread is joined on shutdown before the
  title list is freed
- verify_downloads is snapshotted under its own lock before the pool lock
- the web UI keeps Resume/Cancel after a failed transfer and bounds the
  local "downloading" bridge flag so a card cannot wedge
2026-06-24 11:12:25 +02:00
Knutwurst 01eaef79f2 Add pause/resume, within-part byte-range resume; bump to 0.0.3
Split the single morphing button into a green/amber play-pause (Update →
Pause → Resume) and a red stop (Cancel). Pause aborts the download but
keeps the partial (resumable); Cancel aborts and deletes. Backend gets a
separate pause flag distinct from cancel.

Resume now continues WITHIN a part: the partially-written piece is fetched
from its last byte via an HTTP byte range (with a safe fall back to
re-fetching the whole piece if the CDN ignores the range), instead of
re-downloading the whole part. A title is resumable as soon as any bytes
are on disk.

Version bumped to 0.0.3 (no release tagged).
2026-06-24 09:47:44 +02:00
Knutwurst 66e4912485 Resume interrupted downloads across a reboot
An interrupted download (cancel excepted) now keeps its partial package on
disk instead of deleting it, and records the manifest it belongs to in a
sidecar (state.json). On the next start the title is flagged resumable and
the UI shows a "Paused — X downloaded" note with a Resume button.

Resume refetches the manifest, skips every piece already fully on disk, and
re-fetches only the one partially-written piece (piece-granular, no HTTP
range needed), appending the rest. The fresh-download path is unchanged. A
partial belonging to a different/older manifest is dropped and the download
starts clean; a corrupt (failed SHA-256) download is not kept.

Survives a reboot: a killed payload runs no cleanup, so the partial and its
sidecar persist under /data/patchdl until resumed, completed, or deleted.
2026-06-24 08:58:32 +02:00
Knutwurst 3f40dc1d7c Rework the web UI: tile-based progress, view nav, responsive
Replace the single-page layout with a view-based one (Games / Settings /
Logs via the left nav) and fold all download UI into the game tile — the
separate download queue is gone.

Per tile while downloading: an in-tile progress bar with auto-scaled size
(B/KB/MB/GB/TB), live transfer speed and ETA from the poll deltas, and a
green "Downloading" marker. One fixed-width action button that no longer
reflows with its label: a blue Update/Download/Install that morphs into an
amber Cancel while the download runs, plus a ghost Delete for a finished
package.

Settings and Logs moved to their own pages. Mobile-first responsive layout
(rail collapses to a top bar, icon-only nav, single-column tiles, >=44px
touch targets, 16px inputs). Real free space (statvfs) is shown auto-scaled
in the rail and status strip.

New "Home-screen shortcut" toggle (default on, persisted as home_shortcut
in config.json). The actual PS5 tile install is not wired yet: it needs a
prebuilt deeplinkUri stub PKG installed via sceAppInstUtil.

Fixes from an adversarial review pass:
- Reconcile in-flight downloads from /api/downloads onto the cards, so
  progress + Cancel appear after a reload or a download started elsewhere,
  and stop the per-poll full-grid rebuild.
- Clear the downloaded flag on install and once the server reports the
  title up to date, so a patched title no longer shows Install/Delete
  forever.
- Zero a stale speed/ETA if the byte counter goes backwards.
- a11y: nav buttons keep an accessible name when the label is hidden on
  small screens; visible focus ring on the search box; filter group is
  role=group with aria-pressed; drop the noisy grid-level aria-live; fold
  the transient "checking" state into a visible filter bucket.
2026-06-23 21:48:54 +02:00
Knutwurst ea1328de79 Add optional SHA-256 verification of downloaded manifest pieces
Each Sony manifest piece carries a SHA-256 (hashValue). When the new
"Verify downloaded pieces" setting is on, every piece is hashed while it
streams to disk (OpenSSL EVP, already linked) and compared against the
manifest value; a mismatch aborts the download, deletes the partial, and
reports piece_verify_failed instead of handing a corrupt 60 GB package to
the installer.

Off by default: TLS already protects the bytes in transit and the PS5
installer verifies the whole packageDigest before applying, so this is a
fail-fast belt-and-suspenders check. It is also unverified on hardware
yet, so it stays opt-in (persisted in config.json) until confirmed
on-device; the hex compare is case-insensitive since Sony mixes cases.
2026-06-23 18:08:57 +02:00
Knutwurst 9006965a75 Add download cancel/delete and harden the patch pipeline
Cancel a running download (the worker aborts mid-piece and the partial
file is removed) or delete a finished package, from the queue or the
title card. The progress callback now returns an abort signal that
reaches libcurl and the manifest merge loop.

Manifest merge: bound the piece scan to the "pieces" array so a later
"url" key (e.g. playgoChunkCrcUrl) can't be appended as a bogus piece,
and require each piece's fileOffset to match the bytes written so far so
an out-of-order manifest fails instead of silently producing a corrupt
package.

Report real free space on the download partition via statvfs; it was a
hardcoded 0.

Fixes found in review:
- scan: bound the SFO entry table to the bytes actually read and require
  the key to be NUL-terminated before strcmp (OOB read on a crafted
  param.sfo from a shadow-mounted dir).
- proc: bound the kinfo_proc walk and the name compare to the record and
  the buffer.
- install: publish the API probe under the lock (data race with the MHD
  worker thread) and initialize rc2.
- verxml: reject a truncated attribute value instead of returning it as
  valid.
- web: keep download/install/downloaded flags across a refresh, stop the
  queue poll only after repeated empty results, coerce the progress
  number, and treat a cancelled download (HTTP 200, ok:false) as
  not-downloaded.
2026-06-23 17:52:29 +02:00
Knutwurst 510f199b89 Handle target-aware patch installs 2026-06-23 17:03:51 +02:00
Knutwurst 01464ae88a PatchDL 0.0.1: scan, resolve, download and install PS5 game patches
Standalone PS5 payload with an embedded web UI on :12880, no etaHEN.

- Scans installed titles and classifies the source (genuine install,
  ShadowMountPlus mount, preinstall, unknown) from the on-disk layout.
- Reads name, installed version and the Sony version.xml URL from the PS5
  app database (vendored SQLite).
- Resolves version.xml past nanoDNS via a raw DNS query, TLS pinned to the
  SCEI DNAS root.
- Filters patches to the newest one compatible with the current firmware.
- Downloads the patch and installs it through AppInstUtil (sysmodule loaded
  at runtime), gated to genuine installs with a package title-id match guard.
- Action-based UI filters and an on-screen startup notification with the URL.
2026-06-23 14:52:35 +02:00
Knutwurst 64bce0a3b5 Switch web UI to dark English interface 2026-06-23 08:17:12 +02:00
Knutwurst 56f54fc1a0 Add embedded web server skeleton 2026-06-23 08:12:29 +02:00
Knutwurst 9e89d3a342 Add source-aware update policy 2026-06-23 07:59:41 +02:00
Knutwurst 9c2822f367 Initial PatchDL web UI 2026-06-23 07:52:17 +02:00