mirror of
https://github.com/LisherSong/ps5-web-file-manager.git
synced 2026-10-06 09:00:26 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8344b9bae0 | ||
|
|
cb82ee439d | ||
|
|
9e9830a214 | ||
|
|
687ef6b297 | ||
|
|
36ea055e70 | ||
|
|
2a346d694c |
No files matched your search
+5
-1
@@ -148,10 +148,14 @@ echo "[6/7] make all (增量编译, 复用第三方 obj)..."
|
||||
cd "$PROJ"
|
||||
export PS5_PAYLOAD_SDK="/opt/ps5-payload-sdk"
|
||||
# 仅当二进制缺失或源码变更才全量重编
|
||||
# 重要: 必须包含 assets/* 和 gen-asset-module.py —— 它们经 gen-asset-module.py
|
||||
# 生成 gen/*.c 进而影响 ELF, 不在列表里就会跳过 make 产生伪"无变更"(v1.8.3
|
||||
# 被这个 bug 坑过, ELF sha256 没变)。
|
||||
if [ -f web-file-mgr.elf ]; then
|
||||
echo " 已存在 web-file-mgr.elf, 检查源码变更..."
|
||||
NEEDS_REBUILD=""
|
||||
for src in src/*.c Makefile third_party/minizip-ng/include/*.h third_party/zlib/include/*.h; do
|
||||
for src in src/*.c Makefile assets/* gen-asset-module.py \
|
||||
third_party/minizip-ng/include/*.h third_party/zlib/include/*.h; do
|
||||
[ -e "$src" ] || continue
|
||||
if [ "$src" -nt web-file-mgr.elf ]; then
|
||||
NEEDS_REBUILD="$src"
|
||||
|
||||
+157
-6
@@ -4,14 +4,165 @@ All notable changes to **PS5 Web File Manager** are documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
> Release artifact for v1.8.1:
|
||||
> `web-file-mgr.elf` — size TBD (cross-compile runs in WSL — see `docs/HANDOVER.md`)
|
||||
> sha256 TBD
|
||||
> Release artifact for v1.8.3:
|
||||
> `web-file-mgr.elf` — size 509 704 bytes (~497 KiB)
|
||||
> sha256 `fdcf7b09b69e2160e77dfa084c0e890ba0696d4dd478b1d5ff499cdc9f527955`
|
||||
> ELF class 64, little-endian, e_machine `0x003e` (x86_64-sie-ps5)
|
||||
>
|
||||
> Source delta vs v1.8: 3 source files relaxed (`src/zip_extract.c` k_default_limits,
|
||||
> `assets/main.js` `LARGE_FILE_THRESHOLD_BYTES`, `assets/lang-{en,zh}.js` copy);
|
||||
> no vendored or engine changes.
|
||||
> Source delta vs v1.8.2: 5 files touched (4 user-facing + 1 build pipeline) —
|
||||
> see [v1.8.3] below for details.
|
||||
>
|
||||
> Release artifact for v1.8.2:
|
||||
> `web-file-mgr.elf` — size 509 704 bytes (~497 KiB)
|
||||
> sha256 `1b2c3d68b35e32737105f17d14a80a3c159ceca0cabd274ee168cbcd81906f65`
|
||||
> ELF class 64, little-endian, e_machine `0x003e` (x86_64-sie-ps5)
|
||||
>
|
||||
> Source delta vs v1.8.1: 5 files relaxed (`src/zip_extract.c` k_default_limits
|
||||
> + k_large_limits, `assets/main.js` `LARGE_FILE_THRESHOLD_BYTES`,
|
||||
> `assets/lang-{en,zh}.js` copy, `tests/test_zip_extract.c` advertised-number
|
||||
> assertion, README/HANDOVER numeric references) + 2 PS5-only build fixes
|
||||
> (`Makefile` CFLAGS `-Ithird_party/unrar`, `src/extract.c` forward
|
||||
> declaration of `extract_progress`); no vendored or engine changes.
|
||||
|
||||
## [v1.8.3] — 2026-09-05
|
||||
|
||||
**Hotfix: "Upload and extract" now accepts `.rar` files.**
|
||||
|
||||
The "upload and extract" entry was hard-coded to accept only `.zip`,
|
||||
even though the server-side dispatch in `src/extract.c:79` already
|
||||
correctly routes `.rar` to `rar_extract()`. v1.8.3 fixes the frontend
|
||||
filter so users can select a single-volume plaintext `.rar` from the
|
||||
file picker and have it uploaded + extracted in one click (the same
|
||||
flow that already worked for `.zip`).
|
||||
|
||||
What this enables:
|
||||
|
||||
- Choose a single-volume `.rar` from "Upload and extract"
|
||||
- Server extracts it via the existing `rar_extract()` engine
|
||||
- Uploaded `.rar` is auto-deleted after a successful extract (same as
|
||||
`.zip` since v1.7)
|
||||
|
||||
What this does **not** enable (planned for v1.9.0):
|
||||
|
||||
- **Multi-volume RAR** (e.g. `name.part01.rar` + `name.part02.rar` …)
|
||||
- **Encrypted RAR** (password-protected headers or entries)
|
||||
|
||||
Both still return `extract_unsupported` "single-volume RAR only" /
|
||||
"encrypted RAR is not supported; please extract on a PC first" — see
|
||||
the underlying engine limit in `third_party/unrar/dmc_unrar` (GPL-2.0,
|
||||
1.7.0). v1.9 will swap the vendor to **opello/unrar 7.20.1** (UnRAR
|
||||
License) which natively supports both.
|
||||
|
||||
Changed:
|
||||
|
||||
- `assets/index.html` — `<input id="uploadZip" accept>` now lists
|
||||
`.rar` + the two RAR MIME types next to the existing ZIP entries.
|
||||
- `assets/main.js:2340` — `/\.zip$/i` → `/\.(zip|rar)$/i` (the upload
|
||||
pre-check), plus a local `isRar` flag so the next step branches.
|
||||
- `assets/lang-en.js` — `extractUploadConfirm`: "uploaded ZIP" →
|
||||
"uploaded archive".
|
||||
- `assets/lang-zh.js` — `extractUploadConfirm` & `extractLargeAsk`
|
||||
drop the "ZIP" wording so the copy reads sensibly for RAR uploads.
|
||||
- `assets/main.js:38` — `APP_VERSION` `"v1.7"` → `"v1.8.3"` (footer
|
||||
version string had been hard-coded to v1.7 since the frontend was
|
||||
first imported; it no longer misleads about which build is running).
|
||||
|
||||
No backend changes — the server side was already correct. No test
|
||||
changes — the existing RAR happy-path test in `tests/test_rar_extract.c`
|
||||
passes against the same backend.
|
||||
|
||||
Build pipeline (also v1.8.3):
|
||||
|
||||
- `.build/build-elf.sh` step 6 "no source change → skip make" check now
|
||||
also watches `assets/*` and `gen-asset-module.py`, not just `src/*.c`
|
||||
and the `Makefile`. Without this, v1.8.3 (which touched no backend,
|
||||
only frontend assets feeding `gen/*.c`) was misclassified as "no
|
||||
change" and `make` was skipped — the result was that the v1.8.2 ELF
|
||||
was reported as v1.8.3 with the same sha256. With this fix, only
|
||||
frontend changes correctly trigger a rebuild. Users running the WSL
|
||||
build need to re-copy `.build/build-elf.sh` to `/home/song/build-elf.sh`
|
||||
(canonical source is on the Windows side).
|
||||
|
||||
## [v1.8.2] — 2026-09-05
|
||||
|
||||
**Hotfix: default ZIP extraction limits cover 3A-game single-file archives.**
|
||||
|
||||
The default `k_default_limits` profile is now **2 TiB total / 512 GiB per
|
||||
entry / 500 : 1 ratio** (was 1 TiB / 256 GiB / 500 : 1 in v1.8.1). The
|
||||
frontend threshold `LARGE_FILE_THRESHOLD_BYTES` is bumped from 240 GiB to
|
||||
**480 GiB** to match. The `large=1` profile is widened to **4 TiB total
|
||||
/ 1 TiB per entry / 1000 : 1 ratio** (was 2 TiB / 1 TiB / 1000 : 1); the
|
||||
large profile must always be strictly more permissive than default.
|
||||
|
||||
Why: a 3A-game archive with a single ~300 GiB uncompressed file was
|
||||
**silently rejected by the default profile** (`scan_archive` returns
|
||||
`ZIPX_ERR_LIMIT_FILE_SIZE` in `src/zip_extract.c` line ~717 — the request
|
||||
never reaches the frontend confirmation prompt, so the user just sees
|
||||
"卡壳"). The 256 GiB default cap was tuned for PS5 system backups (which
|
||||
have many smaller entries, not a single huge file) and was wrong for the
|
||||
3A-game single-file case. The default cap is now 512 GiB so a typical
|
||||
3A archive extracts under the default profile without prompting.
|
||||
|
||||
The safety argument is unchanged from v1.8.1: zip-bomb defence is
|
||||
`check_space()` (`statvfs`-based real disk space check before staging) +
|
||||
`max_ratio` (declared compression ratio cap). The size caps are a UX
|
||||
guard, not a security boundary.
|
||||
|
||||
RAR extraction inherits the new defaults automatically — rar_extract.c
|
||||
threads `c->limits` through from the engine, so no rar-side change is
|
||||
required.
|
||||
|
||||
### Changed
|
||||
|
||||
- `src/zip_extract.c` — `k_default_limits` relaxed:
|
||||
- `max_total_bytes`: 1 TiB → **2 TiB**
|
||||
- `max_file_bytes`: 256 GiB → **512 GiB**
|
||||
- `max_ratio`: 500 → **500** (unchanged)
|
||||
- `src/zip_extract.c` — `k_large_limits` widened (must stay > default):
|
||||
- `max_total_bytes`: 2 TiB → **4 TiB**
|
||||
- `max_file_bytes`: 1 TiB → **1 TiB** (unchanged)
|
||||
- `max_ratio`: 1000 → **1000** (unchanged)
|
||||
- `assets/main.js` — `LARGE_FILE_THRESHOLD_BYTES`: 240 GiB → **480 GiB**
|
||||
- `assets/lang-{en,zh}.js` — `extractLargeAsk` copy updated to reflect
|
||||
the new numbers (default 512 GiB / 2 TiB; large 1 TiB / 4 TiB)
|
||||
- `tests/test_zip_extract.c` — `test_large_profile` advertised-number
|
||||
assertion updated: `max_total_bytes == 4 TiB` (was 2 TiB)
|
||||
- `README.md` — both limit tables (ZIP + RAR), the "Tuning the threshold"
|
||||
snippet, and the `err_extract_entry_too_large` FAQ entry bumped to the
|
||||
new numbers
|
||||
- `docs/HANDOVER.md` — `LARGE_FILE_THRESHOLD_BYTES`, the
|
||||
`k_default_limits` / `k_large_limits` ASCII diagram, the user-scenario
|
||||
description, the 480 GiB popup note, and the RAR limits paragraph
|
||||
bumped to the new numbers
|
||||
|
||||
### Unchanged
|
||||
|
||||
- `src/rar_extract.c` — already threads `c->limits` from the engine,
|
||||
picks up the new defaults for free
|
||||
- `max_ratio` — both profiles unchanged (500 : 1 default / 1000 : 1 large)
|
||||
- `check_space()` — unchanged; still the real disk-space guard
|
||||
- `max_entries` — 200 000 default / 500 000 large, unchanged
|
||||
- `docs/UPGRADE-v1.7-zip-large-file-profile.md` — historical v1.7
|
||||
document left as-is so the v1.7 → v1.8.2 evolution is traceable
|
||||
- Test fixture `medium_bomb.zip` (ratio ≈ 238) still exercises both
|
||||
rejection under the default 500 : 1 cap and acceptance under the
|
||||
`large=1` 1000 : 1 cap
|
||||
- 84 host-side checks (70 ZIP + 14 RAR), 0 failures
|
||||
|
||||
### Migration notes
|
||||
|
||||
- **Forward-compatible** — users with v1.8.1 deployments who never trigger
|
||||
`err_extract_entry_too_large` see no difference (defaults are strictly
|
||||
more permissive)
|
||||
- **3A-game single-file archives now extract silently** — no prompt, no
|
||||
manual `large=1` API call required for files up to 512 GiB
|
||||
- **No data loss** — the relaxation only widens accepted archives; the
|
||||
real security guards (`check_space`, `max_ratio`, `path traversal`)
|
||||
are untouched
|
||||
- **No frontend UX change for typical use** — only archives > 480 GiB
|
||||
on disk now trigger the confirmation prompt (previously 240 GiB)
|
||||
|
||||
---
|
||||
|
||||
## [v1.8.1] — 2026-09-05
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ THIRD_PARTY_CFLAGS := -O2 -w -Ithird_party/zlib/include -Ithird_party/minizip-ng
|
||||
PS5_TP_OBJS := $(patsubst %.c,ps5-obj/%.o,$(THIRD_PARTY_SRCS))
|
||||
LINUX_TP_OBJS := $(patsubst %.c,linux-obj/%.o,$(THIRD_PARTY_SRCS))
|
||||
|
||||
CFLAGS := -Oz -fno-asynchronous-unwind-tables -fno-unwind-tables -Wall -Werror -ffunction-sections -fdata-sections -Isrc -Ithird_party/minizip-ng/include -DVERSION_TAG=\"$(VERSION_TAG)\" -DTITLE_ID=\"$(TITLE_ID)\"
|
||||
CFLAGS := -Oz -fno-asynchronous-unwind-tables -fno-unwind-tables -Wall -Werror -ffunction-sections -fdata-sections -Isrc -Ithird_party/minizip-ng/include -Ithird_party/unrar -DVERSION_TAG=\"$(VERSION_TAG)\" -DTITLE_ID=\"$(TITLE_ID)\"
|
||||
CFLAGS += `$(PKG_CONFIG) libmicrohttpd --cflags`
|
||||
LDFLAGS := -Wl,--gc-sections
|
||||
LDADD := `$(PKG_CONFIG) libmicrohttpd --libs`
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> Homebrew HTTP file manager for jailbroken PS5 consoles. Browse, edit, upload, download and extract ZIPs through any browser on the same network — single self-contained ELF payload, no external services, no telemetry.
|
||||
|
||||
**Version:** v1.8 · **Title ID:** `FMGR88888` · **License:** GPLv3+ · **Target:** `x86_64-sie-ps5`
|
||||
**Version:** v1.8.2 · **Title ID:** `FMGR88888` · **License:** GPLv3+ · **Target:** `x86_64-sie-ps5`
|
||||
|
||||
---
|
||||
|
||||
@@ -40,6 +40,48 @@ The same source tree builds a Linux binary for development and a PS5 payload ELF
|
||||
limitations that come from using dmc_unrar (no multi-volume, no
|
||||
encryption in v1.8 — both lift in v1.9 when the library is replaced).
|
||||
|
||||
## What's new in v1.8.1
|
||||
|
||||
- **Default ZIP limits relaxed** (companion to v1.7's large profile).
|
||||
v1.7 shipped with a 64 GiB default per-entry cap, which was too
|
||||
aggressive for typical PS5 system-backup ZIPs (200-300 GiB). v1.8.1
|
||||
raises the default profile to **1 TiB total / 256 GiB per entry /
|
||||
500 : 1 ratio**, with the `large=1` opt-in kept at 2 TiB / 1 TiB /
|
||||
1000 : 1. The frontend threshold rises from 60 GiB to 240 GiB so
|
||||
common system-backup archives no longer trigger the prompt.
|
||||
- RAR extraction inherits the new defaults (rar_extract.c threads
|
||||
`c->limits` from the engine — no engine change required).
|
||||
- Rationale: the real zip-bomb defence is `check_space()` (statvfs-based
|
||||
real disk-space check before staging) + `max_ratio` (declared
|
||||
compression ratio cap). The size caps are a UX guard, not a security
|
||||
boundary.
|
||||
|
||||
## What's new in v1.8.2
|
||||
|
||||
- **Default ZIP limits relaxed again** for the 3A-game single-file case.
|
||||
A single ~300 GiB uncompressed file inside an archive was still
|
||||
silently rejected by v1.8.1 (the default scan returns
|
||||
`ZIPX_ERR_LIMIT_FILE_SIZE` before the request ever reaches the
|
||||
frontend confirmation prompt). v1.8.2 raises the default profile to
|
||||
**2 TiB total / 512 GiB per entry / 500 : 1 ratio**, with the `large=1`
|
||||
opt-in bumped to 4 TiB / 1 TiB / 1000 : 1. Frontend threshold rises
|
||||
from 240 GiB to 480 GiB.
|
||||
- **Two PS5-only build fixes** discovered when cross-compiling for the
|
||||
PS5 target. The host-side test suite (`tests/run-tests.sh`) had
|
||||
silently accepted both because it links the same sources but uses
|
||||
gcc rather than clang 18 and a different include path:
|
||||
- `Makefile` CFLAGS: add `-Ithird_party/unrar` so `src/rar_extract.c`
|
||||
can find the project-authored `dmc_unrar_api.h` facade header.
|
||||
- `src/extract.c`: move `extract_progress()` definition above
|
||||
`extract_dispatch()` so the implicit function declaration is not
|
||||
flagged by `-Werror=implicit-function-declaration` (clang 18 in the
|
||||
PS5 SDK is stricter than the host gcc used by tests).
|
||||
- **Release artifact** for v1.8.2: `web-file-mgr.elf` — 509 704 bytes,
|
||||
sha256 `1b2c3d68b35e32737105f17d14a80a3c159ceca0cabd274ee168cbcd81906f65`,
|
||||
ELF class 64, little-endian, e_machine `0x003e` (x86_64-sie-ps5).
|
||||
- Tests: **84 host-side checks** (70 ZIP + 14 RAR), 0 failures. PS5
|
||||
cross-compile succeeds end-to-end.
|
||||
|
||||
## What's new in v1.7
|
||||
|
||||
- **ZIP large-file profile** (opt-in via the new `large=1` argument on `/api/extract`): relaxed caps of **2 TiB** archive total, **1 TiB** per entry, **1000 : 1** compression ratio. The frontend prompts for confirmation whenever the archive on disk is larger than **60 GiB**; the server only activates the profile when the user explicitly agrees.
|
||||
@@ -116,7 +158,7 @@ make
|
||||
Output:
|
||||
|
||||
```text
|
||||
web-file-mgr.elf (~418 KiB, x86_64-sie-ps5)
|
||||
web-file-mgr.elf (~497 KiB, x86_64-sie-ps5)
|
||||
```
|
||||
|
||||
For pure UI/JS work without the PS5 toolchain:
|
||||
@@ -156,13 +198,13 @@ Plain ZIPs only — stored / deflated / ZIP64, **never encrypted**. The engine i
|
||||
| Limit | Default profile | Large profile (`ZIPX_LIMITS_LARGE`) |
|
||||
|---|---|---|
|
||||
| `max_entries` | 200 000 | 500 000 |
|
||||
| `max_total_bytes` (uncompressed) | 1 TiB | 2 TiB |
|
||||
| `max_file_bytes` (per entry) | 256 GiB | 1 TiB |
|
||||
| `max_total_bytes` (uncompressed) | 2 TiB | 4 TiB |
|
||||
| `max_file_bytes` (per entry) | 512 GiB | 1 TiB |
|
||||
| `max_ratio` (uncompressed / compressed) | 500 : 1 | 1000 : 1 |
|
||||
| `max_depth` (folder nesting) | 32 | 32 |
|
||||
| `max_name_len` / `max_path_len` | 255 / 1024 | 255 / 1024 |
|
||||
|
||||
The **default profile** is shipped safe: a 4 MiB compressed blob that decodes to 800 GiB is rejected before any output file is opened. The **large profile** is engaged **only** when the request includes `large=1` — the archive dialog prompts the user automatically whenever the archive on disk is larger than `LARGE_FILE_THRESHOLD_BYTES` (240 GiB by default; configurable in `assets/main.js`). Confirming the prompt is the user's explicit opt-in; the server still records nothing extra on its own.
|
||||
The **default profile** is shipped safe: a 4 MiB compressed blob that decodes to 800 GiB is rejected before any output file is opened. The **large profile** is engaged **only** when the request includes `large=1` — the archive dialog prompts the user automatically whenever the archive on disk is larger than `LARGE_FILE_THRESHOLD_BYTES` (480 GiB by default; configurable in `assets/main.js`). Confirming the prompt is the user's explicit opt-in; the server still records nothing extra on its own.
|
||||
|
||||
### Security checks
|
||||
|
||||
@@ -184,10 +226,10 @@ Passed as `conflict=` on `/api/extract`:
|
||||
|
||||
### Tuning the threshold
|
||||
|
||||
The 240 GiB frontend threshold lives in `assets/main.js`:
|
||||
The 480 GiB frontend threshold lives in `assets/main.js`:
|
||||
|
||||
```js
|
||||
const LARGE_FILE_THRESHOLD_BYTES = 240 * 1024 * 1024 * 1024;
|
||||
const LARGE_FILE_THRESHOLD_BYTES = 480 * 1024 * 1024 * 1024;
|
||||
```
|
||||
|
||||
Set it to `Infinity` to silence the prompt, lower it to be more conservative, or remove the call entirely — the server still respects `large=1` regardless of the threshold.
|
||||
@@ -228,14 +270,14 @@ profile table on top. Defaults and the `large=1` opt-in are identical:
|
||||
| Limit | Default profile | Large profile (`large=1`) |
|
||||
|---|---|---|
|
||||
| `max_entries` | 200 000 | 500 000 |
|
||||
| `max_total_bytes` (uncompressed) | 1 TiB | 2 TiB |
|
||||
| `max_file_bytes` (per entry) | 256 GiB | 1 TiB |
|
||||
| `max_total_bytes` (uncompressed) | 2 TiB | 4 TiB |
|
||||
| `max_file_bytes` (per entry) | 512 GiB | 1 TiB |
|
||||
| `max_ratio` (uncompressed / compressed) | 500 : 1 | 1000 : 1 |
|
||||
| `max_depth` (folder nesting) | 32 | 32 |
|
||||
| `max_name_len` / `max_path_len` | 255 / 1024 | 255 / 1024 |
|
||||
|
||||
Large-profile RAR extraction uses the same `LARGE_FILE_THRESHOLD_BYTES`
|
||||
(240 GiB) prompt as ZIP — the frontend treats `.rar` and `.zip` the same
|
||||
(480 GiB) prompt as ZIP — the frontend treats `.rar` and `.zip` the same
|
||||
way for the prompt, and the server only ever activates the large caps
|
||||
when the request carries `large=1` (opt-in).
|
||||
|
||||
@@ -285,7 +327,7 @@ the step-by-step upgrade recipe.
|
||||
After `make`, sanity-check the produced ELF:
|
||||
|
||||
```sh
|
||||
ls -la web-file-mgr.elf # size ~430 KiB on v1.8 (~418 KiB on v1.7)
|
||||
ls -la web-file-mgr.elf # size ~497 KiB on v1.8.2 (~427 KiB on v1.8)
|
||||
sha256sum web-file-mgr.elf # record the digest in your release notes
|
||||
file web-file-mgr.elf # expect "ELF 64-bit LSB pie executable, x86-64"
|
||||
od -An -tx1 -N20 web-file-mgr.elf | head -2 # magic 7f45 4c46 0201 + e_machine 003e
|
||||
@@ -301,8 +343,8 @@ A POSIX/host-side C test suite covers the ZIP engine and runs on any Linux / mac
|
||||
cd tests && bash run-tests.sh
|
||||
```
|
||||
|
||||
Output is a per-case `check`-style report — **83 checks** on the current `main`
|
||||
(69 ZIP + 14 RAR). Coverage:
|
||||
Output is a per-case `check`-style report — **84 checks** on the current `main`
|
||||
(70 ZIP + 14 RAR). Coverage:
|
||||
|
||||
- ZIP entry parsing (stored + deflated + ZIP64)
|
||||
- Path traversal, absolute paths, backslash, Windows drive letters
|
||||
@@ -320,7 +362,7 @@ Output is a per-case `check`-style report — **83 checks** on the current `main
|
||||
|
||||
```
|
||||
.
|
||||
├── Makefile # PS5 + Linux builds (VERSION_TAG v1.8)
|
||||
├── Makefile # PS5 + Linux builds (VERSION_TAG v1.8.2)
|
||||
├── install-libmicrohttpd.sh # one-shot dependency installer
|
||||
├── gen-asset-module.py # embeds assets/* as gzip-compressed C arrays
|
||||
├── assets/ # HTML / CSS / JS / icons / param.json
|
||||
@@ -368,10 +410,11 @@ Output is a per-case `check`-style report — **83 checks** on the current `main
|
||||
- **This is a homebrew app and should not intentionally modify system processes or kernel memory.** If you hit a kernel panic, make sure you are using a recent jailbreak method and ELF loader, or revert to the stable method you normally use.
|
||||
- **P2JB users** — if this payload triggers a kernel panic, avoid using it on that setup. Stability matters more than convenience when each retry is expensive.
|
||||
- **The preparing stage can take a while** when a folder contains many files — it sums folder size and checks free space, which helps avoid starting a copy / move / upload / download that cannot finish safely.
|
||||
- **`err_extract_entry_too_large`** — default archive caps are 256 GiB per
|
||||
entry / 500:1 ratio. Confirm the large-file prompt (appears for
|
||||
archives > 240 GiB on disk), split the archive, or pass `large=1`
|
||||
directly to the API.
|
||||
- **`err_extract_entry_too_large`** — default archive caps are 512 GiB per
|
||||
entry / 500:1 ratio (covers a typical 3A-game archive with one ~300 GiB
|
||||
uncompressed file). If you exceed the default, confirm the large-file
|
||||
prompt (appears for archives > 480 GiB on disk), split the archive, or
|
||||
pass `large=1` directly to the API.
|
||||
- **`err_extract_unsupported`** — the archive uses a feature the engine
|
||||
cannot handle: encrypted ZIP, encrypted RAR, multi-volume RAR
|
||||
(`.part02+.rar`), very-old RAR 1.4, RAR symlinks / FIFOs, or a file
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@
|
||||
</section>
|
||||
<input id="uploadFiles" type="file" multiple hidden>
|
||||
<input id="uploadFolder" type="file" multiple webkitdirectory hidden>
|
||||
<input id="uploadZip" type="file" accept=".zip,application/zip,application/x-zip-compressed" hidden>
|
||||
<input id="uploadZip" type="file" accept=".zip,.rar,application/zip,application/x-zip-compressed,application/vnd.rar,application/x-rar-compressed" hidden>
|
||||
|
||||
<section id="content" class="content">
|
||||
<table>
|
||||
|
||||
+2
-2
@@ -101,11 +101,11 @@ window.WFM_LANG = {
|
||||
extracting: "Extracting",
|
||||
extractConfirm: "Extract {name} to {path}?",
|
||||
extractOverwriteAsk: "If a file or folder with the same name already exists in the target:\n\nOK = overwrite same-name files (folders still merge)\nCancel = fail if the target already exists",
|
||||
extractUploadConfirm: "Upload and extract {name}?\n\nTarget folder: {path}\nThe uploaded ZIP will be deleted after success.",
|
||||
extractUploadConfirm: "Upload and extract {name}?\n\nTarget folder: {path}\nThe uploaded archive will be deleted after success.",
|
||||
extractStarted: "Extraction started: {name}",
|
||||
extractDone: "Extraction complete: {name}",
|
||||
extractProgress: "{done} / {total} files",
|
||||
extractLargeAsk: "The archive looks large ({size}). Enable the large-file profile?\n\nOK = yes (single file up to 1 TiB, archive total up to 2 TiB)\nCancel = default limits (single file 256 GiB, archive total 1 TiB); this archive may be rejected",
|
||||
extractLargeAsk: "The archive looks large ({size}). Enable the large-file profile?\n\nOK = yes (single file up to 1 TiB, archive total up to 4 TiB)\nCancel = default limits (single file 512 GiB, archive total 2 TiB); this archive may be rejected",
|
||||
extractLargeActive: "Large-file profile is enabled for this task",
|
||||
extractSelectMainVolume: "Please select the main volume (.rar or .part01.rar)",
|
||||
extractArchivePending: "Preparing to extract {name}",
|
||||
|
||||
+2
-2
@@ -101,11 +101,11 @@ window.WFM_LANG = {
|
||||
extracting: "解压",
|
||||
extractConfirm: "解压 {name} 到 {path}?",
|
||||
extractOverwriteAsk: "若目标已存在同名文件或目录:\n\n确定 = 覆盖同名文件(目录仍会合并)\n取消 = 若目标已存在则失败",
|
||||
extractUploadConfirm: "上传并解压 {name}?\n\n目标目录:{path}\n成功后将删除上传的 ZIP。",
|
||||
extractUploadConfirm: "上传并解压 {name}?\n\n目标目录:{path}\n成功后将删除上传的压缩包。",
|
||||
extractStarted: "已开始解压 {name}",
|
||||
extractDone: "解压完成:{name}",
|
||||
extractProgress: "{done} / {total} 个文件",
|
||||
extractLargeAsk: "ZIP 体积较大({size}),是否启用「大文件模式」?\n\n确定 = 启用(单文件最大 1 TiB / 总解压最大 2 TiB)\n取消 = 默认限制(单文件 256 GiB / 总解压 1 TiB),可能拒绝此压缩包",
|
||||
extractLargeAsk: "压缩包体积较大({size}),是否启用「大文件模式」?\n\n确定 = 启用(单文件最大 1 TiB / 总解压最大 4 TiB)\n取消 = 默认限制(单文件 512 GiB / 总解压 2 TiB),可能拒绝此压缩包",
|
||||
extractLargeActive: "此任务已启用大文件模式",
|
||||
extractSelectMainVolume: "请改选主卷(如 .rar 或 .part01.rar)",
|
||||
extractArchivePending: "正在准备解压 {name}",
|
||||
|
||||
+9
-3
@@ -35,7 +35,7 @@ let uploadXhr = null;
|
||||
let uploadTerminalAbort = false;
|
||||
let L = {};
|
||||
|
||||
const APP_VERSION = "v1.7";
|
||||
const APP_VERSION = "v1.8.3";
|
||||
const LAST_PATH_KEY = "ps5-web-file-mgr:last-path";
|
||||
const SORT_KEY = "ps5-web-file-mgr:list-sort";
|
||||
const LOADING_DISPLAY_DELAY = 250;
|
||||
@@ -835,7 +835,13 @@ async function startExtractTask(path, dstDir, conflict, removeSource, name, larg
|
||||
}
|
||||
}
|
||||
|
||||
const LARGE_FILE_THRESHOLD_BYTES = 240 * 1024 * 1024 * 1024;
|
||||
// Threshold above which the web UI prompts the user before extracting.
|
||||
// Tuned so a typical 3A-game archive (~300 GiB single file) and a PS5 system
|
||||
// backup (~300 GiB total) extract under the default profile without prompting.
|
||||
// Files between this threshold and the default max_file_bytes cap (512 GiB)
|
||||
// still extract silently; larger files require explicit user opt-in via the
|
||||
// large=1 API flag.
|
||||
const LARGE_FILE_THRESHOLD_BYTES = 480 * 1024 * 1024 * 1024;
|
||||
|
||||
function shouldPromptLargeMode(itemSize) {
|
||||
return Number(itemSize || 0) > LARGE_FILE_THRESHOLD_BYTES;
|
||||
@@ -2331,7 +2337,7 @@ function actionUploadAndExtract() {
|
||||
|
||||
async function uploadAndExtractFile(file) {
|
||||
if (busy || loadingPath) return;
|
||||
if (!/\.zip$/i.test(file.name || "")) {
|
||||
if (!/\.(zip|rar)$/i.test(file.name || "")) {
|
||||
alert(t("err_extract_unsupported", { arg: file.name }));
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@ SDK 的 `target/user/homebrew/` 被 `songl(197609)` 拥有 755,普通 song 写
|
||||
- `assets/lang-en.js` 和 `lang-zh.js` 新增 `extractLargeAsk` / `extractLargeActive`
|
||||
|
||||
**前端 UX**:
|
||||
- ZIP 大于 240 GiB 时弹窗「启用大文件模式?」
|
||||
- ZIP 大于 480 GiB 时弹窗「启用大文件模式?」
|
||||
- 用户点 OK → 传 `large=1` → 引擎走 large profile
|
||||
- 用户点取消 → 走 default profile(多半会被拒绝)
|
||||
|
||||
|
||||
+22
-20
@@ -45,6 +45,28 @@ ends_with_ci(const char *path, const char *suffix) {
|
||||
return !strcasecmp(path + path_len - suf_len, suffix);
|
||||
}
|
||||
|
||||
/* Progress callback. The engine already throttles reports (200 ms / 1 MiB),
|
||||
so we can forward each report straight into the shared task state.
|
||||
Defined before extract_dispatch() so the dispatcher's call site compiles
|
||||
cleanly under -Werror=implicit-function-declaration. */
|
||||
static void
|
||||
extract_progress(void *userdata, const zipx_progress_t *p) {
|
||||
file_task_t *task = userdata;
|
||||
unsigned long long prev_done;
|
||||
unsigned long long delta;
|
||||
|
||||
pthread_mutex_lock(&g_tasks_lock);
|
||||
task->entries_total = p->entries_total;
|
||||
task->entries_done = p->entries_done;
|
||||
task->total = p->bytes_total;
|
||||
prev_done = task->done;
|
||||
pthread_mutex_unlock(&g_tasks_lock);
|
||||
|
||||
delta = p->bytes_done > prev_done ? p->bytes_done - prev_done : 0;
|
||||
task_update(task, TASK_RUNNING, p->current ? p->current : task->src,
|
||||
delta, NULL);
|
||||
}
|
||||
|
||||
/* Pick the right engine by the archive file name. Returns ZIPX_ERR_FORMAT
|
||||
for anything that does not look like a supported archive. */
|
||||
static zipx_status_t
|
||||
@@ -71,26 +93,6 @@ extract_dispatch(file_task_t *task, zipx_conflict_t conflict,
|
||||
}
|
||||
}
|
||||
|
||||
/* Progress callback. The engine already throttles reports (200 ms / 1 MiB),
|
||||
so we can forward each report straight into the shared task state. */
|
||||
static void
|
||||
extract_progress(void *userdata, const zipx_progress_t *p) {
|
||||
file_task_t *task = userdata;
|
||||
unsigned long long prev_done;
|
||||
unsigned long long delta;
|
||||
|
||||
pthread_mutex_lock(&g_tasks_lock);
|
||||
task->entries_total = p->entries_total;
|
||||
task->entries_done = p->entries_done;
|
||||
task->total = p->bytes_total;
|
||||
prev_done = task->done;
|
||||
pthread_mutex_unlock(&g_tasks_lock);
|
||||
|
||||
delta = p->bytes_done > prev_done ? p->bytes_done - prev_done : 0;
|
||||
task_update(task, TASK_RUNNING, p->current ? p->current : task->src,
|
||||
delta, NULL);
|
||||
}
|
||||
|
||||
static const char *
|
||||
extract_error_code(zipx_status_t status) {
|
||||
switch(status) {
|
||||
|
||||
+24
-6
@@ -33,22 +33,40 @@
|
||||
#define ZIPX_PUBLISH_MAX_DEPTH 128
|
||||
#define ZIPX_SPACE_SLACK_PER_ENTRY 512
|
||||
|
||||
/* Default limits.
|
||||
*
|
||||
* Tuned to cover real-world PS5 workloads without prompting:
|
||||
* - PS5 system backup ZIPs (~200-300 GiB total, individual chunks <64 GiB)
|
||||
* - 3A-game archives with a single ~300 GiB uncompressed file
|
||||
*
|
||||
* Safety against zip bombs is delegated to:
|
||||
* 1. `check_space()` (statvfs-based real disk space check) before extract
|
||||
* 2. `max_ratio` below (declared compression ratio cap)
|
||||
* The size caps here are an early-fail UX guard, not a security boundary.
|
||||
*/
|
||||
static const zipx_limits_t k_default_limits = {
|
||||
.max_entries = 200000,
|
||||
.max_total_bytes = 1ULL * 1024 * 1024 * 1024 * 1024,
|
||||
.max_file_bytes = 256ULL * 1024 * 1024 * 1024,
|
||||
.max_total_bytes = 2ULL * 1024 * 1024 * 1024 * 1024,
|
||||
.max_file_bytes = 512ULL * 1024 * 1024 * 1024,
|
||||
.max_ratio = 500,
|
||||
.max_depth = 32,
|
||||
.max_name_len = 255,
|
||||
.max_path_len = 1024
|
||||
};
|
||||
|
||||
/* Large profile for archives that exceed the safe limits (e.g. 200 GB system
|
||||
images). The relaxed ratio still rejects obvious bombs; the size caps
|
||||
require the user to opt in via the web UI before they take effect. */
|
||||
/* Large profile for archives that exceed the default cap.
|
||||
*
|
||||
* - max_file_bytes = 1 TiB (single uncompressed file)
|
||||
* - max_total_bytes = 4 TiB (whole archive)
|
||||
* - max_ratio = 1000 (relaxed ratio cap; check_space still applies)
|
||||
*
|
||||
* Requires the user to opt in via the web UI (large=1) before these take
|
||||
* effect. Default limits must always be strictly smaller than large so the
|
||||
* large profile is unambiguously a relaxation.
|
||||
*/
|
||||
static const zipx_limits_t k_large_limits = {
|
||||
.max_entries = 500000,
|
||||
.max_total_bytes = 2ULL * 1024 * 1024 * 1024 * 1024,
|
||||
.max_total_bytes = 4ULL * 1024 * 1024 * 1024 * 1024,
|
||||
.max_file_bytes = 1ULL * 1024 * 1024 * 1024 * 1024,
|
||||
.max_ratio = 1000,
|
||||
.max_depth = 32,
|
||||
|
||||
@@ -559,8 +559,8 @@ test_large_profile(void) {
|
||||
check(large->max_entries == 500000, "max_entries == 500000");
|
||||
check(large->max_file_bytes == 1ULL * 1024 * 1024 * 1024 * 1024,
|
||||
"max_file_bytes == 1 TiB");
|
||||
check(large->max_total_bytes == 2ULL * 1024 * 1024 * 1024 * 1024,
|
||||
"max_total_bytes == 2 TiB");
|
||||
check(large->max_total_bytes == 4ULL * 1024 * 1024 * 1024 * 1024,
|
||||
"max_total_bytes == 4 TiB");
|
||||
check(large->max_ratio == 1000, "max_ratio == 1000");
|
||||
|
||||
/* Behaviour: medium_bomb.zip is 1 MiB of 0..255 cycled, compressing to
|
||||
|
||||
Reference in new issue
Block a user