Files
ps5upload/third_party/unrar
PhantomPtr 5954c8adde 6.0.0: AVA1 replaces FTX2 — one encrypted connection for everything (#374)
See CHANGELOG.md (6.0.0). Full history of the work: tag ftx2-last and branch history archived in ps5upload-docs.
2026-10-05 00:25:05 -07:00
..
2026-09-27 04:31:13 -07:00
2026-09-27 04:31:13 -07:00
2026-09-27 04:31:13 -07:00

unrar 0.5.8 — vendored, with one fix

Upstream: https://github.com/muja/unrar.rs (MIT OR Apache-2.0). Only src/ and the licences are vendored; upstream's examples, tests and .rar test data are not, so this stays a small patch rather than a fork.

Both workspaces redirect to this copy via [patch.crates-io]:

engine/Cargo.toml              unrar = { path = "../third_party/unrar" }
client/src-tauri/Cargo.toml    unrar = { path = "../../third_party/unrar" }

The change

One hunk, in src/open_archive.rs, in the UCM_CHANGEVOLUMEW arm of Internal::callback. Upstream:

WideCString::from_ptr_truncate(p1 as *const _, 2048)

from_ptr_truncate(p, len) builds slice::from_raw_parts(p, 2048) and hands it to from_vec_truncate, which takes impl Into<Vec<_>> — so all 2048 elements are copied first, and only then truncated at the NUL.

UnRAR passes NextName.data() from a std::wstring holding a path (DllVolNotify, volume.cpp:274). For a ~60-character volume name that copy reads 8 KB out of a far smaller allocation: a ~7.7 KB out-of-bounds heap read, on every volume transition.

The fix scans for the NUL first (still capped at UnRAR's 2048) and copies only what is actually there.

Second change: a streaming sink mode

StreamSink, ReadToSink, process_file_raw_seeded and read_to_sink are local additions (all marked ps5upload local addition in the source).

Upstream's only way to get an entry's bytes without writing them to disk is read(), which returns a Vec<u8> — the whole entry in memory. PS5 game dumps carry single entries of 30 GB and more, so ps5upload needs the chunks as UnRAR produces them.

The C API already delivers data incrementally through UCM_PROCESSDATA; upstream's ReadToVec just accumulates it. ReadToSink forwards each chunk to a bounded channel instead, under the same Operation::Test that ReadToVec uses — which decompresses without writing files. Sending on a full channel blocks, which is exactly the backpressure that keeps peak memory at a few chunks.

process_file_raw_seeded exists because process_file_raw builds its Output with Default::default(), and a channel cannot be conjured from Default.

Third change: a callback sink that can stop UnRAR

read_to_fn / FnSink / ReadToFn and ProcessMode::abort are local additions. read_to_fn decompresses an entry through a closure on the calling thread; the closure returns false to stop, and the UCM_PROCESSDATA callback then answers -1, which makes UnRAR abort at once instead of finishing the entry (a 30 GB entry would otherwise run to the end). The AVA1 RAR source uses it so a cancel, or a failing upload lane, ends a decode (including the decode-and-discard of a solid archive's already-uploaded entries) within one callback piece.

Why vendored rather than upgraded

0.5.8 is the latest release; there is no upstream fix to take. The analysis is written up as a ready-to-send issue in docs/unrar-upstream-bug.md.

What it looked like

Any multi-part .rar aborted a debug build:

unsafe precondition(s) violated: ptr::copy_nonoverlapping
  unrar::open_archive::callback   (open_archive.rs:519)
  DllVolNotify / MergeArchive     <- switching to the next volume

Release builds compiled the check out and appeared to work, because the copy is still truncated at the true NUL — the value was right and only the over-read was unsound.

If you update this crate

Re-apply the hunk, or drop the vendoring entirely once upstream ships a release containing the fix (check the UCM_CHANGEVOLUMEW arm). The patch is marked in the source with a ps5upload local fix comment.