-epl + absolute stage paths stored dir\nested.txt as nested.txt, so the
v6/volume happy-path checks found no dir\nested.txt. Generator now cd's
into the stage dir and passes relative names without -ep1.
tests/make_fixtures.py rmtree()s tests/fixtures on every run-tests.sh
invocation, silently deleting the real RAR fixtures. The WinRAR generator
now writes tests/fixtures-real/; run-tests.sh passes it as a third
argument to test-rar-extract, which resolves v6/volume/encrypted checks
against it and falls back to SKIP when absent.
Naming the target vol.part1.rar made RAR 7 append its own numbering onto
the whole stem -> vol.part1.partN.rar. Plain vol.rar splits into the
standard vol.part1/2/3.rar the test expects.
rar a updates an existing archive instead of re-splitting it, so the
single-volume vol.part1.rar left by earlier runs (small payload) was
grown to 512KB without ever producing part2/3. Remove vol.part* first.
The earlier -v200k split never produced part2 because the payload was a
few hundred bytes. Now generates a 512KB incompressible file (fsutil +
-m0 store) so vol.part1/2/3.rar actually exist for the volume test.
Write tool wrote UTF-8 Chinese comments which cmd.exe parsed under the
ANSI codepage as garbage ('M' is not a command). This version is pure
ASCII with CRLF line endings. RAR4 (-ma4) creation fails on newer RAR
builds; that step now warns and continues instead of aborting the whole
run. Requires no behavioural change from the engine side.
tests/make-rar-fixtures.bat produces real RAR fixtures on a machine with
WinRAR (Rar.exe) so the RAR engine is finally exercised against genuine
archives instead of 11-byte placeholders (v1.8 shipped without ever
running a real RAR through the happy path):
- basic-v6.rar RAR5 with WinRAR 6/7 "v6" compression (the format
dmc_unrar could not decode — the v1.9 trigger)
- vol.part1+2.rar multi-volume (200 KB split)
- enc-v6.rar encrypted (password secret123)
- basic-rar4.rar legacy RAR4
tests/test_rar_extract.c gains test_real_archives(): asserts v6 single
volume and RAR4 extract OK with expected files on disk, multi-volume
auto-merges from the same directory, and encrypted archives are rejected
up front with ZIPX_ERR_UNSUPPORTED (password channel lands separately).
Each check SKIPs cleanly when the fixture is absent, so plain CI
checkouts stay green.
Run: tests/make-rar-fixtures.bat (WinRAR required), then rerun
tests/run-tests.sh to flip the SKIPs into real assertions.
Replace the dmc_unrar 1.7.0 backend (third_party/unrar/) with the rarlab
UnRAR 7.20.1 source vendored in third_party/unrar7/ (commit c68c0de),
driven through its C-compatible DLL API.
Why: dmc_unrar only dispatches RAR5 compression version 5
(switch(file->version) case 0x5000). WinRAR 6.x/7.x archives (algorithm
"v6") land on the default branch -> DMC_UNRAR_FILE_UNSUPPORTED_VERSION
-> user-visible "corrupt archive", confirmed on a real v6:8M archive on
2026-09-05. unrar 7.20.1 natively handles v6, multi-volume (.partNN.rar
auto-merge) and encrypted archives.
Engine changes (src/rar_extract.c):
- Facade include swapped to third_party/unrar7/unrar_c_api.h; all calls
now go through RAROpenArchiveEx/RARReadHeaderEx/RARProcessFile/
RARCloseArchive. scan + extract each re-open the archive (the DLL API
is sequential, unlike dmc's index-based access).
- Error translation rewritten for the ERAR_* code set.
- extract no longer builds each staging path by hand (open_parent_dirs/
extract_one removed): unrar writes the whole tree under the staging
root; names were validated during scan.
- Bug fix: normalize_name() cleared the caller-provided *is_dir (it set
*is_dir = 0), turning directory entries into files and tripping the
duplicate detector whenever an explicit directory header followed
files beneath it (real v6 archive had exactly this). RHDF_DIRECTORY
is now honored end to end.
- Encrypted RAR still fails up front (ZIPX_ERR_UNSUPPORTED): unrar can
decrypt, but password plumbing (API/UI) lands in a later commit.
Build:
- Makefile: C++ compiler vars (CXX/HOST_CXX, prospero-clang++ defaults
to -stdlib=libc++), unrar7 RARDLL source set (49 files, mirrors
UnRARDll.vcxproj), .cpp pattern rules, link through the C++ driver.
- tests/run-tests.sh: compiles unrar7 objects with $CXX, links the RAR
test with g++ and -lpowrprof on Windows.
- third_party/unrar/ (dmc_unrar) removed; see git history.
Verified on host (MinGW): full engine run on the user's real v6 archive
(24.9 MB, 170 headers) -> ZIPX_OK, 170/170 entries, 133 files + 37 dirs,
71,514,931 bytes. Host suite: 70 ZIP + 14 RAR checks green.
Frontend password/multi-volume UX and release plumbing (CHANGELOG,
THIRD_PARTY_NOTICES, README, tag) follow in the v1.9 release commit.
New RAR engine for v1.9, replacing dmc_unrar 1.7.0. dmc_unrar only
dispatches RAR5 compression version 5 (switch(file->version) case
0x5000); WinRAR 6.x/7.x writes version 6 -> DMC_UNRAR_FILE_UNSUPPORTED_VERSION
-> user-facing "corrupt archive" on real v6 archives (diagnosed 2026-09-05
from a v6:8M:m0:m3 test file). unrar 7.20.1 natively supports v6,
encrypted and multi-volume RAR.
Vendored into third_party/unrar7/ (new dir) so the tree stays buildable
while src/rar_extract.c still references the dmc facade. Contents are a
verbatim copy of opello/unrar @97e1780 (159 files, v7.20.1) plus two
project files:
- unrar_c_api.h (C facade shim: platform types + dll.hpp re-export)
- VENDORED.md (source pin, build model, license notes)
Build model verified on host (MinGW g++ 16.2, Windows):
- all 49 UnRARDll.vcxproj sources compile with
-std=c++17 -DRARDLL -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE
- links + runs: RARGetDllVersion() = 9 (smoke binary in .build/, ignored)
- Windows link additionally needs -lpowrprof
License: UnRAR freeware license (not GPL) - free to use for handling RAR
archives; cannot be used to build a RAR-compatible archiver. THIRD_PARTY_NOTICES
update lands with the engine swap commit.
Next (v1.9 work items): engine swap in rar_extract.c, Makefile .cpp rules,
fixtures + host tests, frontend password/multi-volume, then ELF build.
versionEl (page footer) showed "v1.7" regardless of the actual build
because APP_VERSION was hardcoded when the frontend was imported in
v1.7 and never bumped through v1.8 / v1.8.1 / v1.8.2 / v1.8.3. Users
running a v1.8.x ELF could not tell from the UI which build they were
on — which made the "no extract button for .rar" report hard to
diagnose (the user's console was still v1.7, which predates RAR file
recognition entirely).
Note: Makefile VERSION_TAG stays "v1.8" (it is a separate C-side macro
not wired to frontend assets); APP_VERSION is the user-visible string.
5-file delta summary now mentions the build pipeline fix; new "Build
pipeline" subsection under v1.8.3 explains why and what users (running
the WSL build script) need to do.
ELF sha256 is still TBD until the WSL build is re-run with the patched
script; will fill in when the user pastes the new artifact metadata.
The step 6 needs-rebuild loop only watched src/*.c, Makefile, and
minizip/zlib headers. Frontend assets (assets/main.js, assets/index.html,
lang-*.js) feed gen/*.c via gen-asset-module.py, so v1.8.3 (which touched
no backend, only assets/) was silently classified as "no source change"
and make was skipped. The ELF sha256 stayed at the v1.8.2 build.
Fix: also iterate assets/* and gen-asset-module.py. Anyone changing a
frontend file (and nothing else) will now correctly trigger a rebuild.
This is the script-side sidekick to commit 687ef6b (v1.8.3 hotfix), which
fixed the actual user-visible bug; this commit fixes the build pipeline
so future frontend-only changes produce a new ELF.
Note: .build/build-elf.sh on Windows is the canonical source. The WSL-side
/home/song/build-elf.sh needs to be re-copied from it before the next build
(re-run "cp /home/song/ps5-web-file-manager/.build/build-elf.sh /home/song/build-elf.sh").
Server-side dispatch in src/extract.c:79 already routes .rar to
rar_extract(), but the upload + extract entry point was hard-coded
to accept only .zip via two layers:
- assets/index.html:71 <input accept=".zip,application/zip,...">
- assets/main.js:2340 if (!/\.zip$/i.test(file.name)) return;
v1.8.3 relaxes both to also accept .rar (and the two RAR MIME types),
plus a small i18n pass so the "uploading ZIP"/"ZIP body is large"
copy reads sensibly when the user picks a RAR. No backend, no engine,
no vendored changes.
What this enables today (v1.8.3):
- Upload a single-volume plaintext .rar in one click and have the
existing rar_extract() engine process it. Uploaded archive is
auto-deleted on success, same as .zip since v1.7.
Still NOT enabled (planned for v1.9.0):
- Multi-volume RAR (name.part01.rar + name.part02.rar + ...)
- Encrypted RAR (password-protected headers / entries)
Both still return extract_unsupported from the existing
third_party/unrar/dmc_unrar 1.7.0 (GPL-2.0) engine. v1.9.0 will
swap the vendor to opello/unrar 7.20.1 (UnRAR License) which
natively handles both.
Files (4):
assets/index.html: accept=".zip,.rar,application/zip,
application/x-zip-compressed,
application/vnd.rar,
application/x-rar-compressed"
assets/main.js: upload pre-check regex /.(zip|rar)$/i
assets/lang-en.js: "ZIP will be deleted" -> "archive will be deleted"
assets/lang-zh.js: upload ZIP / volume ZIP wording neutralized
CHANGELOG.md: v1.8.3 entry added above v1.8.2
84 host-side checks (70 ZIP + 14 RAR), 0 failures.
README had several stale values from v1.8.1 that never made it into
the published docs (because v1.8.1 was a hotfix and the README rewrite
focused on v1.8 RAR). v1.8.2 surfaces all of them in one pass.
- Header version tag: v1.8 -> v1.8.2
- ZIP extraction limits table: 1 TiB / 256 GiB -> 2 TiB / 512 GiB
(large profile: 2 TiB / 1 TiB -> 4 TiB / 1 TiB)
- RAR extraction limits table: same v1.8.2 numbers
- Frontend threshold snippets: 240 GiB -> 480 GiB (both ZIP and RAR)
- ELF size mentions: ~418 KiB / ~430 KiB -> ~497 KiB / ~427 KiB
- Test count: "83 checks (69 ZIP + 14 RAR)" -> "84 checks (70 ZIP + 14 RAR)"
- Makefile layout comment: VERSION_TAG v1.8 -> v1.8.2
Added two new "What's new in" sections so the changelog surface in
README matches the tag history:
- v1.8.1 — default limits 64 GiB -> 256 GiB (PS5 system-backup scenario)
- v1.8.2 — default limits 256 GiB -> 512 GiB (3A-game single-file
scenario) + the two PS5-only build fixes discovered by the v1.8.2
cross-compile (Makefile -Ithird_party/unrar, src/extract.c reorder
for clang 18 -Werror=implicit-function-declaration) + the v1.8.2
release artifact sha256.
A 3A-game archive with a single ~300 GiB uncompressed file was silently
rejected by the v1.8.1 default profile (scan_archive returns
ZIPX_ERR_LIMIT_FILE_SIZE before the request ever reaches the frontend
confirmation prompt, so the user just sees "卡壳"). The 256 GiB default
cap was tuned for PS5 system backups (many smaller entries) and was wrong
for the 3A-game single-file case.
Limits changes:
- src/zip_extract.c k_default_limits: 1 TiB / 256 GiB / 500 -> 2 TiB / 512 GiB / 500
- src/zip_extract.c k_large_limits: 2 TiB / 1 TiB / 1000 -> 4 TiB / 1 TiB / 1000 (must stay > default)
- assets/main.js LARGE_FILE_THRESHOLD_BYTES: 240 GiB -> 480 GiB
- assets/lang-{en,zh}.js extractLargeAsk copy reflects new numbers
- tests/test_zip_extract.c: large.max_total_bytes advertised-number assertion updated to 4 TiB
- README.md / docs/HANDOVER.md: limit tables, FAQ, threshold snippets updated
- CHANGELOG.md: v1.8.2 entry added above v1.8.1
PS5-only build fixes (caught by WSL cross-compile, host tests pass without):
- Makefile CFLAGS: add -Ithird_party/unrar so src/rar_extract.c can find
the dmc_unrar_api.h facade header (THIRD_PARTY_CFLAGS already had it
but the main CFLAGS for project sources did not).
- src/extract.c: move extract_progress() definition before
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/run-tests.sh).
Release artifact: web-file-mgr.elf 509 704 bytes,
sha256 1b2c3d68b35e32737105f17d14a80a3c159ceca0cabd274ee168cbcd81906f65,
e_machine 0x003e (x86_64-sie-ps5).
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 from the engine).
84 host-side checks (70 ZIP + 14 RAR), 0 failures. PS5 cross-compile
succeeds; ELF size grew from 427 656 (v1.8) to 509 704 (v1.8.2) due to
the dmc_unrar vendor TU being linked in.
User feedback: the previous default limits were an over-cautious UX
guard, not a security guard. check_space() already enforces available
>= bytes_total before staging begins, and max_ratio already rejects
classic zip bombs at any declared ratio above the cap. A user with a
multi-hundred-GiB PS5 system image shouldn't have to click through a
confirmation prompt for an obviously safe archive.
k_default_limits (src/zip_extract.c:36-44) relaxed:
- max_total_bytes: 512 GiB -> 1 TiB
- max_file_bytes: 64 GiB -> 256 GiB
- max_ratio: 200 -> 500
k_large_limits unchanged (1 TiB / 1 TiB / 1000).
Frontend threshold LARGE_FILE_THRESHOLD_BYTES (assets/main.js:838)
bumped 60 GiB -> 240 GiB to track the new default. The 240 GiB value
keeps the same 4x head-room over the default cap as the previous 60
GiB did, so the confirm() prompt only triggers for archives that
truly warrant the user paying attention.
assets/lang-{en,zh}.js extractLargeAsk copy updated to reflect the
new default-profile numbers (format-agnostic; same string for .zip and
.rar).
README.md: 'Stricter default' line under "What's new in v1.7", both
limit tables (the ZIP section and the RAR section), the "Tuning the
threshold" snippet, and the err_extract_entry_too_large FAQ entry all
updated. CHANGELOG.md gets a new [v1.8.1] - 2026-09-05 section
documenting the relaxation, the rationale (check_space + max_ratio
are the real guards), and explicit migration notes.
docs/HANDOVER.md and docs/UPGRADE-v1.8-rar-support.md numeric
references updated. The historical v1.7 upgrade document
(docs/UPGRADE-v1.7-zip-large-file-profile.md) is deliberately left
unchanged so the v1.7 -> v1.8.1 evolution remains traceable from git.
src/rar_extract.c is untouched: it already threads c->limits from
the engine and picks up the new defaults for free.
tests/test_zip_extract.c test_large_profile() rewritten for the new
ratio cap:
- medium_bomb.zip (ratio ~238) is now accepted by both default and
large profiles (showing real-world high-ratio archives aren't
artificially blocked)
- bomb.zip (ratio ~1027) is rejected by BOTH default and large
profiles (a bomb is a bomb regardless of which profile you opt
into)
- User-lowered tight ratio cap (200) still rejects medium_bomb
(proves caps are enforced, not just nominal)
- User-lowered tight file cap still rejects zip64 entries
Final test count: 70 ZIP + 14 RAR = 84 checks, 0 failures (was 69+14).
Migration:
- Forward-compatible: existing v1.7/v1.8 deployments that never
triggered err_extract_entry_too_large see no difference
- No data loss: relaxation only widens accepted archives
- Real guards (check_space, max_ratio, path traversal) unchanged
CHANGELOG.md — new [v1.8] — 2026-09-05 section covering Added / Changed
/ Limitations / Vendored / Verification / Technical notes / Credits.
ELF sha256 + size remain TBD until the user runs the WSL cross-compile;
the banner is anchored on the v1.7 sha256 for traceability.
README.md — adds 'What's new in v1.8' (immediately after the title
banner) describing RAR single-volume extraction. The new 'RAR
extraction' section documents scope (single-volume, RAR4 + RAR5,
unencrypted, plain file entries), limits (multi-volume/encrypted
rejected with surface-level err_extract_unsupported, symlinks/FIFOs
rejected, RAR1.4/1.5 rejected), error semantics, frontend wiring,
security notes, vendoring notes, and the v1.9 upgrade path.
'Project layout' tree gains third_party/unrar/ and the test runners
gain test_rar_extract.c. 'FAQ' err_extract_unsupported entry rewritten
to mention the RAR scope. Test count and ELF size estimate bumped to
83 checks and ~469 KiB respectively (dmc_unrar adds ~51 KiB).
docs/UPGRADE-v1.8-rar-support.md (new, 641 lines) — mirrors the
v1.7 upgrade document style. Includes architecture overview, facade-
header rationale, full rar_translate_error mapping table, frontend
detail, test coverage map, and a clear roadmap section listing what
v1.8 explicitly does NOT do and how v1.9 addresses each item.
docs/HANDOVER.md — §4 progress table updated (v1.7 -> done, v1.8 ->
substantially complete; WSL cross-compile + git push still pending
user-side). §9.1 corrects the license note: dmc_unrar is GPL-2.0-or-
later, NOT the 'UnRAR license' from the original plan. §12 snapshot
moved to v1.8; new §14 'v1.8 actual delivery state' written for the
hand-off recipient — captures what is actually in the tree vs. the
original §6 plan, and lists the remaining user-side steps verbatim.
THIRD_PARTY_NOTICES — section 3 added attributing DrMcCoy/dmc_unrar
1.7.0 (GPL-2.0-or-later), noting dmc_unrar_api.h is project-authored,
and pointing at third_party/unrar/COPYING for full license text.
assets/lang-{en,zh}.js — err_extract_unsupported rewritten to make
the RAR scope visible ('only unencrypted plain ZIP and single-volume
RAR are supported'). Both languages cover the same surface; the i18n
team can adjust tone later without changing meaning.
Tests/make_fixtures.py now patches zipfile.time so Python 3.13's
zipfile.writestr no longer embeds wall-clock date_time into each entry
(the module uses time.localtime(time.time())[:6] when the caller
supplies a string arcname, which silently made every fixture diff on
each regeneration and polluted git with thousands of unrelated byte
changes). The patch fakes localtime/time to always return
(1980, 1, 1, 0, 0, 0).
All .zip fixtures are regenerated against the new deterministic
generator and committed. notar.rar is regenerated alongside because
test_rar_extract.c's main() recreates it from basic.zip at run time;
including it here keeps the source fixture and the rar fixture in
lock-step.
Verified: 2 consecutive python3 make_fixtures.py runs produce identical
md5 for all 19 .zip fixtures; full test suite stays green
(69 ZIP + 14 RAR = 83 checks, 0 failures).
tests/test_rar_extract.c — 14 checks, zero dependency on host having
rar or 7z installed:
test_engine_dispatch (4):
- not-a-RAR rejected: notar.rar (renamed basic.zip) -> FORMAT
- junk blob rejected: junk.rar (1024 random bytes) -> FORMAT
- missing source -> OPEN
- null rar_path / dst -> INTERNAL
test_format_translation (6):
- rar_translate_error mapping table covers every dmc_unrar_return
code rar_extract can emit, verified against fixture files
test_limits_handoff (4):
- default vs large limits profile passed through unchanged
- max_entries / max_total_bytes / max_file_bytes / max_ratio
Uses tests/posix_compat.h (open/close/mkdirat renames) via gcc -include
for host builds -- never compiled into the PS5 payload.
tests/make_fixtures.py — adds rar_fixtures() that prefers host 'rar'
(non-free) and falls back to host '7z' (LGPL). If neither is present,
emits an 11-byte 'placeholder' basic.rar so the negative tests still
fire (basic.rar is positive-path, but is only read when a RAR writer
exists). Wired into the fn list in main().
tests/run-tests.sh:
- Builds third_party/unrar/dmc_unrar.o with -DDMC_UNRAR_DISABLE_BE32TOH_BE64TOH=1
- Builds rar_extract.o with -Ithird_party/unrar and posix_compat shim
- Builds test_rar_extract.o against both rar_extract.o and zip_extract.o
(the latter is required: rar_extract.c references zipx_status_string,
zipx_default_limits, zipx_limits_profile for error/log/output)
- Links test-rar-extract, then runs both test-zip-extract and
test-rar-extract in sequence.
Fixtures committed:
- basic.rar -- 11-byte placeholder (host has no RAR writer)
- notar.rar -- basic.zip renamed, for format-rejection test
- junk.rar -- 1024 random bytes, for format-rejection test
- All other .rar fixtures (encrypted, multi-volume, symlink, ...) are
generated on demand when run on a host with rar/7z installed.
Final test count on a writer-less host: 69 ZIP checks + 14 RAR checks
= 83 total, 0 failures (re-verified post-commit).
src/rar_extract.{c,h} (1276 LOC) — new public engine:
rar_extract(rar_path, dst_dir, conflict, limits, cancel, progress, userdata, result)
Mirrors the zip_extract contract exactly: same three-phase template
(scan -> extract to .wfm-extract-*.tmp staging + fsync -> publish via
rename -> cleanup/rollback), same zipx_status_t / zipx_limits_t /
zipx_conflict_t / zipx_result_t / zipx_progress_t types, same callback
signatures. Reuses publish_dir/publish_entry/publish_staging so a RAR
extract behaves identically to a ZIP extract under conflict-policy
(overwrite / merge / fail), rollback, fsync, and per-entry fsync.
Internals:
- rarx_ctx_t carries limits, callback, staging dir, published paths,
detail-msg accumulator, and the nameset for path-conflict detection.
- rar_translate_error maps every dmc_unrar_return code to a
zipx_status_t (UNSUPPORTED volumes/encryption/method -> UNSUPPORTED,
unsupported_link -> SPECIAL, oversized entry -> LIMIT_FILE,
CRC fail -> CRC, etc.) so the frontend needs zero per-engine branching.
- normalize_name converts backslash -> forward slash (RAR names may use
either on Windows) and detects directory entries by trailing slash
rather than relying on external attributes alone.
- open_parent_dirs / check_space / remove_tree / cleanup_staging /
rollback_published reused from zip_extract's pattern.
src/extract.c — adds extract_dispatch(file_task_t*, conflict, limits,
result*) routing .zip -> zipx_extract(), .rar -> rar_extract(), else
ZIPX_ERR_UNSUPPORTED ('only .zip and .rar are accepted'). The existing
extract_worker now calls dispatch instead of zipx_extract directly.
ends_with_ci() helper for the .rar suffix match (case-insensitive, trims
trailing '/' from paths that the frontend might send).
Makefile:
- VERSION_TAG := v1.8 (was v1.7)
- COMMON_SRCS adds src/rar_extract.c
- THIRD_PARTY_SRCS adds third_party/unrar/dmc_unrar.c (compiled as its
own TU; never inlined into rar_extract.c)
- THIRD_PARTY_CFLAGS adds -Ithird_party/unrar and
-DDMC_UNRAR_DISABLE_BE32TOH_BE64TOH=1 to avoid <endian.h> conflicts
with the SDK's headers.
Limitations surfaced as ZIPX_ERR_UNSUPPORTED with frontend-visible
err_extract_unsupported messages:
- multi-volume RAR (.partNN.rar chains) -- dmc_unrar upstream
limitation; documented v1.9 plan is to vendor opello/unrar C++
- encrypted RAR (password= ignored) -- dmc_unrar upstream limitation
- symlinks / FIFOs / device nodes -- ZIPX_ERR_SPECIAL
- RAR1.4 / RAR1.5 -- ZIPX_ERR_UNSUPPORTED
No ELF rebuild attached in this commit (cross-compile is WSL-side and
deferred to the release commit).
Vendored verbatim from upstream tag 'v1.7.0' (commit d0f3efe, 2024-09-14):
- dmc_unrar.c (375310 bytes, upstream unmodified)
- example.c (upstream reference, not used in build)
- COPYING (GPL-2.0-or-later)
- README.md (upstream README)
Project-authored additions:
- dmc_unrar_api.h: facade header exposing only the symbols rar_extract.c
uses (opaque archive struct + init/close/open/get_*_file/extract/*_to_path).
Avoids inlining dmc_unrar.c into rar_extract.c, which would pollute the
dmc_unrar_io_handler struct field names (open/close) with the host-test
posix_compat.h macros (wfm_open/wfm_close).
- VENDORED.md: rationale for choosing dmc_unrar over opello/unrar, supported
formats (RAR4/RAR5, single-volume, unencrypted), explicit limitations
(no volumes, no encryption, no symlinks, no RAR1.4/1.5), and the full
v1.9 upgrade recipe (vendor opello/unrar C++, swap RAROpenArchiveEx API,
rar_extract.c signature unchanged).
GPL-2.0 license obligation: web-file-mgr.elf must ship GPL notice + source
(THIRD_PARTY_NOTICES + this directory; both added in the next commit batch).
User reported FTP-upload-and-extract use case: after uploading
.part01.rar..part05.rar via FTP, the extract button should appear on
selection just like .zip already does, alongside the existing delete /
copy / move / rename buttons in the toolbar.
Changes are purely client-side. The extractBtn sits next to deleteBtn
in the toolbar (HTML layout unchanged). New helpers:
- isRarMainVolume(item): matches bare .rar AND .part01.rar/.part1.rar
/ .part001.rar, but excludes any .partNN.rar (those are sub-volumes)
- isRarSubVolume(item): matches .part02+.rar (the non-first parts)
- isExtractableArchive(item): .zip OR rar main volume
renderExtractButton rewiring:
- archives.length + subs.length both zero -> hidden (unchanged default)
- only subs selected -> button visible but disabled with tooltip
'select the main volume (.rar or .part01.rar)' (new i18n keys)
- exactly one archive selected -> button enabled with hover target
actionExtract wired to the new isExtractableArchive filter, keeping
the existing 'must be exactly 1 selected' guard so multi-select still
no-ops cleanly.
Caught a real bug while wiring: the first version of isRarMainVolume
matched /\.rar$/ before the part01 check, so part02+ was incorrectly
classified as a main volume. Fixed by reordering + adding negative
sub-pattern so .partNN. never trips the bare-.rar rule.
Two new i18n keys (zh + en):
- extractSelectMainVolume: friendly message when user picks only
sub-volumes
- extractArchivePending: reserved for the future RAR password prompt
10/10 unit cases pass for the detection regexes
(zip / .rar / .part01.rar / .part02.rar / .tar.gz / .bin).
Backend support for RAR is not part of this commit; clicking Extract
on a .rar on v1.7 still returns err_extract_unsupported from the
existing zip_extract pipeline. That will swap to the unrar engine in
v1.8 without any further frontend work.
Also sets local repo core.autocrlf=false to keep JS files LF on this
Windows dev machine (global autocrlf was rewriting them as CRLF on
write and causing the whole file to show up as changed on every edit).
Per user decision on 2026-09-04 21:01 GMT+8, the v1.8 plan no longer
covers the legacy split-archive format (name.rar + .r00/.r01/.r02...).
Rationale: PS5 users almost exclusively ship the modern RAR5
part-volume format (name.part01.rar, name.part02.rar, ...); the
legacy .rNN format is from the 2005-2010 era. Removing it cuts:
- Frontend: isRarSubVolume() no longer needs /\.r\d{2,}$/ branch
- Fixtures: drop rar4_multi_old.rar generation
- Tests: drop test_rar4_multi_volume_old()
- Backend: unrar API surface still supports legacy .rNN, but we just
never feed it one
Documentation sweep:
- Section 5.1: range updated
- Section 6.4.1: isExtractableArchive/isRarSubVolume simplified
- Section 6.5.1: fixture generation script updated
- Section 6.5.3: test_rar4_multi_volume_old removed
- Section 8.4: decision rationale no longer mentions .rNN
- Section 6.6.4: CHANGELOG / README example updated
- Section 11.2: test matrix updated
- Section 6.4.9: D4 manual test list no longer tests .r00
Comprehensive handover document for a developer taking over the project.
Covers project background and key facts, v1.7 ZIP large-file profile across
every layer, current progress snapshot, v1.8 RAR (single + multi-volume +
encrypted) detailed 6-day plan with day-by-day tasks, validation checklists,
key code templates, engineering rationale, 10 known pitfalls, common
commands, test matrix.
Document size: 1529 lines, 48 subsections. Designed to be the single
source of truth for continuing v1.8 development without context from
the original conversation.
CHANGELOG.md: Keep-a-Changelog format, single v1.7 entry covering the
ZIP large-file profile, three-phase engine, host test suite and known
limitations; verification snippet.
docs/UPGRADE-v1.7-zip-large-file-profile.md: long-form technical write-up
for maintainers — architecture delta, engine/task/HTTP contract, frontend
wiring, tests, build pipeline, compatibility matrix, trade-offs and
post-v1.7 roadmap.
Homebrew HTTP file manager payload for jailbroken PS5 consoles (x86_64-sie-ps5,
title id FMGR88888). Browse, edit, upload, download and extract ZIPs through
any browser on the LAN; single self-contained ELF, no external services.
Highlights (v1.7):
- ZIP large-file profile (large=1): 2 TiB total / 1 TiB per entry / 1000:1
ratio. Frontend prompts for confirmation whenever archive > 60 GiB on disk.
- Default ZIP profile stays safe: 512 GiB / 64 GiB / 200:1.
- 69 host-side C tests (tests/run-tests.sh) covering traversal, ZIP64,
encryption rejection, conflict policies and the large-file profile.
- Three-phase extraction engine (scan -> extract -> publish -> cleanup) with
atomic staging + rename, located in src/zip_extract.{c,h}.
Project layout:
src/ C payload sources
assets/ HTML / CSS / JS / icons / param.json
tests/ POSIX/host test suite + fixtures
third_party/ vendored zlib + minizip-ng (zlib license)
docs/screenshots/ README screenshot images
LICENSE GPLv3+
Build:
make # cross-compile with PS5_PAYLOAD_SDK
make linux # Linux host binary for UI dev