diff --git a/AGENTS.md b/AGENTS.md index bd70332..a54a8a8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,7 +56,7 @@ write safety); `docs/LZ4.md` the LZ4 user guide. over `sevenz-rust2` (decode only), `rar.rs` our FFI wrapper over `unrar_sys` (bundles UnRAR's C++; every target, the PS5 payload included), `parts.rs` multi-part naming, `stream.rs` the `Reader`. Glue: `core/src/archive.rs` (`ArchiveTree`), `inspect.rs`, `library::archive_game_root`. Licence: `vendor/LICENSE-UnRAR.txt`, `vendor/LICENSE-UnRAR-exception.md` (copied into every release). -- `crates/ps5-dump-forge-cli`: binary `ps5-dump-forge` (`inspect`, `convert`, `serve`), JSON-lines events. +- `crates/ps5-dump-forge-cli`: binary `ps5-dump-forge` (`inspect`, `convert`, `lz4-patch`, `lz4-unpatch`, `serve`), JSON-lines events. - `crates/ps5-dump-forge-ps5`: the payload's global allocator (`Pool`, installed by the CLI only for the PS5): freed blocks of 64 B..4 MiB kept in power-of-two classes, since the PS5 libc serializes every malloc across threads (a `.pkg`'s Kraken pool: 12.7 → 49.3 MB/s at 4 threads). @@ -237,8 +237,10 @@ write safety); `docs/LZ4.md` the LZ4 user guide. `.pkg` are read-only on the console; on the PS5 an image ShadowMount+ has mounted or one inside `/mnt/shadowmnt`, `smp_finding`, in preflight and again before the replace). `output` is ignored: the job writes `.-.part` beside the source (the destination check counts the whole new image), verifies it as usual, re-checks - the source's `SourceStamp`, closes the source and renames the part over it (`Part::replace`, one - atomic rename, folder synced). Any failure leaves the source byte-identical and the `.part` deleted. + the source's `SourceStamp`, closes the source and puts the part in its place (`Part::replace`: the original kept as + `*.orig.part` (a hard link, or a rename where the volume has none), folder synced, the part renamed over + the source, folder synced, the backup removed). Any failure leaves the source byte-identical and the + `.part` deleted (where putting the original back fails, the error names its `*.orig.part` backup). The server's stale-part filter takes an in-place job's `.part` from its source. 3. Deleting the Edit tab's source, a Library entry or a File explorer row (the trash button of the Edit card, of a Library card/row or of an explorer row, or its Delete key; one **No** / **Yes, delete** @@ -434,7 +436,8 @@ write safety); `docs/LZ4.md` the LZ4 user guide. `/private/tmp`, ...; Linux `/proc`, `/usr`, `/run` except `/run/media/…`, ...; Windows, Program Files, ... on the system drive). A path at or inside the canonical home is allowed first (a `/var/home/me` or `/root` home works). Everything else is allowed (home and its children, `/Volumes`, `/media`, `/mnt`, - volume and drive roots). `library_cover` refuses only links, devices and `/mnt/shadowmnt`. + volume and drive roots). `library_cover` refuses links, devices, `/mnt/shadowmnt`, a file + without one of the five image extensions and an image on another filesystem than its folder. Walk exclusions: 12 skip names, the home's app-data folder, `.part`, non-UTF-8 names. - **Limits** (`library.rs`): depth 6 (root 0), 20,000 directories and 200,000 entries per root, 10,000 entries per directory, 256 image opens, 2,000 games, 15 s budget, 20 warnings; hitting one sets @@ -497,7 +500,7 @@ write safety); `docs/LZ4.md` the LZ4 user guide. FPKG is labelled `fpkg`; its files, the `Format`/`Kind` id and the CLI flag stay `pkg`. Format copy and the About tab's formats table (`app/src/Formats.tsx`) follow ShadowMountPlus 1.7's README and release notes. About's feature list (`formats.features`: bold name + a few words) is the user's text (by user decision): - Convert, Edit, Delete, LZ4 "with smart healing", Streamed, Archives, Library, Safe, USB and SD cards, In + Convert, Edit, Delete, LZ4 "with smart healing", Streamed, Archives, Storage (Library + File explorer), Safe, USB and SD cards, In place or rebuilt; sized so the card matches the QR card's height at 1200px; every language translates it. - UI languages: English (the fallback), Russian, Simplified Chinese (`zh`), German, Spanish, French, Japanese, Brazilian Portuguese (`pt`), Italian, Korean and Arabic; UI text only (`app/src/i18n.ts`). Backend text diff --git a/Cargo.lock b/Cargo.lock index a68284a..17df6d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2458,7 +2458,7 @@ dependencies = [ [[package]] name = "ps5-dump-forge-archive" -version = "0.2.0" +version = "0.3.0" dependencies = [ "cc", "crc32fast", @@ -2471,7 +2471,7 @@ dependencies = [ [[package]] name = "ps5-dump-forge-cli" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", "libc", @@ -2483,7 +2483,7 @@ dependencies = [ [[package]] name = "ps5-dump-forge-core" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", "blake3", @@ -2506,7 +2506,7 @@ dependencies = [ [[package]] name = "ps5-dump-forge-exfat" -version = "0.2.0" +version = "0.3.0" dependencies = [ "ps5-dump-forge-patch", "ps5upload-fpkg", @@ -2515,14 +2515,14 @@ dependencies = [ [[package]] name = "ps5-dump-forge-fpkg" -version = "0.2.0" +version = "0.3.0" dependencies = [ "ps5upload-fpkg", ] [[package]] name = "ps5-dump-forge-gui" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", "dunce", @@ -2535,7 +2535,7 @@ dependencies = [ [[package]] name = "ps5-dump-forge-lz4" -version = "0.2.0" +version = "0.3.0" dependencies = [ "blake3", "crc32fast", @@ -2547,14 +2547,14 @@ dependencies = [ [[package]] name = "ps5-dump-forge-patch" -version = "0.2.0" +version = "0.3.0" dependencies = [ "crc32fast", ] [[package]] name = "ps5-dump-forge-pfs" -version = "0.2.0" +version = "0.3.0" dependencies = [ "flate2", "ps5-dump-forge-exfat", @@ -2565,11 +2565,11 @@ dependencies = [ [[package]] name = "ps5-dump-forge-ps5" -version = "0.2.0" +version = "0.3.0" [[package]] name = "ps5-dump-forge-server" -version = "0.2.0" +version = "0.3.0" dependencies = [ "dunce", "flate2", @@ -2584,7 +2584,7 @@ dependencies = [ [[package]] name = "ps5-dump-forge-ufs2" -version = "0.2.0" +version = "0.3.0" dependencies = [ "ps5-dump-forge-patch", "ps5upload-fpkg", diff --git a/Cargo.toml b/Cargo.toml index 4a2c8ea..7a6683a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ resolver = "3" [workspace.package] edition = "2024" license = "GPL-3.0-or-later" -version = "0.2.0" +version = "0.3.0" rust-version = "1.99" [workspace.dependencies] diff --git a/DESIGN.md b/DESIGN.md index 5271668..9054a91 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -58,7 +58,7 @@ gradient notice bar) and the app icon (near-black navy with a blue edge and an o | ffpfs | `fmt-ffpfs` | teal `#7ee0e0` | | ffpfsc | `fmt-ffpfsc` | amber `#ffc580` | | fpkg | `fmt-pkg` | green `#8fe0ab` (Convert's default target) | -| LZ4 packed folder | `fmt-lz4` | lime `#d6e875` (only the LZ4 tab's Pack target picker offers it) | +| LZ4 packed folder | `fmt-lz4` | lime `#d6e875` (a job row's pill for a Pack into a folder; the Pack target picker shows that target as Folder) | **Semantic tags.** Tinted pills for states: `.tag.blue` Writing, `.tag.green` Done, `.tag.red` Failed, `.tag.orange` Backport, `.tag.violet` DLC and Additional content (a source without `eboot.bin`, beside @@ -300,8 +300,8 @@ tiles and tags. Two columns from 880px; Edit splits 1:2 from 1100px (its game ca shows the red reason and a `.small` **Enter password** (lock icon) in `.row.locked`. The password stays in memory with that source (inspect, naming and its jobs), never stored or shown. - **Delete** (`confirmDelete` in `delete.tsx`, `role="alertdialog"`, shared by Edit, the Library and - the File explorer): the warn icon, "Delete this folder?" / "Delete this image?" (the explorer: also - "Delete this file?"), the whole path in an + the File explorer): the trash icon in the warn amber (`.head-icon.warn`), "Delete this folder?" / "Delete this image?" / + "Delete this archive?" (the explorer: also "Delete this file?"), the whole path in an `.out-box`, a muted line (for good, no Trash or undo), and two buttons: **No** (focused first) and **Yes, delete** (`.danger`). Afterwards a muted "Deleted ". @@ -341,8 +341,8 @@ tiles and tags. Two columns from 880px; Edit splits 1:2 from 1100px (its game ca "Skipped locations and files" fold. No roots: "No folders are available to scan." - **File explorer** (`Files.tsx`, `.explorer.files`): Edit's explorer card (head with the folder icon, "File explorer", on a computer the root's `PathLine`, **Refresh**, **Choose folder**; Up and - crumbs, the filter and count, the `xgrid` well) without a pending bar: a muted info line says changes - happen at once. Row actions, icon-only (`aria-label` + `title`): Rename (pencil; inline name input, F2), + crumbs, the filter and count, the `xgrid` well) without a pending bar: an info `.note-line` (`.explorer-note`, `--fg-2`) says + changes happen at once. Row actions, icon-only (`aria-label` + `title`): Rename (pencil; inline name input, F2), Move to (the `move` icon; the folder picker), Delete (trash, `.danger`), then for a folder or an image/archive a ⋯ (`dots`) `ChooseMenu` with Edit and Convert ("More actions for "; it opens upwards, or from its other edge, where the well would clip it: `.menu.up`, `.menu.start`), then a diff --git a/README.md b/README.md index b1da3ed..f52878f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@

PS5 Dump Forge

-Runs on everything (including your PS5) and converts everything: **folder**, **exfat**, **ffpfs**, **ffpfsc**, **pkg**. +Runs on everything (including your PS5), converts and edits everything: game `folders`, `exfat`, `ffpkg`, `ffpfs`, +`ffpfsc` and debug `FPKG` packages, straight from `zip`, `7z` and `rar` too, with experimental `LZ4` asset packs for AMPR games. -Can utilize LZ4 compression and unpack archives (**zip**/**7zip**/**rar**). The interface is in **11 languages**. @@ -28,11 +28,11 @@ The interface is in **11 languages**. ## Formats Images are mounted by [ShadowMountPlus](https://github.com/drakmor/ShadowMountPlus); the debug FPKG is -installed. Every format can be created and read back (an image or `.fpkg` can be extracted to a folder). +installed. Every format can be created and read back (an image or a debug FPKG `.pkg` can be extracted to a folder). | Format | Console speed | Size on disk | Notes | |---|---|---|---| -| `.fpkg` (debug FPKG, saved as `.pkg`) | Native, full speed | 30–60% less, depending on the game | Installs on firmware 11.60 or lower with kstuff + fpkg-enable + ppr-patch. Kraken level (`--kraken`): **Fast** (default), **Balanced** / **Smallest** ≈ 2.6% smaller at ~6× / ~9× the compression time, **Stored** uncompressed (for a console that installs a compressed package but won't start it). Reads packages from this builder and compatible debug builders. | +| `.fpkg` (debug FPKG, saved as `.pkg`) | Native, full speed | 30–60% less, depending on the game | Installs on firmware 11.60 or lower. Reads packages from this builder and compatible debug builders. | | `.ffpkg` (UFS2) | Full image speed | Equal to the game size | ShadowMountPlus's recommendation. | | `.exfat` (512 B sectors, 64 KiB clusters) | Full image speed | Equal to the game size | For games that only run like external-drive content. | | `.ffpfs` (PFS, uncompressed) | Full image speed | Equal to the game size | Experimental in ShadowMountPlus. ASCII file names only. | @@ -54,24 +54,21 @@ Before you press Apply the tab says how the changes will go in: marked unclean, or a `.ffpkg` not laid out the way Forge writes one, takes Rebuild instead. If the check after writing fails, or the computer or console stops part way, the image is put back from an undo file beside it (`.forge-undo`); one left by a crash is restored on the next edit. - **Rebuild**: a new image of the same format is written beside the original from the original plus your - changes, verified, then put in its place. It needs about the image's size of free space beside it. It is + changes, verified, then put in its place. It needs free space beside the original for the new image (the tab says how much: for `.ffpfsc` and `.pkg` + it can be more than the original's size). It is the only way for `.ffpfsc` and `.pkg`, and the way when the changes don't fit in place. -A folder is changed directly, with no undo: a failure part way leaves the earlier changes in. Archives can be -browsed and extracted from, not edited; a game with LZ4 packs must be unpacked first. For an AMPR title, a change to its file -list writes a fresh `ampr_emu.index` and drops the old traces; the ampr_emu runtime is left as it is. On the -PS5, an image ShadowMountPlus has mounted can't be edited: stop the game and move the image out of -ShadowMountPlus's folders (or turn it off) first. In a browser on another device, dropped files are uploaded to the console -when you press Apply, so they need that much free space beside the game until the job ends. Close the game -before applying; once applied there is no undo. - **Extract** copies a file or folder (a row's Extract button, or **Extract folder** for the folder shown, the whole game at the top) out of any source, images, `.pkg` and archives included, into a folder you choose. Nothing there is ever replaced: a name that already exists stops it before anything is copied. -A source with a `sce_sys/param.json` naming a title ID but no `eboot.bin` is **additional content** (a DLC, an update or -another package) and is labelled so everywhere. It can be edited and extracted like a game, and converted -to a folder or an `.fpkg` (not to an image). +## Storage + +The **Storage** tab holds the **Library** (the games found in a folder you choose, or on the PS5's drives) +and a **File explorer**. Both open a game in Edit or Convert. The File explorer renames, moves and deletes at +once, after you confirm: a delete is permanent (no Trash), a move stays on the same drive and never replaces +anything, and anything an unfinished job uses is refused. On the PS5, images ShadowMountPlus has mounted are +tagged **Mounted** in the File explorer and can't be edited, moved or deleted. ## LZ4 asset packs @@ -91,6 +88,12 @@ Without traces or a profile, Pack falls back to a built-in guess of which files ampr_emu 0.4.2.1 is an upstream test build: some games crash when saving. Details, CLI options and caveats: [docs/LZ4.md](docs/LZ4.md). +A plain conversion (Convert, or the CLI without LZ4 options) also repairs an AMPR title: it installs the bundled +ampr_emu where there is none, replaces an older, unknown or trace build, and writes a fresh `ampr_emu.index` when +the old one doesn't match the files written, dropping the old traces and logs with it (into a `.pkg`, the package +builder writes the index). An LZ4-packed source keeps its index, traces and logs as they are. Convert says up +front what it will do. + ## Download and run Every release archive on the Releases page holds the app and the `ps5-dump-forge` command-line tool. Nothing to install; the app writes nothing beyond its own @@ -102,7 +105,7 @@ WebView data, so keep it in any folder you can write to. (`nc -w 3 9021 < ps5-dump-forge--ps5.elf`), or list it in an autoloader's `autoload.txt`. A notification says Dump Forge is launched; open it from its icon. - **Open the UI** from the Dump Forge icon, or in any browser on the same network: - `http://:8095`. + `http://:8095`. In the console's browser, **L2** / **R2** switch tabs. ### macOS (11 or later) diff --git a/app/package-lock.json b/app/package-lock.json index 4967a05..4855ad3 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "forge-app", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "forge-app", - "version": "0.2.0", + "version": "0.3.0", "license": "GPL-3.0-or-later", "dependencies": { "@tauri-apps/api": "~2.12.1", diff --git a/app/package.json b/app/package.json index 279bcf7..2e38aa3 100644 --- a/app/package.json +++ b/app/package.json @@ -2,7 +2,7 @@ "name": "forge-app", "private": true, "type": "module", - "version": "0.2.0", + "version": "0.3.0", "license": "GPL-3.0-or-later", "scripts": { "dev": "vite", diff --git a/app/src-tauri/tauri.conf.json b/app/src-tauri/tauri.conf.json index d0c55bb..1ebf0e9 100644 --- a/app/src-tauri/tauri.conf.json +++ b/app/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "PS5 Dump Forge", "mainBinaryName": "PS5 Dump Forge", - "version": "0.2.0", + "version": "0.3.0", "identifier": "io.github.quer3q.ps5-dump-forge", "build": { "beforeDevCommand": "npm run dev", diff --git a/app/src/locales/ar.tsx b/app/src/locales/ar.tsx index b2bda69..f3cc564 100644 --- a/app/src/locales/ar.tsx +++ b/app/src/locales/ar.tsx @@ -164,7 +164,7 @@ const ar: Messages = { ["LZ4", "تتبّع لعبة تستخدم AMPR ثم حزم أصولها مع إصلاح ذكي (تجريبي)."], ["بالتدفّق", "من أي مصدر إلى أي وجهة: بلا نسخة مؤقتة، حتى للصورة داخل ffpfsc أو اللعبة داخل أرشيف."], ["الأرشيفات", "ملف ⁦.zip⁩ أو ⁦.7z⁩ أو ⁦.rar⁩ يضم لعبة يُتصفَّح ويُحوَّل ويُحزَم كأنه مجلد."], - ["المكتبة", "الألعاب على هذا الحاسوب أو الجهاز، جاهزة للتحويل والتحرير."], + ["التخزين", "مكتبة لألعابك ومستكشف الملفات لإعادة التسمية والنقل والحذف."], ["آمن", "لا تتغير لعبتك أبدًا ما لم تطلب ذلك؛ تُتجاوز الملفات غير المفيدة وتُبلَّغ أسماء الملفات السيئة مسبقًا."], ["USB وبطاقات SD", "يُكتب على الذاكرات والبطاقات المهيأة بنظام exFAT أو FAT32."], ["في مكانه أو بإعادة بناء", "تدخل التعديلات مباشرة في الصورة إن كانت تتسع، وإلا في صورة جديدة تحل محلها بعد التحقق."], diff --git a/app/src/locales/de.tsx b/app/src/locales/de.tsx index 50939f6..8384487 100644 --- a/app/src/locales/de.tsx +++ b/app/src/locales/de.tsx @@ -123,7 +123,7 @@ const de: Messages = { ["LZ4", "Tracing eines AMPR-Spiels, dann Packen seiner Assets mit intelligenter Reparatur (experimentell)."], ["Gestreamt", "von jeder Quelle zu jedem Ziel: ohne temporäre Kopie, auch für das Image in einem ffpfsc oder ein Spiel in einem Archiv."], ["Archive", "ein .zip, .7z oder .rar mit einem Spiel wird wie ein Ordner durchsucht, konvertiert oder gepackt."], - ["Bibliothek", "die Spiele auf diesem Computer oder dieser Konsole, bereit zum Konvertieren und Bearbeiten."], + ["Speicher", "eine Bibliothek Ihrer Spiele und ein Datei-Explorer zum Umbenennen, Verschieben und Löschen."], ["Sicher", "Ihr Spiel wird nie verändert, außer Sie wünschen es; Müll-Dateien werden übersprungen, ungültige Dateinamen vorab gemeldet."], ["USB und SD-Karten", "geschrieben auf Sticks und Karten mit exFAT oder FAT32."], ["An Ort und Stelle oder neu gebaut", "Änderungen gehen direkt ins Image, wenn sie passen, sonst in ein neues Image, das es nach der Prüfung ersetzt."], diff --git a/app/src/locales/en.tsx b/app/src/locales/en.tsx index 30d25af..5b86416 100644 --- a/app/src/locales/en.tsx +++ b/app/src/locales/en.tsx @@ -134,7 +134,7 @@ const en = { ["LZ4", "trace an AMPR game, then pack its assets with smart healing (experimental)."], ["Streamed", "from any source to any target: no temporary copy, not even the image inside an ffpfsc or a game in an archive."], ["Archives", "a .zip, .7z or .rar holding one game is browsed, converted or packed like a folder."], - ["Library", "the games on this computer or console, ready to convert and edit."], + ["Storage", "a Library of your games and a File explorer to rename, move and delete."], ["Safe", "your game is never changed unless you ask; junk files are skipped and bad file names reported up front."], ["USB and SD cards", "written onto sticks and cards formatted exFAT or FAT32."], ["In place or rebuilt", "edits go straight into an image when they fit, else into a new image that replaces it once checked."], diff --git a/app/src/locales/es.tsx b/app/src/locales/es.tsx index 755d238..caeef88 100644 --- a/app/src/locales/es.tsx +++ b/app/src/locales/es.tsx @@ -137,7 +137,7 @@ const es: Messages = { ["LZ4", "trazar un juego con AMPR y luego empaquetar sus recursos con reparación inteligente (experimental)."], ["En streaming", "de cualquier origen a cualquier destino: sin copia temporal, ni siquiera para la imagen dentro de un ffpfsc o un juego en un archivo comprimido."], ["Archivos comprimidos", "un .zip, .7z o .rar con un juego se explora, convierte o empaqueta como una carpeta."], - ["Biblioteca", "los juegos de este ordenador o consola, listos para convertir y editar."], + ["Almacenamiento", "una Biblioteca de tus juegos y un Explorador de archivos para renombrar, mover y eliminar."], ["Seguro", "tu juego nunca cambia salvo que lo pidas; los archivos basura se omiten y los nombres de archivo incorrectos se avisan de antemano."], ["USB y tarjetas SD", "se escribe en memorias y tarjetas formateadas en exFAT o FAT32."], ["En el sitio o reconstruido", "los cambios van directo a la imagen si caben; si no, a una imagen nueva que la reemplaza tras comprobarla."], diff --git a/app/src/locales/fr.tsx b/app/src/locales/fr.tsx index c232b77..813ca0f 100644 --- a/app/src/locales/fr.tsx +++ b/app/src/locales/fr.tsx @@ -127,7 +127,7 @@ const fr: Messages = { ["LZ4", "traçage d’un jeu AMPR, puis empaquetage de ses ressources avec réparation intelligente (expérimental)."], ["En flux", "de n’importe quelle source vers n’importe quelle cible : sans copie temporaire, même pour l’image dans un ffpfsc ou un jeu dans une archive."], ["Archives", "un .zip, .7z ou .rar contenant un jeu se parcourt, se convertit ou s’empaquette comme un dossier."], - ["Bibliothèque", "les jeux de cet ordinateur ou de cette console, prêts à convertir et à modifier."], + ["Stockage", "une Bibliothèque de vos jeux et un Explorateur de fichiers pour renommer, déplacer et supprimer."], ["Sûr", "votre jeu n’est jamais modifié sans que vous le demandiez ; les fichiers parasites sont ignorés et les mauvais noms de fichiers signalés d’avance."], ["USB et cartes SD", "écrit sur des clés et cartes formatées en exFAT ou FAT32."], ["Sur place ou reconstruit", "les modifications vont directement dans l’image si elles tiennent, sinon dans une nouvelle image qui la remplace après vérification."], diff --git a/app/src/locales/it.tsx b/app/src/locales/it.tsx index bf6d24c..239e43b 100644 --- a/app/src/locales/it.tsx +++ b/app/src/locales/it.tsx @@ -133,7 +133,7 @@ const it: Messages = { ["LZ4", "traccia un gioco AMPR, poi impacchettane gli asset con riparazione intelligente (sperimentale)."], ["In streaming", "da qualsiasi origine a qualsiasi destinazione: senza copia temporanea, nemmeno per l’immagine dentro un ffpfsc o un gioco in un archivio."], ["Archivi", "uno .zip, .7z o .rar con un gioco si sfoglia, converte o impacchetta come una cartella."], - ["Libreria", "i giochi su questo computer o console, pronti da convertire e modificare."], + ["Archiviazione", "una Libreria dei tuoi giochi ed Esplora file per rinominare, spostare ed eliminare."], ["Sicuro", "il tuo gioco non cambia mai se non lo chiedi; i file spazzatura vengono saltati e i nomi di file errati segnalati in anticipo."], ["USB e schede SD", "scritto su chiavette e schede formattate exFAT o FAT32."], ["Sul posto o ricostruito", "le modifiche vanno direttamente nell’immagine se ci stanno, altrimenti in una nuova immagine che la sostituisce dopo il controllo."], diff --git a/app/src/locales/ja.tsx b/app/src/locales/ja.tsx index 8e7fb84..6dd9825 100644 --- a/app/src/locales/ja.tsx +++ b/app/src/locales/ja.tsx @@ -129,7 +129,7 @@ const ja: Messages = { ["LZ4", "AMPR ゲームをトレースし、そのアセットをスマート修復付きでパックします(実験的)。"], ["ストリーミング", "任意のソースから任意の出力先へ。一時コピーなしで、ffpfsc 内のイメージやアーカイブ内のゲームでも同様です。"], ["アーカイブ", "ゲームを含む .zip、.7z、.rar をフォルダーのように閲覧、変換、パックできます。"], - ["ライブラリ", "このコンピューターまたは本体にあるゲームを、すぐに変換・編集できます。"], + ["ストレージ", "ゲームのライブラリと、名前の変更・移動・削除ができるファイルエクスプローラー。"], ["安全", "依頼しない限りゲームは変更されません。ジャンクファイルはスキップし、不正なファイル名は事前に報告します。"], ["USB と SD カード", "exFAT または FAT32 でフォーマットしたメモリーやカードに書き込めます。"], ["その場で、または再構築", "収まる場合は編集をイメージに直接反映し、収まらない場合は新しいイメージを作り、検証後に置き換えます。"], diff --git a/app/src/locales/ko.tsx b/app/src/locales/ko.tsx index a1ca30b..3d1d45d 100644 --- a/app/src/locales/ko.tsx +++ b/app/src/locales/ko.tsx @@ -123,7 +123,7 @@ const ko: Messages = { ["LZ4", "AMPR 게임을 트레이스한 다음 에셋을 스마트 복구와 함께 패킹합니다(실험적)."], ["스트리밍", "어떤 원본에서 어떤 대상으로든 임시 복사본 없이 처리합니다. ffpfsc 안의 이미지나 아카이브 안의 게임도 마찬가지입니다."], ["아카이브", "게임이 든 .zip, .7z, .rar 를 폴더처럼 탐색, 변환, 패킹합니다."], - ["라이브러리", "이 컴퓨터나 본체에 있는 게임을 바로 변환하고 편집할 수 있습니다."], + ["저장소", "게임 라이브러리와 이름 변경·이동·삭제를 할 수 있는 파일 탐색기."], ["안전", "요청하지 않으면 게임을 절대 변경하지 않으며, 불필요한 파일은 건너뛰고 잘못된 파일 이름은 미리 알려 줍니다."], ["USB 및 SD 카드", "exFAT 또는 FAT32로 포맷된 USB 메모리와 카드에 기록합니다."], ["제자리 또는 재빌드", "수정 사항이 들어가면 이미지에 바로 반영하고, 아니면 새 이미지를 만들어 검증 후 교체합니다."], diff --git a/app/src/locales/pt.tsx b/app/src/locales/pt.tsx index 45c9cc9..7bb845b 100644 --- a/app/src/locales/pt.tsx +++ b/app/src/locales/pt.tsx @@ -129,7 +129,7 @@ const pt: Messages = { ["LZ4", "rastrear um jogo AMPR e depois empacotar os assets dele com reparo inteligente (experimental)."], ["Em streaming", "de qualquer origem para qualquer destino: sem cópia temporária, nem para a imagem dentro de um ffpfsc ou um jogo em um arquivo compactado."], ["Arquivos compactados", "um .zip, .7z ou .rar com um jogo é explorado, convertido ou empacotado como uma pasta."], - ["Biblioteca", "os jogos deste computador ou console, prontos para converter e editar."], + ["Armazenamento", "uma Biblioteca dos seus jogos e um Explorador de arquivos para renomear, mover e excluir."], ["Seguro", "seu jogo nunca é alterado a menos que você peça; arquivos inúteis são ignorados e nomes de arquivo inválidos são avisados antes."], ["USB e cartões SD", "gravado em pendrives e cartões formatados em exFAT ou FAT32."], ["No lugar ou refeito", "as edições vão direto na imagem quando cabem; senão, vão para uma nova imagem que a substitui depois de verificada."], diff --git a/app/src/locales/ru.tsx b/app/src/locales/ru.tsx index e3da194..141a6ef 100644 --- a/app/src/locales/ru.tsx +++ b/app/src/locales/ru.tsx @@ -125,7 +125,7 @@ const ru: Messages = { ["LZ4", "трейсинг игры с AMPR и упаковка её ресурсов с умным восстановлением (экспериментально)."], ["Потоково", "из любого источника в любой приёмник: без временной копии, даже для образа внутри ffpfsc и игры в архиве."], ["Архивы", "игра в .zip, .7z или .rar просматривается, конвертируется и упаковывается как папка."], - ["Библиотека", "игры на этом компьютере или консоли, готовые к конвертации и редактированию."], + ["Хранилище", "Библиотека ваших игр и Проводник, чтобы переименовывать, перемещать и удалять."], ["Безопасно", "ваша игра не меняется без вашего запроса; мусорные файлы пропускаются, а плохие имена файлов выявляются заранее."], ["USB и SD-карты", "запись на флешки и карты с exFAT или FAT32."], ["На месте или с пересборкой", "правки вносятся прямо в образ, если помещаются, иначе в новый образ, который после проверки заменяет прежний."], diff --git a/app/src/locales/zh.tsx b/app/src/locales/zh.tsx index 15771e0..c1dc37d 100644 --- a/app/src/locales/zh.tsx +++ b/app/src/locales/zh.tsx @@ -121,7 +121,7 @@ const zh: Messages = { ["LZ4", "追踪 AMPR 游戏,再以智能修复方式打包其资源(实验性)。"], ["流式", "可从任意来源到任意目标:无需临时副本,即使是 ffpfsc 内的镜像或压缩包中的游戏。"], ["压缩包", "包含一款游戏的 .zip、.7z 或 .rar 可像文件夹一样浏览、转换或打包。"], - ["游戏库", "这台电脑或主机上的游戏,可直接转换和编辑。"], + ["存储", "您的游戏库,以及可重命名、移动和删除的文件浏览器。"], ["安全", "除非您要求,否则绝不修改您的游戏;会跳过垃圾文件,并提前报告不合规的文件名。"], ["USB 和 SD 卡", "可写入 exFAT 或 FAT32 格式的 U 盘和存储卡。"], ["原位或重建", "能容纳时直接在镜像内编辑,否则生成新镜像,校验通过后替换原镜像。"], diff --git a/ps5/README.md b/ps5/README.md index 9ebf591..422deb8 100644 --- a/ps5/README.md +++ b/ps5/README.md @@ -84,10 +84,10 @@ FORGE_DEBUG_API=1 ps5/build.sh # bring-up only: adds POST /api/debug, GET /pro path confinement; paths go to core as given, as in the Tauri app. One delete route and one move route, by user decision (`delete_path`, Edit's and the Storage tab's; `move_path`, its File explorer's; the page's question authenticates nobody). -- Limits: head 16 KiB, body 1 MiB (`Content-Length` only; `edit_upload` streams any size, 30 s idle per read), 10 s per request, 30 s per write (a download: +- Limits: head 16 KiB, body 1 MiB (8 MiB for `start_job` and `edit_check`; `Content-Length` only; `edit_upload` streams any size, 30 s idle per read), 10 s per request, 30 s per write (a download: per 1 MiB chunk), 16 handler - threads (then 503), 8 unfinished jobs (then 429), 2 inspections, naming, `host_tree` or `edit_check` requests at once (429, - `Retry-After: 1`), 32 finished jobs and 200 log lines each kept, `list_dir` 10,000 entries (`truncated`), + threads (then 503), 8 unfinished jobs (then 429), 2 source reads at once (inspections, naming, `host_tree`, `edit_check`, + Library scans and covers; a further read waits up to 30 s for a slot, at most 4 waiting, then 429 `Retry-After: 1`), 32 finished jobs and 200 log lines each kept, `list_dir` 10,000 entries (`truncated`), `stale_parts` 1,000. `compression_threads: null` is `max(1, cores − 1)`; on the PS5 `cores` is the payload's 5 CPUs (`compat.c`) and two are left free (3 workers), for the console's browser. @@ -105,8 +105,8 @@ FORGE_DEBUG_API=1 ps5/build.sh # bring-up only: adds POST /api/debug, GET /pro | `POST /api/start_job` | `{request: ConvertRequest}` | job id (`lz4_in_place: true` replaces an `.exfat`/`.ffpkg` source; `output` ignored; non-empty `edits` is an edit job of the source, in place or with `edit_rebuild`, `output` and `format` ignored; non-empty `extract` copies those game paths into the folder `output`, `format` `folder`) | | `POST /api/cancel_job` | `{id}` | `null` (idempotent) | | `POST /api/stale_parts` | `{dirs}` | `string[]`, without running jobs' parts (an in-place or edit job's beside its source) or a staging folder an upload or unfinished job uses | -| `POST /api/edit_check` | `{request: ConvertRequest}` | `EditCheck {in_place, rebuild, needed_inside, free_inside, rebuild_needs, free_beside}`: why each mode can't apply (empty = it can) and the sizes (bytes, `null` where they don't apply); reads only, except that a leftover undo journal no job uses is restored first (after the ShadowMount+ check) and the U1 probe writes and removes its file. The source and every `Add.from` must be a folder or regular file (else 400); counts as an inspection (429), 409 once stopping | -| `POST /api/host_tree` | `{path}` | `{kind: "file"\|"dir", size, files: [{path, size}], empty_dirs, truncated}`: a file or folder as an edit's add will read it (junk left out; a link or special file refused); `truncated` past 200,000 entries or 15 s. 404 missing, counts as an inspection (429), 409 once stopping, 500 with core's message | +| `POST /api/edit_check` | `{request: ConvertRequest}` | `EditCheck {in_place, rebuild, needed_inside, free_inside, rebuild_needs, free_beside}`: why each mode can't apply (empty = it can) and the sizes (bytes, `null` where they don't apply); reads only, except that a leftover undo journal no job uses is restored first (after the ShadowMount+ check) and the U1 probe writes and removes its file. The source and every `Add.from` must be a folder or regular file (else 400); counts as an inspection (the 2-read cap), 409 once stopping | +| `POST /api/host_tree` | `{path}` | `{kind: "file"\|"dir", size, files: [{path, size}], empty_dirs, truncated}`: a file or folder as an edit's add will read it (junk left out; a link or special file refused); `truncated` past 200,000 entries or 15 s. 404 missing, counts as an inspection (the 2-read cap), 409 once stopping, 500 with core's message | | `POST /api/edit_upload?source=&path=` | raw bytes (`Content-Length` required) | `{path}`: the staged file, `.forge-upload-.part/`, where the UI sends `path` as `-/` (`stagingNames`: a random tag per Apply) (canonical source; folders made on demand, 0777 on the PS5), written to a temporary name and renamed over. 411 no length, 400 bad query, game path or a body that ended early or stalled, 404 source missing, 409 a non-file in the way, the same target uploading, an unfinished job using it, or stopping | | `POST /api/edit_discard` | `{source}` | `null`: the source's staging folder removed by `delete_path`'s rules (missing: `null`); 409 while an upload into it or a job using it is unfinished, or stopping | | `POST /api/lz4_patch` | `{source}` | `Lz4Patch`; 409 while a job is queued or running | @@ -114,10 +114,10 @@ FORGE_DEBUG_API=1 ps5/build.sh # bring-up only: adds POST /api/debug, GET /pro | `GET /api/lz4_traces?source=` | | both trace files as one zip download (below) | | `POST /api/delete_path` | `{path}` | `null`: the file or folder is gone for good (below); 400 refused, 404 missing, 409 a job uses it or stopping, 500 failed (a folder may be partly gone) | | `POST /api/move_path` | `{from, to}` | `null`: moved or renamed on its volume, `to` never replaced; `from` refused as `delete_path` refuses it (same roots kept), `to` refused inside `from`, on another drive, taken, or with an undo journal pending; 400 refused, 404 missing (source or target folder), 409 a job uses either or stopping, 500 failed | -| `POST /api/library_scan` | `{}` (an object; `root` string scans just that folder on any filesystem (a network share included; a chosen root that is a link is resolved first, links inside the walk are never followed, `/mnt/shadowmnt` refused; system folders such as `/` or `/usr` are refused with core's message), non-string 400; `root` is required on a computer (400 "choose a folder to scan" without it) and optional on the PS5) | `{roots, entries: [{path, kind, title_id, title_name, version, has_cover, lz4_packed, additional_content}], truncated, warnings, storage?}`: the Library's scan of that folder, or on the PS5 of the server's current roots (browse roots without `/mnt/shadowmnt`, re-resolved each call so a newly attached drive appears on Refresh). `storage` `{free, total}` (bytes, `statfs` of `/data`) only on the PS5, read on each scan. Reads only; counts as an inspection (429 past 2), 409 once stopping, 500 with core's message. Partial results set `truncated` and add `warnings` (core's scan limits and 15 s cooperative budget). The request deadline governs receiving the request, not core's work, which can't be interrupted | +| `POST /api/library_scan` | `{}` (an object; `root` string scans just that folder on any filesystem (a network share included; a chosen root that is a link is resolved first, links inside the walk are never followed, `/mnt/shadowmnt` refused; system folders such as `/` or `/usr` are refused with core's message), non-string 400; `root` is required on a computer (400 "choose a folder to scan" without it) and optional on the PS5) | `{roots, entries: [{path, kind, title_id, title_name, version, has_cover, lz4_packed, additional_content}], truncated, warnings, storage?}`: the Library's scan of that folder, or on the PS5 of the server's current roots (browse roots without `/mnt/shadowmnt`, re-resolved each call so a newly attached drive appears on Refresh). `storage` `{free, total}` (bytes, `statfs` of `/data`) only on the PS5, read on each scan. Reads only; counts as an inspection (the 2-read cap), 409 once stopping, 500 with core's message. Partial results set `truncated` and add `warnings` (core's scan limits and 15 s cooperative budget). The request deadline governs receiving the request, not core's work, which can't be interrupted | | `POST /api/library_cover` | `{path}` | a PNG data-URL string, or `null` (no icon, too large, not a PNG); fetched lazily per card. Same cap, 409 and 500 rules (an unreadable source is a 500) | | `POST /api/list_dir` | `{path: string\|null, explorer?: bool}` | `{path, parent, entries: [{name, path, dir, size, link, mounted}], truncated}` (`null`: the browse roots; `parent` `null` at a root; `link`: the entry is a link, its `path` the resolved target; with `explorer` the roots are the Library's, and on the PS5 `/mnt/shadowmnt` is hidden (a path in it 400) and `mounted` marks an image ShadowMount+ has mounted) | -| `GET /api/jobs` | | `{instance, jobs: [{id, request, progress, done, log, log_total}]}` | +| `GET /api/jobs` | | `{instance, jobs: [{id, request, stages, progress, done, log, log_total}]}` | | `POST /api/quit` | `{}` | `{}`, then cancels every job, waits for cleanup and exits | | `POST /api/quit` | `{"if_idle": true}` | `{}`, then exits as above, only when idle: 409 "Dump Forge is busy: …" while a job is queued or running, a `lz4_traces` download streams or a patch, delete or move holds admission past 1 s, 409 "Dump Forge is stopping" (the handover's request) | | `GET /api/elfldr` | — | `{up}`: elfldr accepts a connection (1 s; connect and close, nothing sent); 404 on a computer | diff --git a/vendor/README.md b/vendor/README.md index cb0ddce..56205a1 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -11,7 +11,7 @@ inspection), taken from ps5upload with a small local patch series on top. | License | GPL-3.0-or-later (see `LICENSE-ps5upload`, and the note below) | | Edition | 2021, as upstream, on purpose: it keeps the diff to upstream small | -The trees here are upstream plus every patch in `patches/`, in order. Nothing else differs. +The trees here are upstream plus every patch in `patches/` except 0021 (lzma-rust2, below), in order. Nothing else differs. `LICENSE-UnRAR.txt` and `LICENSE-UnRAR-exception.md` are not vendored code: UnRAR's license and the GPLv3 §7 permission for the UnRAR source that `unrar_sys` (crates.io) builds into every target. The @@ -84,6 +84,7 @@ rm -rf /tmp/series && mkdir /tmp/series cp -R /tmp/ps5upload/engine/crates/ps5upload-fpkg /tmp/ps5upload/engine/crates/ps5upload-pkg /tmp/series/ (cd /tmp/series && git init -q && git add -A && git commit -qm pristine) for p in patches/*.patch; do + case $p in *0021-*) continue ;; esac (cd /tmp/series && git apply "$OLDPWD/$p" && git add -A && \ git commit -qm "$(basename "$p" .patch | cut -d- -f2-)") done @@ -98,7 +99,7 @@ it on top. Then write every patch again, each against its parent: ```sh cd /tmp/series && n=0 for c in $(git rev-list --reverse HEAD~$(($(git rev-list --count HEAD) - 1))..HEAD); do - n=$((n + 1)) + n=$((n + 1)); [ $n = 21 ] && n=22 git diff --binary --histogram "$c~1" "$c" > "$OLDPWD/patches/$(printf %04d $n)-$(git log -1 --format=%s "$c" | tr ' ' -).patch" done ```