First release under the fork-marker convention: VERSION_TAG carries a trailing
`M`, so /api/version, the PS5 start-up notification, the stdout banner, the UI
footer and the ELF file name all read "v1.9.3M" in one move -- and a fork build
can no longer collide with an upstream artifact of the same version, a mix-up
that already happened twice. The footer carries a tooltip spelling the marker
out.
Two bodies of work.
1. Encrypted archives (ZIP / RAR / 7z)
- ZIP: ZipCrypto (traditional PKWARE) and WinZip AES-256, through
minizip-ng plus a vendored crypto layer (mz_crypt_wfm.c,
mz_strm_pkcrypt.c, mz_strm_wzaes.c).
- RAR: RARSetPassword, wired after RAROpenArchiveEx and before the first
RARReadHeaderEx. The ordering is load-bearing, not stylistic.
- 7z: 7zAES including -mhe=on encrypted headers, via a virtual
ISeekInStream that splices a pseudo-header + the real archive + the
decrypted header, so no offset stored inside the archive has to move.
A wrong password is reported as ZIPX_ERR_PASSWORD, and a failed attempt
leaves no staging directory behind.
2. Reporting, and the UI round that on-device testing produced
- A RAR whose dictionary exceeds what the build supports now gets its own
extract_dict_too_large code instead of being mis-reported as "entry too
large"; the message names both the required and the supported size. The
behaviour is deliberately unchanged -- such archives are still refused,
because admitting one means allocating the whole window up front, which
is why rarlab's own CLI refuses them by default.
- The upload entry is a menu again: one "Upload" button opening "Upload
files / Upload folder". The previous main-button-plus-small-arrow made
"upload folder" effectively undiscoverable.
- A drag-and-drop hint sits in the footer (hidden on the console browser,
where drag is not how anyone uploads).
- The extract button is now always present and merely disabled until
exactly one archive is selected, instead of appearing out of nowhere.
- Upload-and-extract on an encrypted archive now prompts for the password
directly. The retry table used to be keyed by PATH, and for a non-ASCII
directory the string the page holds and the string the server reports
are not the same bytes -- the lookup missed, so the user got a bare
error box and had to press Extract by hand before the prompt appeared.
It is now keyed by task id, which the server assigns and echoes back
verbatim.
- Error text passes through decodeFsText(), so a GBK entry name no longer
surfaces as `â®…ç§.psd`.
- Local names are encoded with encodeFsText() before being joined onto a
server-side path. fs_path_value() declines to rewrite a path if ANY code
point exceeds 0xFF, so concatenating a local name onto a server directory
produced a mixed representation and a silently dead path.
- The menu row highlight was losing the cascade to the generic button rule
(identical specificity, later in the file) while inheriting the toolbar's
3px focus ring, which overflowed a 46px row. Both rules are now scoped to
the panel and the keyboard cue is an inset ring, so it cannot escape the
row at any line height.
- The footer status line is clamped to a single line; a long
"uploading 3/12: some-name.zip" used to wrap out of the 46px footer.
- A first failed password attempt now says the archive is encrypted,
instead of blaming a password the user was never asked for.
Artifact
web-file-mgr-v1.9.3M.elf
903,448 B
sha256 8ca47d5aaca75085b32641300cce30fadb7df7749cb6b53d04f129bcecc286b7
e_machine 0x003e (x86-64 / PS5)
The file size is identical to the four builds before it, and every one of
them carries a different sha256: only .rodata moved, and by less than the
16 KiB section alignment absorbs. Compare sections with `readelf -SW` --
never infer "nothing changed" from the byte count.
Verification
- host suites: 140 ZIP + 37 RAR = 177 checks, 0 failures
- 7z suite: 27 cases, 0 failures. The `aeshe` entry that used to sit in
KNOWN_GAPS is gone -- the -mhe=on fixture now passes both the folder
decoder and the extraction facade
- frontend: .build/ui_retry_test.mjs (40 checks), .build/ui_upload_menu_test.mjs
(40 checks), .build/preview_check.mjs (12 assertions in headless Chromium
against the real page and a fixture API). Two layout regressions and the
highlight cascade bug were caught by the last one and by nothing else --
reading the source, both CSS rules "look correct"
- built twice from this tree: byte-identical (cmp clean). rsync refreshes
every asset mtime, so this is a genuine recompile, not make short-circuiting
on unchanged sources
- embedded assets verified in place with .build/check-elf-gzip.py, because
gen-asset-module.py gzips them and plain `strings` finds none of their text
- exercised end to end on a real PS5; the checklist is
docs/DEVICE-TEST-v1.9.3M.md
Docs
- docs/USER-GUIDE-zh-CN.md (new, simplified Chinese user guide)
- docs/DEVICE-TEST-v1.9.3M.md (new, on-device acceptance checklist)
- docs/REAL-CONSOLE-PROFILE.md (new, measured console behaviour)
- docs/archive/HANDOVER-v1.8-planning.md (superseded v1.8 design notes)
- CHANGELOG / README (both languages) / HANDOVER updated with the artifact
fingerprint, the section deltas and the new test counts
The v1.9.1 tag was a lightweight tag sitting on 9a36c3c, four commits BEHIND
the tree that produced the released ELF, so `git clone --branch v1.9.1` could
not rebuild the published artifact. v1.9.2 is that same tree with the version
literal bumped, which puts the tag back on the commit the binary came from.
Source delta vs the v1.9.1 tag (all of it already shipped inside the v1.9.1
binary -- the tag was just wrong, nothing was missing from the download):
- src/demangle_stub.c + -Wl,--icf=all: 1,034,328 -> 870,488 B (-15.8%)
The demangler was only reachable from the uncaught-exception path.
- upload entry: single-click file/folder pick, plus drag-and-drop upload
- docs: language switcher in both READMEs
Artifact
web-file-mgr-v1.9.2.elf
870,488 B
sha256 177e90fecf93a0251e83f67884fba4551051be248330b0d70fda8ea732f88e84
e_machine 0x003e (x86-64 / PS5)
Verification
- 163 checks (ZIP 108 + RAR 27 + 7z 28), 0 failures; `aeshe` remains the
known -mhe=on gap
- the build is reproducible: reverting the two version literals and
rebuilding reproduces the v1.9.1 ELF byte for byte, and re-applying them
reproduces this one. That is what pins the byte-level difference between
the two artifacts down to the version string alone
- docs/SIZE-OPTIMIZATION.md gains appendix A (artifact-consistency
verification, incl. the 55,280-byte raw diff explained as linker string
pool relayout) and appendix B (per-symbol accounting: 628 symbols
removed, 0 added, 76 ICF fold groups)
Not yet validated on retail hardware.
Most 7z folders are a single plain LZMA2 coder (7-Zip default -m0=lzma2
-ms=on). For that shape the facade now drives the SDK's own parallel
decoder (Lzma2DecMt, vendored with MtDec/Threads) instead of the
single-threaded chain walk; the chain stays responsible for every other
shape (BCJ2 stacks, encrypted folders, exotic methods) and as the
fallback when the platform cannot provide threads (SZ_ERROR_THREAD
downgrades, never fails).
Adapters: ISeqInStream over the read_at callback, ISeqOutStream into the
staging sink (runs on the calling thread, so the sink contract is
unchanged), ICompressProgress polling the cancel hook. The folder CRC is
taken over the decoded stream as before.
329 MiB fixture, internal timing: 1050 ms (asm, 1 thread) -> 930 ms
(4 threads) -> 765 ms (8 threads + 1 MiB inBufSize_MT), i.e. 1.37x,
now at parity with 7za -mmt=off (898 ms); 7za -mmt=8 is 485 ms. The PS5
count is 8 Zen 2 cores like the dev host; SZX_MT_THREADS=8.
Test matrices: 7z 28 + ZIP 108 + RAR 27 checks green.
The footer string in the browser was a hard-coded `const APP_VERSION = "v1.9"`
in assets/main.js, completely disconnected from the Makefile's VERSION_TAG.
It had already drifted: the UI kept showing "v1.9" through the entire v1.9.1
release, while the startup notification and the ELF name both said v1.9.1.
Two sources of truth for one fact is one too many.
Backend:
* new src/version.c -- GET /api/version -> {"ok":true,"version":"v1.9.1",
"titleId":"FMGR88888"}, built straight from the -DVERSION_TAG /
-DTITLE_ID macros the Makefile already passes, so there is nothing left
to forget when bumping a release.
* registered in filemgr_api_request() next to /api/space and declared in
filemgr_internal.h; added to COMMON_SRCS so both the PS5 and the linux
targets link it.
Frontend:
* the literal becomes APP_VERSION_FALLBACK and is rendered first so the
footer is never empty, then loadVersion() refreshes it from the API in
the background. A failed request is deliberately swallowed -- a wrong
version string is cosmetic and should not raise an error toast.
* both the host i18n path and the PlayStation browser branch are
untouched; the footer element itself (#versionText) does not move.
Note the earlier answer to "does the version show in the PS5 UI?" was that it
does -- the bottom-right footer -- but it was showing the stale literal, not
the build's real tag. That is what this fixes.
Verified:
* WSL prospero-clang 18.1.8 -- ELF 1017864 B, e_machine=0x003e, contains
both the string "v1.9.1" and the "/api/version" route.
* MinGW gcc 16.2.0 host tests -- ZIP 108 + RAR 27 + 7z 28 = 163 checks,
0 failures.
Every build overwrote the same `web-file-mgr.elf`, so the only way to tell two
builds apart was the sha256 -- and the one thing you cannot see from a
filename on a USB stick is which firmware you are about to run. Derive the
binary name from VERSION_TAG:
BIN := web-file-mgr-$(VERSION_TAG).elf
LINUX_BIN := web-file-mgr-linux-$(VERSION_TAG)
Because VERSION_TAG also feeds -DVERSION_TAG, bumping it now updates the
embedded version string, the PS5 notification and the filename in one place.
The build scripts must not hardcode the name or they break the moment the
version changes, so build-elf-wsl.sh now reads it back out of the Makefile:
VERSION=$(sed -n 's/^VERSION_TAG *[?:]*= *//p' Makefile | head -1)
BIN_NAME="web-file-mgr-${VERSION}.elf"
rsync exclude widened to /web-file-mgr-*.elf, and build-win.sh now always
re-pushes the WSL script (it only copied it when missing, so editing
build-elf-wsl.sh silently kept running the stale copy).
Verified: same sources as the previous commit produce a byte-identical binary
under the new name -- sha256 94851c26ebe1a3b296fb78748aa8de90ea0c022aa4004d64e700d4156a0cc64a
both before and after, so this commit is pure plumbing.
The ELF we tagged v1.9.1 still reported "Version: v1.9" -- Makefile line 16
pinned VERSION_TAG by hand and nobody updated it when the tag was cut, so the
PS5 boot notification and `web-file-mgr.elf --version` both mislabeled the
binary. Bump it to v1.9.1 and switch := to ?= so a one-off build can override
it (`make VERSION_TAG=v1.9.2`) without touching the file.
Rebuilt ELF:
1017864 B, sha256 94851c26ebe1a3b296fb78748aa8de90ea0c022aa4004d64e700d4156a0cc64a
e_machine=0x003e, `strings` confirms the embedded literal is now v1.9.1
.gitignore: the per-directory deny list for .build/ never kept up -- every
debugging session drops a new probe directory (aespw/, bigzip/, chaincheck.py,
dbg/, engine-e2e/, ...) and they all showed up as untracked. Flip to a
whitelist: ignore .build/* and re-allow only the files that are genuinely part
of the repo. Also ignore .workbuddy/ (session data: never commit, never delete).
PS5 (Zen 2) has every AES-NI / AVX / VAES instruction set, but prospero-clang
defaults to a generic x86_64 target that does NOT pre-declare
_mm256_aesenc_epi128 -- AesOpt.c relies on a compiler-version macro that lets
clang 18 in unconditionally, so it blew up with 20 errors and stopped at
-ferror-limit= before the rest of the build could even run.
You can't drop AesOpt.c either: Aes.c references the
AesCbc_{Encode,Decode}_HW / AesCtr_Code_HW[256] symbols via AesGenTables,
so without AesOpt.c the link fails (five undefined-symbol errors).
Fix: add a path-filtered CFLAGS_7Z = -maes -mavx2 -mvaes and apply it only to
third_party/7z/*.c -- zlib and minizip-ng don't need it, the project sources
don't need it, and it's a no-op at runtime since the symbols exist on PS5.
Verified:
* WSL prospero-clang++ 18.1.8 (FreeBSD sysroot) -- success
web-file-mgr.elf 1017864 B, sha256 2eb04581...473a157, e_machine=0x003e
* MinGW gcc 16.2.0 host tests -- 163 checks (ZIP 108 + RAR 27 + 7z 28) all pass
With the extraction engine in place (021c9cb), the /api/extract task
finally picks .7z files up.
* src/filemgr_internal.h -- file_task_t gains extract_password (256
bytes, UTF-8, the size a sane user will never exceed but enough for the
encrypted RAR / 7z passwords the engine actually accepts).
* src/extract.c -- extract_dispatch() routes a single .7z to sevenz_extract()
and a byte-split set (name.7z.001, .002, ...) to the same call (the
volume detector classifies by extension). api_extract() reads the
optional "password" form field and copies it into the task; the error
code map gains ZIPX_ERR_PASSWORD -> "extract_password".
* Makefile -- the sevenz_extract / sevenz_chain / sevenz_volstream sources
join COMMON_SRCS, and every third_party/7z/*.c (LZMA SDK 26.03, public
domain, decoder subset) joins THIRD_PARTY_C_SRCS. The C flag set gains
-Ithird_party/7z and -DZ7_PPMD_SUPPORT, the latter so PPMd is built
in (the SDK guards PPMd sources behind a macro that defaults to off).
* assets/main.js -- isSevenZipArchive + isSevenZipSplitVolume; both feed
isExtractableArchive. actionExtract() now prompts for a password on
7z archives (encrypted-stream support is opt-in -- the engine rejects
unencrypted archives with no password just fine, but the prompt saves
a wasted scan when an archive is encrypted). startExtractTask() takes
the password and sends it as a form field.
* assets/lang-{en,zh}.js -- extractPasswordAsk string for the new prompt.
ZIP 108 / RAR 27 / 7z 26 still all green on the host.
No real-device build attempted in this commit: the WSL runner is the
one that knows the PS5 toolchain, and the user has to be the one to
push it through. The Makefile changes are the wiring the cross
compiler needs; a `make linux` on a Linux host with libmicrohttpd-dev
should link end-to-end.
A split ZIP is several files that only form an archive together. Until now a
`.zip.001` was rejected as an unsupported format and a `.z01`/`.partN.zip`
member could not be resolved, so these downloads were unusable.
Three naming conventions are recognised, from any member of the set:
name.zip.001, name.zip.002, ... byte split (7-Zip)
name.part1.zip, name.part2.zip byte split (WinRAR)
name.z01, ..., name.zip zip split disks (Info-ZIP / PKZIP)
The parts are served through a new stream that makes them look like one
archive, so nothing is copied to disk first (a 160 GiB set would otherwise
need a second 160 GiB scratch copy):
- src/zipx_volstream.c implements an mz_stream over the ordered part list.
Byte splits keep absolute offsets; zip split disks carry offsets relative to
the disk an entry starts on, so that mode honours
MZ_STREAM_PROP_DISK_NUMBER the way mz_zip_entry_seek_local_header drives it
and starts on the last volume, where the central directory lives.
- src/zipx_volume.c groups the set, verifies the numbering is complete and
reports exactly which volume is missing.
- zip_extract.c tries the layout implied by the naming first and the other one
as a fallback, so tools that disagree about offset bases still work.
- extract.c dispatches volume members to the engines and, when the caller
asked for the source to be deleted, removes every volume instead of leaving
orphaned parts behind.
Error messages are specific on purpose: an incomplete set reports the missing
file name (e.g. "gap.zip.002 is missing"), a lone first volume says the
remaining volumes are missing, `.rar.001` sets explain the rename that makes
them work, and 7z volumes say 7z is not supported yet.
Tests: tests/make_split_fixtures.py builds all three layouts (the disk set is
written with per-disk offsets exactly as APPNOTE 4.4.11 describes) plus two
broken sets; test_zip_extract.c extracts each layout from a different member
and byte-compares the nested binary entries. 108 ZIP + 27 RAR checks, 0
failures.
Host-side verification that the ZIP engine can extract a >4GiB zip64
entry end to end (4.7 GiB fixture, content byte-compared):
- tests/compat/sys/statvfs.h: resolve the path before taking the drive
letter (relative paths used to fail) and scale f_bavail by 4096 since
`unsigned long` is 32-bit on Windows and silently truncated ~250GB
down to ~2.3GB; real 64-bit hosts and the PS5 (LP64) are unaffected.
- tests/bigfile_e2e.c: standalone driver to extract one large archive
and print the engine result (verification via cmp/sha256 by caller).
- tests/run-tests.sh: incremental clean instead of `rm -rf` of the whole
build tree.
- Makefile: force -DHAVE_FSEEKO so minizip uses fseeko/ftello instead of
falling back to fseek/ftell on libcs without the probe.
Result: 4.7 GiB zip64 extracts OK on host in ~6s, output identical to
source. Engine 64-bit write path confirmed clean; PS5 failure needs the
strerror passthrough build for real-machine errno capture.
unrar rijndael/system call __builtin_cpu_supports (AES-NI probe) which
clang lowers to __cpu_model + __cpu_indicator_init; the FreeBSD-style PS5
sysroot ships no libgcc, so the link failed. Add a PS5-only (x86_64, non
linux/win) stub TU; host builds keep the real libgcc-provided symbols.
The link driver is now the C++ compiler (needed for unrar objects), but
it treats the .c files on the command line as C++ and clang 18 rejects
that with -Werror=deprecated. Pass -x c before the C source list and
-x none before the object files.
isnt.cpp / motw.cpp need windows.h (OS version / Zone.Identifier checks)
and are not in the official unrar UNIX makefile; the _UNIX branch
#ifdef's their symbols out, so PS5 and linux builds must not compile
them (first PS5 build failed on isnt.cpp). run-tests.sh MinGW flow
keeps them.
build-elf.sh: make failure previously fell through to step 7 which
reported the STALE elf as OK (sha unchanged, user confused); make now
aborts the script (pipefail already set).
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.
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.
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).
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