Compare commits

...
24 Commits
Author SHA1 Message Date
Songlx516 76c4eadbf5 docs(changelog): fill v1.9 ELF sha256 (bb8f17e9...) 2026-09-05 23:34:36 +08:00
Songlx516 ad765776f7 fix(build): stub libgcc __cpu_model symbols for PS5 link
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.
2026-09-05 23:34:35 +08:00
Songlx516 ae41434089 fix(build): restore POSIX type shims in unrar_c_api.h
The _WIN32-only include of windows.h left PS5/prospero builds (no _WIN32,
no _UNIX for project sources) with PASCAL/HANDLE/LONG undeclared when
rar_extract.c pulled in dll.hpp. Restore the _UNIX mirror block that was
lost in an earlier edit.
2026-09-05 23:31:38 +08:00
Songlx516 af6b440f33 fix(build): compile project C sources with -x c when linking via clang++
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.
2026-09-05 23:30:03 +08:00
Songlx516 01a6616472 fix(build): exclude Windows-only unrar sources on PS5/linux; abort on make fail
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).
2026-09-05 23:26:37 +08:00
Songlx516 068983b062 release(v1.9): docs, notices and version bumps (ELF sha TBD until WSL build)
- CHANGELOG: new [v1.9] section (engine swap rationale, v6 / multi-volume /
  decryption-capable, 94 checks); banner with TBD size/sha256.
- README: header version v1.9; "What's new in v1.9"; RAR extraction section
  rewritten for unrar 7.20.1 (v6 + multi-volume supported, encrypted pending
  password plumbing); vendoring/licence and old v1.9-plan sections replaced.
- THIRD_PARTY_NOTICES: dmc_unrar GPL-2.0 entry -> rarlab UnRAR freeware
  licence entry (third_party/unrar7/license.txt); dmc_unrar history note.
- assets/main.js APP_VERSION "v1.8.3" -> "v1.9" (footer version string).
- Makefile VERSION_TAG v1.8 -> v1.9.
2026-09-05 23:23:21 +08:00
Songlx516 da67bfa284 test(rar): commit real RAR fixtures (v6/encrypted/3-volume)
Generated once with tests/make-rar-fixtures.bat (WinRAR RAR 7.23) so
checkouts without WinRAR still exercise the real-archive happy paths.
basic-rar4.rar is absent because RAR 7.x dropped RAR4 writing; that
check SKIPs.
2026-09-05 23:18:39 +08:00
Songlx516 452b9b0187 fix(extract): handle multi-volume split segments in scan and extract
unrar in RAR_OM_EXTRACT mode presents a file that spans volumes as
several header segments with the SAME name, flagged RHDF_SPLITBEFORE/
SPLITAFTER. The scan dedup rejected the continuation segments as
"duplicate entry name" and the whole multi-volume extract failed.

Fix: segments after the first (RHDF_SPLITBEFORE set) skip the
normalize/dedup/size/limit accounting in scan (the first segment already
carries the full size) and, in extract, still call RARProcessFile(EXTRACT)
to drive the volume chain but are not counted or size-accumulated.

Verified against real fixtures (tests/fixtures-real/, generated by
tests/make-rar-fixtures.bat on the user's RAR 7.23): a 512 KB file split
across vol.part1/2/3.rar extracts whole (status ZIPX_OK, big.bin + root.txt).
Host suite now 70 ZIP + 24 RAR checks, 0 failures.
2026-09-05 23:18:18 +08:00
Songlx516 91757bfd02 fix(tests): preserve dir structure in rar fixtures (-ep1 was flattening)
-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.
2026-09-05 23:08:41 +08:00
Songlx516 30d0decf58 test(rar): keep real fixtures in fixtures-real/ (make_fixtures wipes fixtures/)
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.
2026-09-05 23:05:37 +08:00
Songlx516 22a4b5c76a fix(tests): name volume target vol.rar so rar yields vol.partN.rar
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.
2026-09-05 23:02:01 +08:00
Songlx516 ff010add68 fix(tests): delete stale vol files before re-splitting
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.
2026-09-05 23:01:17 +08:00
Songlx516 68056f2d9f fix(tests): force real multi-volume fixture with 512KB stored payload
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.
2026-09-05 22:59:21 +08:00
Songlx516 3db921e408 fix(tests): make-rar-fixtures.bat pure ASCII + RAR4 tolerant
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.
2026-09-05 22:58:10 +08:00
Songlx516 c58c145733 test(rar): real-archive fixtures generator + v1.9 happy-path checks
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.
2026-09-05 22:53:23 +08:00
Songlx516 a0482a41ed feat(extract): swap RAR engine to unrar 7.20.1 (v1.9, v6/multi-volume)
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.
2026-09-05 22:40:58 +08:00
Songlx516 c68c0def35 vendor(unrar): add unrar 7.20.1 (RARLab source, opello mirror)
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.
2026-09-05 21:57:15 +08:00
Songlx516 8344b9bae0 fix(ui): APP_VERSION now reads v1.8.3 (was hardcoded "v1.7")
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.
2026-09-05 21:29:21 +08:00
Songlx516 cb82ee439d docs(changelog): v1.8.3 also ships the build-script rebuild-detection fix
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.
2026-09-05 20:30:16 +08:00
Songlx516 9e9830a214 fix(build-elf.sh): include assets/* and gen-asset-module.py in change check
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").
2026-09-05 20:11:55 +08:00
Songlx516 687ef6b297 feat(upload): "Upload and extract" now accepts .rar (v1.8.3 hotfix)
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.
2026-09-05 20:04:11 +08:00
Songlx516 36ea055e70 docs(readme): sync numbers to v1.8.2; add v1.8.1 + v1.8.2 sections
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.
2026-09-05 16:21:15 +08:00
Songlx516 2a346d694c relax(extract): default limits 256 GiB -> 512 GiB / 1 TiB -> 2 TiB; large 2 TiB -> 4 TiB; threshold 240 GiB -> 480 GiB (v1.8.2)
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.
2026-09-05 16:10:31 +08:00
Songlx516 bf55a4e4fd relax(extract): default limits 64 GiB -> 256 GiB / 512 GiB -> 1 TiB / 200 -> 500 (v1.8.1 hotfix)
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
2026-09-05 15:42:47 +08:00
192 changed files with 35922 additions and 13574 deletions

No files matched your search

+13 -3
View File
@@ -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"
@@ -162,10 +166,16 @@ if [ -f web-file-mgr.elf ]; then
echo " 无源码变更 -- 跳过 make"
else
echo " 源码变更: $NEEDS_REBUILD"
make all 2>&1 | tail -40
if ! make all 2>&1 | tail -60; then
echo "FAIL: make all 失败(见上)。注意: 旧 ELF 仍留在原地, 但不算新产物"
exit 6
fi
fi
else
make all 2>&1 | tail -40
if ! make all 2>&1 | tail -60; then
echo "FAIL: make all 失败(见上)"
exit 6
fi
fi
# 7. 验证 + 同步回 Windows
+280 -6
View File
@@ -4,14 +4,288 @@ 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:
> `web-file-mgr.elf` — size TBD (cross-compile runs in WSL — see `docs/HANDOVER.md`)
> sha256 TBD
> Release artifact for v1.9:
> `web-file-mgr.elf` — size 919 440 bytes (~897 KiB)
> sha256 `bb8f17e9addc6a9984f611503ca01b51f8984b773d353630da1f25bd1a28a997`
> ELF class 64, little-endian, e_machine `0x003e` (x86_64-sie-ps5)
>
> Source delta vs v1.7: +2 vendored files (`third_party/unrar/dmc_unrar.c`,
> `third_party/unrar/dmc_unrar_api.h`), +1 new source pair
> (`src/rar_extract.{c,h}`), `src/extract.c` gains a dispatch layer.
> Source delta vs v1.8.3: RAR engine replaced (dmc_unrar 1.7.0 → rarlab
> UnRAR 7.20.1, `third_party/unrar/` → `third_party/unrar7/`), new
> `src/rar_extract.c` scan/extract implementation, Makefile + host-test
> C++ rules, 5 real RAR fixtures committed. See [v1.9] below.
>
> 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.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.9] — 2026-09-05
**RAR engine replaced: rarlab UnRAR 7.20.1 (v6 / multi-volume / decryption-capable).**
The vendored dmc_unrar 1.7.0 only dispatched RAR5 compression version 5
(`switch(file->version)` case `0x5000`). Archives written by WinRAR 6.x /
7.x (algorithm string `v6`, version field `0x5001`) hit the default branch
and surfaced as "corrupt archive" — confirmed on a real `v6:8M` archive.
v1.9 swaps in the official rarlab UnRAR source (7.20.1) via its
C-compatible DLL API, compiled as a static library (`-DRARDLL`, PS5 uses
the toolchain's default `libc++`).
What this enables:
- **RAR5 "v6" compression** (WinRAR 6/7 archives) — the v1.9 trigger.
- **Multi-volume RAR** (`.partNN.rar`): unrar stitches volumes by name when
all parts sit next to the opened volume. Select the first volume
(`name.part1.rar`) and extract as usual.
- RAR4 and older RAR5 remain supported.
- The engine *can* decrypt encrypted archives (`RARSetPassword`), but the
password channel (API + UI) is not wired yet — encrypted headers/entries
still fail up front with `err_extract_unsupported`. Planned for a follow-up.
Engine changes:
- `src/rar_extract.c` rewritten to unrar's sequential DLL API
(`RAROpenArchiveEx → RARReadHeaderEx → RARProcessFile`); scan and extract
each re-open the archive. Multi-volume continuation segments
(`RHDF_SPLITBEFORE`) are advanced but not re-counted/deduped.
- The three-phase scan → staging → publish/rollback machinery is unchanged.
- Bug fix: `normalize_name()` no longer clears the caller's directory flag
(a real v6 archive with an explicit directory header after its files
tripped the duplicate detector).
Build & test:
- Makefile: `.cpp` rules for the unrar RARDLL source set (49 files, mirrors
`UnRARDll.vcxproj`); links through the C++ driver; `VERSION_TAG` v1.9.
- tests: 5 real RAR fixtures committed under `tests/fixtures-real/`
(generated with `tests/make-rar-fixtures.bat` + WinRAR); new happy-path
checks extract a real v6 archive, verify files on disk, auto-merge a
3-volume split, and reject encrypted archives. Total: **70 ZIP + 24 RAR
= 94 checks** (up from 70 + 14; the old 14 RAR checks never ran a real
archive).
Credits: unrar (c) Alexander Roshal, freeware license — see
`third_party/unrar7/license.txt` and `THIRD_PARTY_NOTICES`.
## [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
**Hotfix: relaxed default ZIP extraction limits.**
The default `k_default_limits` profile is now **1 TiB total / 256 GiB per
entry / 500 : 1 ratio** (was 512 GiB / 64 GiB / 200 : 1). The frontend
threshold `LARGE_FILE_THRESHOLD_BYTES` is bumped from 60 GiB to 240 GiB
to match. The `large=1` profile (1 TiB / 1 TiB / 1000 : 1) is unchanged.
Why: the previous default was a UX-oriented early-fail guard, not a
security guard — `check_space()` already enforces available ≥ bytes_total
before staging begins, and `max_ratio` already rejects classic zip
bombs. A user with a multi-hundred-GiB system image shouldn't have to
click through a confirmation prompt for what's a perfectly safe archive.
The relaxed default still rejects any archive whose declared
uncompressed total exceeds the destination's free space (real check,
not a declared-vs-fs assertion) and any archive with a declared ratio
above 500 : 1 (real zip-bomb guard).
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`: 512 GiB → **1 TiB**
- `max_file_bytes`: 64 GiB → **256 GiB**
- `max_ratio`: 200 → **500**
- `assets/main.js` — `LARGE_FILE_THRESHOLD_BYTES`: 60 GiB → **240 GiB**
- `assets/lang-{en,zh}.js` — `extractLargeAsk` default-profile copy
updated to reflect the new numbers
- `README.md` — "Stricter default ZIP profile" line, the limit table
(two locations), and the `err_extract_entry_too_large` FAQ entry
bumped to the new numbers; "Tuning the threshold" snippet updated to
240 GiB
- `docs/HANDOVER.md` and `docs/UPGRADE-v1.8-rar-support.md` — the
few remaining numeric references in those docs updated
### Unchanged
- `src/rar_extract.c` — already threads `c->limits` from the engine,
picks up the new defaults for free
- `k_large_limits` — `large=1` profile (1 TiB / 1 TiB / 1000 : 1) is
unchanged
- `docs/UPGRADE-v1.7-zip-large-file-profile.md` — historical v1.7
document left as-is so the v1.7 → v1.8.1 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
- 83 host-side checks (69 ZIP + 14 RAR), 0 failures
### Migration notes
- **Forward-compatible** — users with v1.7 / v1.8 deployments who never
trigger `err_extract_entry_too_large` see no difference (defaults are
strictly more permissive)
- **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 > 240 GiB
on disk now trigger the confirmation prompt (previously 60 GiB)
---
## [v1.8] — 2026-09-05
+67 -17
View File
@@ -13,7 +13,7 @@ ifeq ($(MAKECMDGOALS),)
endif
endif
VERSION_TAG := v1.8
VERSION_TAG := v1.9
TITLE_ID := FMGR88888
PYTHON ?= python3
STRIP ?= $(PS5_PAYLOAD_SDK)/bin/prospero-strip
@@ -25,7 +25,7 @@ HOST_PKG_CONFIG ?= pkg-config
BIN := web-file-mgr.elf
LINUX_BIN := web-file-mgr-linux
COMMON_SRCS := src/main.c src/websrv.c src/filemgr.c src/file_response.c src/task.c src/upload.c src/download.c src/text.c src/list.c src/space.c src/fs_util.c src/json_util.c src/path_util.c src/asset.c src/mime.c src/notify.c src/pkg_installer.c src/pkg_info.c src/extract.c src/zip_extract.c src/rar_extract.c
PS5_SRCS := $(COMMON_SRCS) src/app_installer.c
PS5_SRCS := $(COMMON_SRCS) src/app_installer.c src/cpu_support_stub.c
LINUX_SRCS := $(COMMON_SRCS)
BASE_ASSETS := $(filter-out %.dds,$(wildcard assets/*))
ifneq ($(filter linux,$(MAKECMDGOALS)),)
@@ -35,22 +35,60 @@ ASSETS := $(filter-out assets/icon0.png,$(BASE_ASSETS))
endif
GEN_SRCS := $(patsubst assets/%,gen/%, $(ASSETS:=.c))
# Vendored third-party: zlib + minizip-ng (ZIP), dmc_unrar (RAR). Compiled with
# relaxed warnings (-w) — these are not our code and we do not want to chase
# upstream style updates on every SDK upgrade.
THIRD_PARTY_SRCS := $(wildcard third_party/zlib/src/*.c) $(wildcard third_party/minizip-ng/src/*.c) third_party/unrar/dmc_unrar.c
THIRD_PARTY_CFLAGS := -O2 -w -Ithird_party/zlib/include -Ithird_party/minizip-ng/include -Ithird_party/unrar \
-DHAVE_ZLIB -DZLIB_COMPAT -DHAVE_UNISTD_H=1 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE \
-DDMC_UNRAR_DISABLE_BE32TOH_BE64TOH=1
PS5_TP_OBJS := $(patsubst %.c,ps5-obj/%.o,$(THIRD_PARTY_SRCS))
LINUX_TP_OBJS := $(patsubst %.c,linux-obj/%.o,$(THIRD_PARTY_SRCS))
# Vendored third-party: zlib + minizip-ng (ZIP, C) and unrar 7.20.1 (RAR,
# C++). unrar sources are compiled as a static library in RARDLL mode (no
# main()); the project talks to it through the extern "C" DLL API in
# third_party/unrar7/unrar_c_api.h. Compiled with relaxed warnings (-w) —
# these are not our code and we do not want to chase upstream style updates.
#
# C++ compilers: PS5 uses prospero-clang++ (FreeBSD-style sysroot; the
# toolchain defaults to -stdlib=libc++, driver links libc++ automatically);
# host builds use the plain host C++ compiler (libstdc++).
CXX ?= $(dir $(CC))prospero-clang++
HOST_CXX ?= c++
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)\"
# Source set mirrors UnRARDll.vcxproj's ClCompile list (49 files) MINUS the
# Windows-only isnt.cpp / motw.cpp (they need windows.h; the official unrar
# UNIX makefile omits them, and PS5/linux both use the _UNIX branch where
# their symbols are #ifdef'd out).
UNRAR7_SRCS := \
third_party/unrar7/archive.cpp third_party/unrar7/arcread.cpp third_party/unrar7/blake2s.cpp \
third_party/unrar7/cmddata.cpp third_party/unrar7/consio.cpp third_party/unrar7/crc.cpp \
third_party/unrar7/crypt.cpp third_party/unrar7/dll.cpp third_party/unrar7/encname.cpp \
third_party/unrar7/errhnd.cpp third_party/unrar7/extinfo.cpp third_party/unrar7/extract.cpp \
third_party/unrar7/filcreat.cpp third_party/unrar7/file.cpp third_party/unrar7/filefn.cpp \
third_party/unrar7/filestr.cpp third_party/unrar7/find.cpp third_party/unrar7/getbits.cpp \
third_party/unrar7/global.cpp third_party/unrar7/hash.cpp third_party/unrar7/headers.cpp \
third_party/unrar7/largepage.cpp third_party/unrar7/match.cpp \
third_party/unrar7/options.cpp third_party/unrar7/pathfn.cpp \
third_party/unrar7/qopen.cpp third_party/unrar7/rar.cpp third_party/unrar7/rarpch.cpp \
third_party/unrar7/rarvm.cpp third_party/unrar7/rawread.cpp third_party/unrar7/rdwrfn.cpp \
third_party/unrar7/rijndael.cpp third_party/unrar7/rs.cpp third_party/unrar7/rs16.cpp \
third_party/unrar7/scantree.cpp third_party/unrar7/secpassword.cpp third_party/unrar7/sha1.cpp \
third_party/unrar7/sha256.cpp third_party/unrar7/smallfn.cpp third_party/unrar7/strfn.cpp \
third_party/unrar7/strlist.cpp third_party/unrar7/system.cpp third_party/unrar7/threadpool.cpp \
third_party/unrar7/timefn.cpp third_party/unrar7/ui.cpp third_party/unrar7/unicode.cpp \
third_party/unrar7/unpack.cpp third_party/unrar7/volume.cpp
THIRD_PARTY_C_SRCS := $(wildcard third_party/zlib/src/*.c) $(wildcard third_party/minizip-ng/src/*.c)
THIRD_PARTY_C_FLAGS := -O2 -w -Ithird_party/zlib/include -Ithird_party/minizip-ng/include \
-DHAVE_ZLIB -DZLIB_COMPAT -DHAVE_UNISTD_H=1 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE
UNRAR7_CXX_FLAGS := -O2 -w -std=c++17 -DRARDLL -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE
# prospero-clang++ defaults to -stdlib=libc++; state it explicitly for clarity.
UNRAR7_CXX_FLAGS_PS5 := $(UNRAR7_CXX_FLAGS) -stdlib=libc++
UNRAR7_CXX_FLAGS_HOST:= $(UNRAR7_CXX_FLAGS)
PS5_TP_OBJS := $(patsubst %.c,ps5-obj/%.o,$(THIRD_PARTY_C_SRCS)) \
$(patsubst %.cpp,ps5-obj/%.o,$(UNRAR7_SRCS))
LINUX_TP_OBJS := $(patsubst %.c,linux-obj/%.o,$(THIRD_PARTY_C_SRCS)) \
$(patsubst %.cpp,linux-obj/%.o,$(UNRAR7_SRCS))
CFLAGS := -Oz -fno-asynchronous-unwind-tables -fno-unwind-tables -Wall -Werror -ffunction-sections -fdata-sections -Isrc -Ithird_party/minizip-ng/include -Ithird_party/unrar7 -DVERSION_TAG=\"$(VERSION_TAG)\" -DTITLE_ID=\"$(TITLE_ID)\"
CFLAGS += `$(PKG_CONFIG) libmicrohttpd --cflags`
LDFLAGS := -Wl,--gc-sections
LDADD := `$(PKG_CONFIG) libmicrohttpd --libs`
LDADD += -lSceIpmi -lSceAppInstUtil -lSceUserService
LINUX_CFLAGS := -O2 -flto -Wall -Werror -Isrc -Ithird_party/minizip-ng/include -DVERSION_TAG=\"$(VERSION_TAG)\" -DTITLE_ID=\"$(TITLE_ID)\"
LINUX_CFLAGS := -O2 -flto -Wall -Werror -Isrc -Ithird_party/minizip-ng/include -Ithird_party/unrar7 -DVERSION_TAG=\"$(VERSION_TAG)\" -DTITLE_ID=\"$(TITLE_ID)\"
LINUX_CFLAGS += `$(HOST_PKG_CONFIG) libmicrohttpd --cflags`
LINUX_LDADD := `$(HOST_PKG_CONFIG) libmicrohttpd --libs` -pthread
@@ -78,16 +116,28 @@ gen/%.c: assets/% gen-asset-module.py | gen
ps5-obj/%.o: %.c
@mkdir -p $(dir $@)
$(CC) $(THIRD_PARTY_CFLAGS) -c -o $@ $<
$(CC) $(THIRD_PARTY_C_FLAGS) -c -o $@ $<
linux-obj/%.o: %.c
@mkdir -p $(dir $@)
$(HOST_CC) $(THIRD_PARTY_CFLAGS) -c -o $@ $<
$(HOST_CC) $(THIRD_PARTY_C_FLAGS) -c -o $@ $<
ps5-obj/%.o: %.cpp
@mkdir -p $(dir $@)
$(CXX) $(UNRAR7_CXX_FLAGS_PS5) -c -o $@ $<
linux-obj/%.o: %.cpp
@mkdir -p $(dir $@)
$(HOST_CXX) $(UNRAR7_CXX_FLAGS_HOST) -c -o $@ $<
# Link with the C++ driver so libc++ (PS5) / libstdc++ (host) is pulled in
# automatically for the unrar objects. The project's own C sources are passed
# through -x c (clang++ would otherwise compile .c files as C++ and trip
# -Wdeprecated); -x none restores extension-based handling for the .o files.
$(BIN): $(PS5_SRCS) $(GEN_SRCS) $(PS5_TP_OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(filter %.c,$^) $(PS5_TP_OBJS) $(LDADD)
$(CXX) $(CFLAGS) $(LDFLAGS) -o $@ -x c $(filter %.c,$^) -x none $(PS5_TP_OBJS) $(LDADD)
$(STRIP) $@
$(LINUX_BIN): $(LINUX_SRCS) $(GEN_SRCS) $(LINUX_TP_OBJS)
$(HOST_CC) $(LINUX_CFLAGS) -o $@ $(filter %.c,$^) $(LINUX_TP_OBJS) $(LINUX_LDADD)
$(HOST_CXX) $(LINUX_CFLAGS) -o $@ -x c $(filter %.c,$^) -x none $(LINUX_TP_OBJS) $(LINUX_LDADD)
$(HOST_STRIP) $@
+116 -61
View File
@@ -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.9 · **Title ID:** `FMGR88888` · **License:** GPLv3+ · **Target:** `x86_64-sie-ps5`
---
@@ -12,6 +12,22 @@ A payload ELF that runs an HTTP file manager inside a jailbroken PS5. Open `http
The same source tree builds a Linux binary for development and a PS5 payload ELF for deployment — see `make linux` below.
## What's new in v1.9
- **RAR engine replaced with the official rarlab UnRAR 7.20.1**
(`third_party/unrar7/`, replacing dmc_unrar). This is what actually
makes RAR extraction work on real files: dmc_unrar could not decode
archives written by **WinRAR 6.x/7.x** (RAR5 "v6" compression) and had
no multi-volume support — both now work.
- **RAR5 "v6" archives extract** (the v1.8-era "corrupt archive" report
on WinRAR 6/7 files is gone).
- **Multi-volume RAR** (`.part01.rar` chains): unrar stitches the parts by
name when the full set sits next to the volume you open.
- Engine can decrypt encrypted RAR (`RARSetPassword`) — password UI /
API plumbing still pending, encrypted archives are rejected for now.
- Host tests now run real archives (v6 / encrypted / 3-volume fixtures
committed under `tests/fixtures-real/`): **70 ZIP + 24 RAR = 94 checks**.
## What's new in v1.8
- **Single-volume RAR extraction** via the vendored FLOSS library
@@ -40,10 +56,52 @@ 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.
- **Stricter default ZIP profile** stays safe: **512 GiB** total / **64 GiB** per entry / **200 : 1** ratio. A 4 MiB compressed payload that expands to 800 GiB still gets rejected before any output file is opened.
- **Stricter default ZIP profile** stays safe: **1 TiB** total / **256 GiB** per entry / **500 : 1** ratio. A 4 MiB compressed payload that expands to 800 GiB still gets rejected before any output file is opened.
- **69 host-side C tests** (`tests/run-tests.sh`) now cover path traversal, ZIP64, encryption rejection, ratios, conflict policies and the new large-file profile (`tests/test_zip_extract.c`).
- Earlier refinements — see `git log` since v1.6.
@@ -68,7 +126,7 @@ The same source tree builds a Linux binary for development and a PS5 payload ELF
- **Upload** — single files or folder trees from any device on the LAN (hidden in the PS5 browser). Atomic temp + rename.
- **Download** — single file as raw bytes, or folders/multi-select as a streaming `.tar`. Hidden in the PS5 browser.
- **Tasks** — full-screen overlay with delayed show, live progress, throughput, ETA, cancel, and recovery if the browser is closed and reopened mid-task.
- **Archive extraction** — ZIP (encrypted rejected) and RAR (single-volume unencrypted); see the [ZIP extraction](#zip-extraction) and [RAR extraction](#rar-extraction) sections below for scope.
- **Archive extraction** — ZIP (encrypted rejected) and RAR (v1.9: RAR4 + RAR5 incl. WinRAR 6/7 "v6", multi-volume; encrypted still rejected pending password UI); see the [ZIP extraction](#zip-extraction) and [RAR extraction](#rar-extraction) sections below for scope.
- **PKG** — install and preview `.pkg` files.
- **Images** — preview `.png .jpg .jpeg .gif .bmp .webp`.
- **Localization** — English + Simplified Chinese, auto-selected from `navigator.languages`.
@@ -116,7 +174,7 @@ make
Output:
```text
web-file-mgr.elf (~418 KiB, x86_64-sie-ps5)
web-file-mgr.elf (~several hundred KiB, larger in v1.9 with unrar; x86_64-sie-ps5)
```
For pure UI/JS work without the PS5 toolchain:
@@ -156,13 +214,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) | 512 GiB | 2 TiB |
| `max_file_bytes` (per entry) | 64 GiB | 1 TiB |
| `max_ratio` (uncompressed / compressed) | 200 : 1 | 1000 : 1 |
| `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` (60 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,41 +242,42 @@ Passed as `conflict=` on `/api/extract`:
### Tuning the threshold
The 60 GiB frontend threshold lives in `assets/main.js`:
The 480 GiB frontend threshold lives in `assets/main.js`:
```js
const LARGE_FILE_THRESHOLD_BYTES = 60 * 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.
## RAR extraction
A single-volume, **unencrypted** RAR archive engine (`src/rar_extract.{c,h}`,
backed by the vendored FLOSS library
[`dmc_unrar`](https://github.com/DrMcCoy/dmc_unrar) at
`third_party/unrar/dmc_unrar.c`). Files with the extension `.rar` get the
same **Extract** button as `.zip` files; the engine is dispatched by
`src/extract.c` based on extension.
A RAR extraction engine (`src/rar_extract.{c,h}`) backed by the **official
rarlab UnRAR source** (`third_party/unrar7/`, version 7.20.1, compiled as a
static library and driven through its C-compatible DLL API). Files with the
extension `.rar` get the same **Extract** button as `.zip` files; the engine
is dispatched by `src/extract.c` based on extension.
> v1.9 replaced the v1.8 engine (dmc_unrar 1.7.0). dmc_unrar could not
> decode archives written by WinRAR 6.x/7.x (RAR5 "v6" compression) and had
> no multi-volume support; unrar handles both natively.
### Scope
| Format | Support | Notes |
|---|---|---|
| RAR 1.5 / 2.0 / 2.6 / 2.9 / 3.0 / 3.6 / 4.0 | ✅ | Single-volume, unencrypted |
| RAR 5.0 | ✅ | Single-volume, unencrypted |
| Solid blocks, dictionary 4 MiB (RAR4) / 32 MiB (RAR5) | ✅ | |
| RAR 1.5 → 4.x (incl. 2.9 / 3.6 / 4.0) | ✅ | |
| RAR 5.0 and **5.0 "v6"** (WinRAR 6.x / 7.x) | ✅ | The v1.9 trigger |
| Solid blocks, dictionary up to 1 GiB | ✅ | |
| PPMd decompression (RAR 3.0+) | ✅ | |
| **Multi-volume** (`.part01.rar` + `.part02.rar` + …) | ❌ | Rejected with `ZIPX_ERR_UNSUPPORTED`. The frontend greys out non-`01` sub-volumes with a tooltip. Join / unrar on a PC first. |
| **Encrypted RAR** (any encrypted header or file) | ❌ | Rejected with `ZIPX_ERR_UNSUPPORTED`. There is no `password=` field in `/api/extract`. |
| **Multi-volume** (`.part01.rar` + `.part02.rar` + …) | ✅ | unrar stitches parts by name when the whole set sits next to the volume you open. Select the first volume (`name.part1.rar` / `name.part01.rar`); non-first volumes are still greyed out in the UI with a hint. |
| **Encrypted RAR** | ⏳ | The engine can decrypt (`RARSetPassword`), but the password field / prompt is not wired into `/api/extract` yet. Encrypted archives are rejected up front with `ZIPX_ERR_UNSUPPORTED`. |
| Symbolic links / FIFOs / sockets / devices | ❌ | Rejected with `ZIPX_ERR_SPECIAL` (mirrors ZIP behaviour) |
| RAR 1.4 (very old) | ❌ | Not supported by dmc_unrar 1.7.0; rejected upstream |
| RAR 1.3 (pre-1.4) | ❌ | Rejected upstream by unrar |
The "extract on a PC first" recovery is the same escape hatch the engine
uses for ZIP encryption errors: when an unsupported archive is rejected,
the user gets an `extract_unsupported` failure with the file name as the
detail argument. The frontend already shows this with the typical
bilingual retry guidance.
When an archive is rejected, the user gets an `extract_unsupported`
failure with the file name as the detail argument. The frontend already
shows this with the typical bilingual retry guidance.
### Limits
@@ -228,14 +287,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) | 512 GiB | 2 TiB |
| `max_file_bytes` (per entry) | 64 GiB | 1 TiB |
| `max_ratio` (uncompressed / compressed) | 200 : 1 | 1000 : 1 |
| `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`
(60 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).
@@ -255,37 +314,32 @@ The RAR engine applies the same checks as the ZIP engine — re-uses
### Vendoring and licence
`third_party/unrar/dmc_unrar.c` is vendored **verbatim** from
[`DrMcCoy/dmc_unrar`](https://github.com/DrMcCoy/dmc_unrar), upstream
commit pinned at the same date as the v1.8 release. The file is
**GPL-2.0-or-later** (see `third_party/unrar/COPYING`), which means the
resulting `web-file-mgr.elf` is also effectively GPL-2.0-or-later. The
already-GPLv3+ project is forward-compatible with that, and the
compliant distribution form (binary + corresponding sources + GPL
notice alongside the LGPL notice for libmicrohttpd) is the same
process you already follow for every prior release. The minimal
project-authored facade `third_party/unrar/dmc_unrar_api.h` carries the
project's own licence and is *not* bound to GPL.
`third_party/unrar7/` is a verbatim copy of the official **rarlab UnRAR
source** (7.20.1), mirrored by
[`opello/unrar`](https://github.com/opello/unrar) at commit `97e1780`. It is
distributed under the **UnRAR freeware licence** (see
`third_party/unrar7/license.txt`): it may be used in any software to handle
RAR archives, but may not be used to develop a RAR-compatible *archiver* or
re-create the RAR compression algorithm. The project-authored facade
`third_party/unrar7/unrar_c_api.h` carries the project's own licence.
### v1.9 plan
> The v1.8 engine `third_party/unrar/dmc_unrar.c` (DrMcCoy/dmc_unrar 1.7.0,
> GPL-2.0-or-later) was removed in v1.9; its notice lives in git history.
When (if) multi-volume RAR and encrypted RAR become worth the
engineering cost, the recommended path is to replace
`third_party/unrar/dmc_unrar.c` with a vendored mirror of
[`opello/unrar`](https://github.com/opello/unrar) (a faithful copy of
rarlab UnRAR 7.x, C++17, supports volumes + encryption). The
`rar_extract()` signature, the dispatch layer, and the host tests do
**not** need to change — only the engine behind `rar_extract()` and the
`password=` field on `/api/extract`. See
[`third_party/unrar/VENDORED.md`](./third_party/unrar/VENDORED.md) for
the step-by-step upgrade recipe.
### Encrypted RAR (planned)
The engine can decrypt archives (via `RARSetPassword`), but the password
channel — a `password=` field on `/api/extract` plus a frontend prompt —
is not wired yet. Encrypted archives currently fail with
`extract_unsupported`. The engine swap (v1.9) removed the hard engine
limits; the remaining work is purely API/UI plumbing.
## Verification
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 grew in v1.9 (unrar static library); ~509 KiB was v1.8.3
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 +355,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 +374,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 +422,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 64 GiB per
entry / 200:1 ratio. Confirm the large-file prompt (appears for
archives > 60 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
+27 -23
View File
@@ -2,8 +2,9 @@ Third-Party Notices
===================
This project vendors a minimal set of third-party source files under
`third_party/` to provide the ZIP extraction feature (the `/api/extract`
endpoint). Their full license texts are included alongside the sources.
`third_party/` to provide the ZIP and RAR extraction features (the
`/api/extract` endpoint). Their full license texts are included
alongside the sources.
1. minizip-ng
-----------
@@ -21,26 +22,29 @@ endpoint). Their full license texts are included alongside the sources.
Files : third_party/zlib/** (vendored subset, compiled with relaxed
warnings into the final binary)
3. dmc_unrar
---------
Version : 1.7.0
Source : https://github.com/DrMcCoy/dmc_unrar
License : GPL-2.0-or-later (see third_party/unrar/COPYING)
Files : third_party/unrar/dmc_unrar.c (single-file library, compiled
with relaxed warnings; the small facade header
third_party/unrar/dmc_unrar_api.h is project-authored and
carries the project's license).
3. unrar (rarlab UnRAR source, v7.20.1)
-------------------------------------
Version : 7.20.1 (RAR 7.23-free source snapshot, 2025-10-28)
Source : https://www.rarlab.com/rar_add.htm — mirrored by
https://github.com/opello/unrar (commit 97e1780)
License : UnRAR freeware license (see third_party/unrar7/license.txt)
Files : third_party/unrar7/** (RARDLL source set compiled with
relaxed warnings; unrar_c_api.h is project-authored and
carries the project's license)
dmc_unrar is the engine behind `src/rar_extract.c` (the v1.8 RAR support).
It is a dependency-free, single-file, C89/C99 FLOSS UnRAR library. Because
it is licensed under the GPL-2.0-or-later, the resulting `web-file-mgr.elf`
binary inherits the obligation to ship the GPL notice and source — which is
exactly what this file plus `third_party/unrar/COPYING` provides. The
interface in `dmc_unrar_api.h` is minimal and re-declares only the entry
points the project uses.
unrar is the engine behind `src/rar_extract.c` (the v1.9 RAR support: RAR4,
RAR5 including WinRAR 6/7 "v6" compression, and multi-volume archives; the
engine can also decrypt via RARSetPassword once a password channel is wired
up). The UnRAR source may be used in any software to handle RAR archives,
but may not be used to develop a RAR-compatible *archiver* or to re-create
the RAR compression algorithm, which is proprietary.
Both libraries in section 1 and 2 are distributed under the zlib license,
which permits redistribution in source and binary form provided the
copyright notice and this list of conditions are retained. dmc_unrar is
distributed under the GNU GPL v2-or-later. See the individual LICENSE /
COPYING files in each `third_party/` subdirectory for the complete terms.
(The v1.8 engine, DrMcCoy/dmc_unrar 1.7.0 under GPL-2.0-or-later, was
replaced by the rarlab UnRAR source in v1.9; see git history under
`third_party/unrar/` for its notice.)
Libraries in sections 1 and 2 are distributed under the zlib license, which
permits redistribution in source and binary form provided the copyright
notice and this list of conditions are retained. unrar is distributed under
its own freeware terms. See the individual LICENSE / license.txt files in
each `third_party/` subdirectory for the complete terms.
+1 -1
View File
@@ -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
View File
@@ -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 64 GiB, archive total 512 GiB); 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
View File
@@ -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取消 = 默认限制(单文件 64 GiB / 总解压 512 GiB),可能拒绝此压缩包",
extractLargeAsk: "压缩包体积较大({size}),是否启用「大文件模式」?\n\n确定 = 启用(单文件最大 1 TiB / 总解压最大 4 TiB)\n取消 = 默认限制(单文件 512 GiB / 总解压 2 TiB),可能拒绝此压缩包",
extractLargeActive: "此任务已启用大文件模式",
extractSelectMainVolume: "请改选主卷(如 .rar 或 .part01.rar)",
extractArchivePending: "正在准备解压 {name}",
+9 -3
View File
@@ -35,7 +35,7 @@ let uploadXhr = null;
let uploadTerminalAbort = false;
let L = {};
const APP_VERSION = "v1.7";
const APP_VERSION = "v1.9";
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 = 60 * 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;
}
+6 -6
View File
@@ -87,7 +87,7 @@ SDK 的 `target/user/homebrew/` 被 `songl(197609)` 拥有 755,普通 song 写
```
┌────────────────────────────────────────────────────────────────┐
│ Frontend: assets/main.js │
│ - LARGE_FILE_THRESHOLD_BYTES = 60 GiB (硬编码) │
│ - LARGE_FILE_THRESHOLD_BYTES = 240 GiB (硬编码) │
│ - shouldPromptLargeMode(itemSize) → 弹 confirm │
│ - startExtractTask(path, dst, conflict, remove, name, large) │
└────────────────────┬───────────────────────────────────────────┘
@@ -103,8 +103,8 @@ SDK 的 `target/user/homebrew/` 被 `songl(197609)` 拥有 755,普通 song 写
│
┌────────────────────▼───────────────────────────────────────────┐
│ Engine: src/zip_extract.{h,c} │
│ - k_default_limits: 200K / 512GiB / 64GiB / 200:1 │
│ - k_large_limits: 500K / 2TiB / 1TiB / 1000:1 │
│ - k_default_limits: 200K / 1TiB / 256GiB / 500:1 │
│ - k_large_limits: 500K / 2TiB / 1TiB / 1000:1 │
│ - ZIPX_LIMITS_DEFAULT=0 / ZIPX_LIMITS_LARGE=1 │
│ - zipx_limits_profile(int) → const zipx_limits_t* │
│ - zipx_extract() 签名不变,向后兼容 │
@@ -121,7 +121,7 @@ SDK 的 `target/user/homebrew/` 被 `songl(197609)` 拥有 755,普通 song 写
- `assets/lang-en.js` 和 `lang-zh.js` 新增 `extractLargeAsk` / `extractLargeActive`
**前端 UX**:
- ZIP 大于 60 GiB 时弹窗「启用大文件模式?」
- ZIP 大于 480 GiB 时弹窗「启用大文件模式?」
- 用户点 OK → 传 `large=1` → 引擎走 large profile
- 用户点取消 → 走 default profile(多半会被拒绝)
@@ -1130,8 +1130,8 @@ multi-volume (`name.part01.rar`, `name.part02.rar`, …). Encrypted
archives prompt for a password client-side; the password is held
only in memory and never saved.
Limits mirror the ZIP profiles (200K entries / 512 GiB / 64 GiB /
ratio 200 by default, with the same `large=1` opt-in to 500K /
Limits mirror the ZIP profiles (200K entries / 1 TiB / 256 GiB /
ratio 500 by default, with the same `large=1` opt-in to 500K /
2 TiB / 1 TiB / 1000).
```
+4 -4
View File
@@ -416,10 +416,10 @@ self-evident from the failure.
### 7.4 The `large=1` prompt for RAR
The threshold is shared. A `.rar` larger than `60 GiB` triggers the
The threshold is shared. A `.rar` larger than `240 GiB` triggers the
same `promptLargeMode()` confirmation as a `.zip`. The confirmation
text uses `extractLargeAsk` (unchanged from v1.7) — the wording is
format-agnostic, so no new strings are needed.
text uses `extractLargeAsk` (slightly relaxed in v1.8.1) — the wording
is format-agnostic, so no new strings are needed.
---
@@ -441,7 +441,7 @@ host has any RAR tooling.
| `test_engine_dispatch_null_dst` | `rar_extract(path, NULL, …)` is rejected with `ZIPX_ERR_INTERNAL`. |
| `test_engine_dispatch_dst_is_regular_file` | `rar_extract(path, /some/file, …)` returns `ZIPX_ERR_CONFLICT` (open_parent_dirs fails). |
| `test_format_translation` | Parametric: for each `DMC_UNRAR_*` code we care about, the corresponding `rar_translate_error()` mapping is exercised indirectly (via `result->message` strings). |
| `test_limits_handoff_default` | When `task->extract_large == 0`, the default profile is handed in (200 K entries / 512 GiB / 64 GiB / 200:1). |
| `test_limits_handoff_default` | When `task->extract_large == 0`, the default profile is handed in (200 K entries / 1 TiB / 256 GiB / 500:1). |
| `test_limits_handoff_large` | When `task->extract_large == 1`, the large profile is handed in (500 K / 2 TiB / 1 TiB / 1000:1). |
| `test_translate_open_fail_to_err_open` | DMC open-failure → `ZIPX_ERR_OPEN`. |
| `test_translate_volume_unsp_to_err_unsupported` | The DMC volume code → `ZIPX_ERR_UNSUPPORTED`. |
+26
View File
@@ -0,0 +1,26 @@
/* PS5-only stub for the libgcc CPU model symbols.
*
* unrar's rijndael.cpp / system.cpp call __builtin_cpu_supports() to pick
* AES-NI fast paths. Clang lowers that to a reference on __cpu_model (data)
* and __cpu_indicator_init() (function), which the FreeBSD-style PS5
* sysroot does not provide (no libgcc). This TU supplies both so the link
* succeeds; the detection result is unused because we always build the
* portable C path.
*
* Do NOT add this file to host/linux builds: libstdc++/libgcc already
* define __cpu_model there and the symbols would collide. */
#if defined(__x86_64__) && !defined(__linux__) && !defined(_WIN32)
struct __cpu_model {
int __cpu_vendor;
int __cpu_type;
int __cpu_subtype;
};
int __cpu_indicator_init(void) {
return 0;
}
struct __cpu_model __cpu_model = { 0, 0, 0 };
#endif
+22 -20
View File
@@ -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) {
+197 -360
View File
@@ -1,18 +1,26 @@
/* Safe RAR extraction engine used by the /api/extract task.
Wraps the vendored dmc_unrar library (https://github.com/DrMcCoy/dmc_unrar).
Wraps the vendored rarlab UnRAR 7.x library (third_party/unrar7) through
its C-compatible DLL API (dll.hpp / unrar_c_api.h facade).
The publish / staging / rollback / normalize-name / dedup machinery is
mirrored from zip_extract.c so that any consumer of zipx_result_t gets a
consistent error and progress contract regardless of archive format.
See third_party/unrar/VENDORED.md for what dmc_unrar does and does not
support (single-volume RAR only; multi-volume and encrypted are
rejected up front). */
Backend notes (v1.9, unrar 7.20.1):
* RAR4 and RAR5 (any compression version, incl. WinRAR 6/7 "v6")
single-volume archives.
* Multi-volume archives: unrar auto-merges subsequent volumes by name
pattern when all .partNN.rar files sit next to the opened volume.
* Encrypted RAR: the engine can decrypt via RARSetPassword, but the
password plumbing (API + UI) is not wired yet — encrypted archives
currently fail with ZIPX_ERR_UNSUPPORTED.
See third_party/unrar7/VENDORED.md for the full integration notes. */
#include "rar_extract.h"
#include "zip_extract.h" /* for the shared status / progress / limits API */
#include "dmc_unrar_api.h" /* facade — links against dmc_unrar.o at build time */
#include "unrar_c_api.h" /* facade — links against the unrar7 static lib at build time */
#include <dirent.h>
#include <errno.h>
@@ -28,11 +36,11 @@
#include <time.h>
#include <unistd.h>
/* (intentionally no `#include "dmc_unrar.c"` here — that would pull the
library into this translation unit, where the host test build's
tests/posix_compat.h renames `open` / `close` to `wfm_open` / `wfm_close`
and breaks dmc_unrar's `dmc_unrar_io_handler` struct member access.
dmc_unrar.c is compiled as its own translation unit and joined at link.) */
/* (intentionally no `#include "rar.hpp"` etc. here — those are C++ headers.
rar_extract.c talks to unrar exclusively through the extern "C" DLL API in
dll.hpp, and the unrar sources are compiled as their own translation units
(RARDLL mode) and joined at link time. This keeps the host test build's
tests/posix_compat.h renames (open->wfm_open) from leaking into unrar.) */
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
@@ -288,7 +296,7 @@ nameset_put(rarx_nameset_t *set, uint64_t h, int is_dir) {
/**************************************************************************
* entry name validation (mirrors zip_extract.c::normalize_name but the
* RAR side does not have a trailing separator, so dir entries are only
* detected via dmc_unrar_file_is_directory()).
* detected via the RHDF_DIRECTORY header flag).
**************************************************************************/
static int
@@ -300,7 +308,11 @@ normalize_name(const char *in, char *out, size_t out_size, int *is_dir,
size_t seg_len = 0;
uint32_t levels = 0;
*is_dir = 0;
/* NOTE: is_dir is owned by the caller — unrar announces directories via
the RHDF_DIRECTORY header flag, not via a trailing separator, so we
must NOT clear it here (a stray `*is_dir = 0` previously turned every
directory entry into a file and tripped the duplicate detector when an
explicit directory header followed files beneath it). */
*depth = 0;
if(!in_len) {
return rarx_fail(c, ZIPX_ERR_UNSAFE_NAME, in, "empty entry name");
@@ -422,77 +434,46 @@ nameset_check_duplicate(rarx_nameset_t *set, const char *name, int is_dir,
}
/**************************************************************************
* dmc_unrar -> zipx error translation
* unrar DLL error -> zipx error translation
**************************************************************************/
static int
rar_translate_error(dmc_unrar_return code, const char *detail,
rarx_ctx_t *c) {
rar_translate_error(int code, const char *detail, rarx_ctx_t *c) {
switch(code) {
case DMC_UNRAR_OK:
case ERAR_SUCCESS:
return ZIPX_OK;
/* Archive-open level (the user will see this when they upload a
multi-volume or fully-encrypted RAR — let them know it is on purpose). */
case DMC_UNRAR_ARCHIVE_UNSUPPORTED_VOLUMES:
return rarx_fail(c, ZIPX_ERR_UNSUPPORTED, detail,
"multi-volume RAR archives are not supported; "
"please extract on a PC first");
/* The unrar engine handles multi-volume automatically (it merges the
next .partNN.rar by name); a missing next volume surfaces as EOPEN. */
case ERAR_EOPEN:
return rarx_fail(c, ZIPX_ERR_OPEN, detail, "%s", strerror(errno));
case DMC_UNRAR_ARCHIVE_UNSUPPORTED_ENCRYPTED:
return rarx_fail(c, ZIPX_ERR_UNSUPPORTED, detail,
"encrypted RAR archives are not supported; "
"please extract on a PC first");
case ERAR_ECREATE:
case ERAR_ECLOSE:
case ERAR_EREAD:
case ERAR_EWRITE:
return rarx_fail(c, ZIPX_ERR_IO, detail, "%s", strerror(errno));
/* Per-file (still surfaced up-front during scan). */
case DMC_UNRAR_FILE_UNSUPPORTED_ENCRYPTED:
case ERAR_MISSING_PASSWORD:
case ERAR_BAD_PASSWORD:
/* Password plumbing (API + UI) is not wired yet. */
return rarx_fail(c, ZIPX_ERR_UNSUPPORTED, detail,
"encrypted RAR entries are not supported");
case DMC_UNRAR_FILE_UNSUPPORTED_SPLIT:
return rarx_fail(c, ZIPX_ERR_UNSUPPORTED, detail,
"split RAR entries are not supported");
case ERAR_SMALL_BUF:
return rarx_fail(c, ZIPX_ERR_LIMIT_NAME, detail, "name buffer is too small");
case DMC_UNRAR_FILE_UNSUPPORTED_LINK:
return rarx_fail(c, ZIPX_ERR_SPECIAL, detail, "symbolic link entry");
case DMC_UNRAR_FILE_UNSUPPORTED_VERSION:
case DMC_UNRAR_FILE_UNSUPPORTED_METHOD:
return rarx_fail(c, ZIPX_ERR_UNSUPPORTED, detail,
"RAR entry uses an unsupported compression method");
case DMC_UNRAR_FILE_UNSUPPORTED_LARGE:
case ERAR_LARGE_DICT:
return rarx_fail(c, ZIPX_ERR_LIMIT_FILE, detail,
"RAR entry is larger than the supported maximum");
"archive needs a larger dictionary than supported");
case DMC_UNRAR_ARCHIVE_EMPTY:
return rarx_fail(c, ZIPX_ERR_FORMAT, detail, "empty RAR archive");
case ERAR_BAD_DATA:
return rarx_fail(c, ZIPX_ERR_CRC, detail, "checksum mismatch in entry data");
case DMC_UNRAR_ARCHIVE_UNSUPPORTED_ANCIENT:
return rarx_fail(c, ZIPX_ERR_UNSUPPORTED, detail,
"RAR 1.4 / 1.5 archives are not supported");
case DMC_UNRAR_ARCHIVE_NOT_RAR:
return rarx_fail(c, ZIPX_ERR_FORMAT, detail,
"not a RAR archive");
case DMC_UNRAR_OPEN_FAIL:
return rarx_fail(c, ZIPX_ERR_OPEN, detail, "%s", strerror(errno));
case DMC_UNRAR_READ_FAIL:
case DMC_UNRAR_WRITE_FAIL:
case DMC_UNRAR_SEEK_FAIL:
return rarx_fail(c, ZIPX_ERR_IO, detail, "%s", strerror(errno));
case DMC_UNRAR_FILE_CRC32_FAIL:
return rarx_fail(c, ZIPX_ERR_CRC, detail, "CRC32 mismatch");
case DMC_UNRAR_INVALID_DATA:
case DMC_UNRAR_NO_ALLOC:
case DMC_UNRAR_ALLOC_FAIL:
case DMC_UNRAR_ARCHIVE_IS_NULL:
case DMC_UNRAR_ARCHIVE_NOT_CLEARED:
case DMC_UNRAR_ARCHIVE_MISSING_FIELDS:
case ERAR_BAD_ARCHIVE:
case ERAR_UNKNOWN_FORMAT:
case ERAR_UNKNOWN:
case ERAR_NO_MEMORY:
default:
return rarx_fail(c, ZIPX_ERR_FORMAT, detail,
"invalid or corrupt RAR archive");
@@ -532,15 +513,6 @@ path_parent(const char *path, char *out, size_t out_size) {
return 0;
}
static void
chmod_0777_fd(int fd) {
int err = errno;
if(fchmod(fd, 0777)) {
errno = err;
}
}
static int
make_staging(rarx_ctx_t *c, const char *parent) {
unsigned int attempt;
@@ -617,57 +589,6 @@ rollback_published(rarx_ctx_t *c) {
}
}
/* mkdir -p: ensures every segment below root_fd exists. Returns the open
descriptor for the deepest directory. */
static int
open_parent_dirs(int root_fd, const char *rel, rarx_ctx_t *c) {
char buf[ZIPX_PATH_MAX];
int fd = root_fd;
char *seg;
char *save = NULL;
if(strlen(rel) >= sizeof(buf)) {
rarx_fail(c, ZIPX_ERR_LIMIT_NAME, rel, "path is too long");
return -1;
}
strcpy(buf, rel);
for(seg = strtok_r(buf, "/", &save); seg; seg = strtok_r(NULL, "/", &save)) {
int next;
if(!mkdirat(fd, seg, 0777)) {
next = openat(fd, seg, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if(next >= 0) {
chmod_0777_fd(next);
c->dirs_created++;
}
} else if(errno == EEXIST) {
next = openat(fd, seg, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
} else {
rarx_fail(c, ZIPX_ERR_IO, rel, "cannot create directory '%s': %s", seg,
strerror(errno));
if(fd != root_fd) {
close(fd);
}
return -1;
}
if(next < 0) {
rarx_fail(c, ZIPX_ERR_IO, rel, "cannot open directory '%s': %s", seg,
strerror(errno));
if(fd != root_fd) {
close(fd);
}
return -1;
}
if(fd != root_fd) {
close(fd);
}
fd = next;
}
return fd;
}
/**************************************************************************
* scan phase
**************************************************************************/
@@ -695,74 +616,70 @@ check_space(rarx_ctx_t *c, const char *target) {
}
static int
scan_archive(dmc_unrar_archive *rar, rarx_ctx_t *c) {
scan_archive(HANDLE hArc, rarx_ctx_t *c) {
rarx_nameset_t set;
dmc_unrar_size_t i;
dmc_unrar_size_t total;
int ret = 0;
if(nameset_init(&set, 4096)) {
return rarx_fail(c, ZIPX_ERR_INTERNAL, NULL, "out of memory");
}
total = dmc_unrar_get_file_count(rar);
for(i = 0; i < total; i++) {
for(;;) {
struct RARHeaderDataEx hdr;
uint64_t uncomp;
char name[ZIPX_PATH_MAX];
char *name_buf;
dmc_unrar_size_t name_size;
int is_dir;
uint32_t depth = 0;
const dmc_unrar_file *info;
dmc_unrar_return supported;
int rc;
info = dmc_unrar_get_file_stat(rar, i);
if(!info) {
ret = rarx_fail(c, ZIPX_ERR_FORMAT, NULL, "cannot read entry header");
memset(&hdr, 0, sizeof(hdr));
rc = RARReadHeaderEx(hArc, &hdr);
if(rc == ERAR_END_ARCHIVE) {
break;
}
supported = dmc_unrar_file_is_supported(rar, i);
if(supported != DMC_UNRAR_OK) {
/* is_supported() already returns ZIPX-mapped error via a temporary */
char detail[64];
snprintf(detail, sizeof(detail), "entry %llu",
(unsigned long long)i);
rarx_fail(c, ZIPX_ERR_UNSUPPORTED, detail, "%s",
dmc_unrar_strerror(supported));
ret = (int)c->result->status;
break;
}
is_dir = dmc_unrar_file_is_directory(rar, i) ? 1 : 0;
/* First call to learn the required size, second to fill the buffer. */
name_size = dmc_unrar_get_filename(rar, i, NULL, 0);
if(!name_size) {
ret = rarx_fail(c, ZIPX_ERR_FORMAT, NULL,
"cannot read entry name");
break;
}
name_buf = malloc(name_size);
if(!name_buf) {
ret = rarx_fail(c, ZIPX_ERR_INTERNAL, NULL, "out of memory");
break;
}
if(dmc_unrar_get_filename(rar, i, name_buf, name_size) != name_size) {
free(name_buf);
ret = rarx_fail(c, ZIPX_ERR_FORMAT, NULL,
"truncated entry name");
break;
}
/* dmc_unrar does not guarantee NUL termination; force it. */
name_buf[name_size - 1] = 0;
/* RAR stores UTF-8 encoded names. Replace any non-UTF-8 sequence with
'?' to keep the downstream string well-formed. */
dmc_unrar_unicode_make_valid_utf8(name_buf);
if(normalize_name(name_buf, name, sizeof(name), &is_dir, &depth, c)) {
free(name_buf);
if(rc != ERAR_SUCCESS) {
rar_translate_error(rc, NULL, c);
ret = -1;
break;
}
free(name_buf);
/* unrar hands out the header name as a NUL-terminated string
(UTF-8 on the PS5 / POSIX build). */
if(hdr.FileName[0] == 0) {
ret = rarx_fail(c, ZIPX_ERR_FORMAT, NULL, "empty entry name");
break;
}
is_dir = (hdr.Flags & RHDF_DIRECTORY) ? 1 : 0;
/* Encrypted entries: the unrar engine can decrypt them via RARSetPassword,
but the password plumbing is not wired yet — reject up front with the
same message the v1.8 backend used. */
if(hdr.Flags & RHDF_ENCRYPTED) {
ret = rarx_fail(c, ZIPX_ERR_UNSUPPORTED, hdr.FileName,
"encrypted RAR entries are not supported");
break;
}
/* Multi-volume: a file spanning volumes is presented as several header
segments with the SAME name. Segments after the first carry
RHDF_SPLITBEFORE; they must drive the engine forward (SKIP) but must
not be counted, deduped or size-accumulated again. The first segment
already carries the full file size. */
if(hdr.Flags & RHDF_SPLITBEFORE) {
rc = RARProcessFile(hArc, RAR_SKIP, NULL, NULL);
if(rc != ERAR_SUCCESS && rc != ERAR_END_ARCHIVE) {
ret = -1;
rar_translate_error(rc, hdr.FileName, c);
break;
}
continue;
}
if(normalize_name(hdr.FileName, name, sizeof(name), &is_dir, &depth, c)) {
ret = -1;
break;
}
(void)depth;
if(nameset_check_duplicate(&set, name, is_dir, c) ||
nameset_add_path(&set, name, is_dir, c)) {
@@ -771,7 +688,7 @@ scan_archive(dmc_unrar_archive *rar, rarx_ctx_t *c) {
}
if(!is_dir) {
uint64_t uncomp = info->uncompressed_size;
uncomp = (uint64_t)hdr.UnpSizeHigh << 32 | hdr.UnpSize;
if(uncomp > c->limits.max_file_bytes) {
ret = rarx_fail(c, ZIPX_ERR_LIMIT_FILE, name,
@@ -779,8 +696,6 @@ scan_archive(dmc_unrar_archive *rar, rarx_ctx_t *c) {
(unsigned long long)c->limits.max_file_bytes);
break;
}
/* RAR headers do not reliably expose compressed_size for all formats,
so the safe compression-ratio check is skipped. */
if(uncomp >= c->limits.max_total_bytes ||
c->bytes_total > c->limits.max_total_bytes - uncomp) {
ret = rarx_fail(c, ZIPX_ERR_LIMIT_TOTAL, name,
@@ -805,6 +720,17 @@ scan_archive(dmc_unrar_archive *rar, rarx_ctx_t *c) {
}
report(c, ZIPX_PHASE_SCAN, name, 0);
}
/* Advance to the next file header (also drives multi-volume merges). */
rc = RARProcessFile(hArc, RAR_SKIP, NULL, NULL);
if(rc != ERAR_SUCCESS) {
if(rc != ERAR_END_ARCHIVE) {
ret = -1;
rar_translate_error(rc, name, c);
break;
}
break;
}
}
nameset_free(&set);
@@ -815,175 +741,72 @@ scan_archive(dmc_unrar_archive *rar, rarx_ctx_t *c) {
* extract phase
**************************************************************************/
/* dmc_unrar_extract_file_to_path() opens the destination file with fopen()
without creating parent directories. We always call it against an
already-prepared full path inside the staging tree. */
/* unrar extracts each entry directly under the staging root and creates
parent directories itself. All entry names were validated (normalize_name)
during scan, so what lands in the staging tree is safe by construction. */
static int
extract_one(dmc_unrar_archive *rar, rarx_ctx_t *c, int root_fd,
const char *name, dmc_unrar_size_t index, uint64_t declared) {
char *full_path;
char dir_part[ZIPX_PATH_MAX];
char base[ZIPX_PATH_MAX];
char *slash;
int dir_fd;
dmc_unrar_return dr;
struct stat st;
uint64_t before_bytes;
extract_archive(HANDLE hArc, rarx_ctx_t *c) {
int ret = 0;
snprintf(dir_part, sizeof(dir_part), "%s", name);
slash = strrchr(dir_part, '/');
if(slash) {
snprintf(base, sizeof(base), "%s", slash + 1);
*slash = 0;
} else {
snprintf(base, sizeof(base), "%s", name);
dir_part[0] = 0;
}
if(!base[0]) {
return rarx_fail(c, ZIPX_ERR_UNSAFE_NAME, name, "empty file name");
}
dir_fd = open_parent_dirs(root_fd, dir_part, c);
if(dir_fd < 0) {
return -1;
}
full_path = malloc(ZIPX_PATH_MAX);
if(!full_path) {
rarx_fail(c, ZIPX_ERR_INTERNAL, name, "out of memory");
if(dir_fd != root_fd) {
close(dir_fd);
}
return -1;
}
if(dir_part[0]) {
snprintf(full_path, ZIPX_PATH_MAX, "%s/%s/%s",
c->staging, dir_part, base);
} else {
snprintf(full_path, ZIPX_PATH_MAX, "%s/%s", c->staging, base);
}
before_bytes = c->bytes_done;
dr = dmc_unrar_extract_file_to_path(rar, index, full_path, NULL, true);
if(dr != DMC_UNRAR_OK) {
rar_translate_error(dr, name, c);
ret = -1;
goto done;
}
if(stat(full_path, &st)) {
rarx_fail(c, ZIPX_ERR_IO, name, "cannot stat extracted file: %s",
strerror(errno));
ret = -1;
goto done;
}
if((uint64_t)st.st_size > declared + declared + (64 * 1024)) {
/* Defensive: the on-disk size should never wildly exceed the declared
size. The cap is generous so PPMd + headers do not trip it. */
rarx_fail(c, ZIPX_ERR_LIMIT_FILE, name,
"extracted file is larger than declared");
unlink(full_path);
ret = -1;
goto done;
}
if((uint64_t)st.st_size > c->limits.max_total_bytes - c->bytes_done) {
rarx_fail(c, ZIPX_ERR_LIMIT_TOTAL, name,
"archive contents are larger than %llu bytes",
(unsigned long long)c->limits.max_total_bytes);
unlink(full_path);
ret = -1;
goto done;
}
c->bytes_done += (uint64_t)st.st_size;
/* If dmc_unrar did not advance bytes_done above (e.g. declared == 0 for a
directory or zero-byte file), credit the bytes_total estimate so the
progress bar keeps moving. */
if(c->bytes_done == before_bytes && declared) {
c->bytes_done += declared;
}
done:
free(full_path);
if(dir_fd != root_fd) {
close(dir_fd);
}
if(!ret) {
c->files_created++;
}
return ret;
}
static int
extract_archive(dmc_unrar_archive *rar, rarx_ctx_t *c, int root_fd) {
dmc_unrar_size_t i;
dmc_unrar_size_t total = dmc_unrar_get_file_count(rar);
int ret = 0;
for(i = 0; i < total && !ret; i++) {
char *name_buf = NULL;
dmc_unrar_size_t name_size;
char name[ZIPX_PATH_MAX];
for(;;) {
struct RARHeaderDataEx hdr;
int rc;
int is_dir;
uint32_t depth = 0;
const dmc_unrar_file *info;
memset(&hdr, 0, sizeof(hdr));
rc = RARReadHeaderEx(hArc, &hdr);
if(rc == ERAR_END_ARCHIVE) {
break;
}
if(rc != ERAR_SUCCESS) {
rar_translate_error(rc, NULL, c);
ret = -1;
break;
}
if(canceled(c)) {
ret = rarx_fail(c, ZIPX_ERR_CANCELED, NULL, NULL);
break;
}
if(hdr.FileName[0] == 0) {
ret = rarx_fail(c, ZIPX_ERR_FORMAT, NULL, "empty entry name");
break;
}
if(hdr.Flags & RHDF_ENCRYPTED) {
/* scan already rejected these; defensive only. */
ret = rarx_fail(c, ZIPX_ERR_UNSUPPORTED, hdr.FileName,
"encrypted RAR entries are not supported");
break;
}
info = dmc_unrar_get_file_stat(rar, i);
if(!info) {
ret = rarx_fail(c, ZIPX_ERR_FORMAT, NULL, "cannot read entry header");
break;
is_dir = (hdr.Flags & RHDF_DIRECTORY) ? 1 : 0;
/* Split continuation segments still need RARProcessFile(EXTRACT) so the
volume chain is driven and the file is completed, but only the first
segment is counted / reported / size-accumulated. */
if(hdr.Flags & RHDF_SPLITBEFORE) {
rc = RARProcessFile(hArc, RAR_EXTRACT, c->staging, NULL);
if(rc != ERAR_SUCCESS) {
rar_translate_error(rc, hdr.FileName, c);
ret = -1;
break;
}
continue;
}
is_dir = dmc_unrar_file_is_directory(rar, i) ? 1 : 0;
name_size = dmc_unrar_get_filename(rar, i, NULL, 0);
if(!name_size) {
ret = rarx_fail(c, ZIPX_ERR_FORMAT, NULL, "cannot read entry name");
break;
}
name_buf = malloc(name_size);
if(!name_buf) {
ret = rarx_fail(c, ZIPX_ERR_INTERNAL, NULL, "out of memory");
break;
}
if(dmc_unrar_get_filename(rar, i, name_buf, name_size) != name_size) {
free(name_buf);
ret = rarx_fail(c, ZIPX_ERR_FORMAT, NULL, "truncated entry name");
break;
}
name_buf[name_size - 1] = 0;
dmc_unrar_unicode_make_valid_utf8(name_buf);
if(normalize_name(name_buf, name, sizeof(name), &is_dir, &depth, c)) {
free(name_buf);
rc = RARProcessFile(hArc, RAR_EXTRACT, c->staging, NULL);
if(rc != ERAR_SUCCESS) {
rar_translate_error(rc, hdr.FileName, c);
ret = -1;
break;
}
free(name_buf);
(void)depth;
if(is_dir) {
int dir_fd = open_parent_dirs(root_fd, name, c);
if(dir_fd < 0) {
ret = -1;
break;
}
if(dir_fd != root_fd) {
close(dir_fd);
}
c->dirs_created++;
} else {
uint64_t declared = info->uncompressed_size;
if(extract_one(rar, c, root_fd, name, i, declared)) {
ret = -1;
break;
}
c->files_created++;
c->bytes_done += (uint64_t)hdr.UnpSizeHigh << 32 | hdr.UnpSize;
}
c->entries_done++;
report(c, ZIPX_PHASE_EXTRACT, name, 0);
report(c, ZIPX_PHASE_EXTRACT, hdr.FileName, 0);
}
return ret;
}
@@ -1151,9 +974,7 @@ rar_extract(const char *rar_path, const char *dst_dir,
char parent[ZIPX_PATH_MAX];
char dst_copy[ZIPX_PATH_MAX];
struct stat st;
dmc_unrar_archive rar;
dmc_unrar_return dr;
int root_fd = -1;
HANDLE hArc = NULL;
int dst_existed = 0;
int status;
@@ -1197,50 +1018,67 @@ rar_extract(const char *rar_path, const char *dst_dir,
goto done;
}
dr = dmc_unrar_archive_init(&rar);
if(dr != DMC_UNRAR_OK) {
status = rarx_fail(c, ZIPX_ERR_INTERNAL, rar_path,
"cannot initialize RAR decoder");
goto done;
}
dr = dmc_unrar_archive_open_path(&rar, rar_path);
if(dr != DMC_UNRAR_OK) {
status = rar_translate_error(dr, rar_path, c);
goto done;
/* Open with RAR_OM_EXTRACT for both passes: the unrar engine drives
multi-volume merges identically while scanning (skip) and extracting,
and encrypted headers surface here as a missing/bad password. */
{
struct RAROpenArchiveDataEx od;
memset(&od, 0, sizeof(od));
od.ArcName = (char *)rar_path;
od.OpenMode = RAR_OM_EXTRACT;
hArc = RAROpenArchiveEx(&od);
if(!hArc) {
status = rar_translate_error((int)od.OpenResult, rar_path, c);
goto done;
}
}
if(scan_archive(&rar, c)) {
if(scan_archive(hArc, c)) {
status = (int)c->result->status;
RARCloseArchive(hArc);
hArc = NULL;
goto done;
}
if(canceled(c)) {
status = rarx_fail(c, ZIPX_ERR_CANCELED, NULL, NULL);
RARCloseArchive(hArc);
hArc = NULL;
goto done;
}
if(check_space(c, dst_existed ? dst_copy : parent)) {
status = (int)c->result->status;
RARCloseArchive(hArc);
hArc = NULL;
goto done;
}
if(make_staging(c, parent)) {
status = (int)c->result->status;
RARCloseArchive(hArc);
hArc = NULL;
goto done;
}
root_fd = open(c->staging, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if(root_fd < 0) {
status = rarx_fail(c, ZIPX_ERR_IO, c->staging, "cannot open staging: %s",
strerror(errno));
goto done;
/* Pass 2: re-open and extract each entry straight into the staging tree
(unrar creates parent directories itself; names were validated during
scan). The staging root is pre-verified below for fast failure. */
RARCloseArchive(hArc);
hArc = NULL;
{
struct RAROpenArchiveDataEx od;
memset(&od, 0, sizeof(od));
od.ArcName = (char *)rar_path;
od.OpenMode = RAR_OM_EXTRACT;
hArc = RAROpenArchiveEx(&od);
if(!hArc) {
status = rar_translate_error((int)od.OpenResult, rar_path, c);
goto done;
}
}
if(extract_archive(&rar, c, root_fd)) {
if(extract_archive(hArc, c)) {
status = (int)c->result->status;
goto done;
}
if(root_fd >= 0) {
close(root_fd);
root_fd = -1;
}
if(canceled(c)) {
status = rarx_fail(c, ZIPX_ERR_CANCELED, NULL, NULL);
goto done;
@@ -1254,11 +1092,10 @@ rar_extract(const char *rar_path, const char *dst_dir,
status = ZIPX_OK;
done:
if(root_fd >= 0) {
close(root_fd);
if(hArc) {
RARCloseArchive(hArc);
}
cleanup_staging(c);
dmc_unrar_archive_close(&rar);
result->entries_total = c->entries_total;
result->entries_done = c->entries_done;
result->bytes_total = c->bytes_total;
+9 -8
View File
@@ -1,21 +1,22 @@
#pragma once
/* Safe RAR extraction engine used by the /api/extract task.
Wraps the vendored dmc_unrar library (https://github.com/DrMcCoy/dmc_unrar).
Wraps the vendored rarlab UnRAR 7.20.1 (third_party/unrar7) through its
C-compatible DLL API (unrar_c_api.h facade).
Reuses the zip_extract types so the dispatch layer can call either engine
through the same status / limits / progress protocol.
See zip_extract.h for the shared limits, conflict, progress and result types.
Constraints of v1.8 (dmc_unrar 1.7.0 backend):
* Single-volume RAR archives only. Multi-volume (.partNN.rar) archives
are rejected with ZIPX_ERR_UNSUPPORTED — extract them on a PC first.
* Unencrypted RAR only. Encrypted headers / files are rejected with
ZIPX_ERR_UNSUPPORTED. There is no password argument for the same reason.
Backend notes (v1.9, unrar 7.20.1):
* RAR4 and RAR5, any compression version including WinRAR 6/7 "v6".
* Multi-volume: unrar merges next .partNN.rar by name automatically.
* Encrypted RAR is NOT yet supported end-to-end: the engine can decrypt
via RARSetPassword, but password plumbing (API + UI) is unwired, so
encrypted headers/entries fail with ZIPX_ERR_UNSUPPORTED today.
See third_party/unrar/VENDORED.md for the upgrade path to rarlab UnRAR
(which does support both) when / if it becomes worth the C++ integration. */
See third_party/unrar7/VENDORED.md for full integration notes. */
#include "zip_extract.h"
+25 -7
View File
@@ -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 = 512ULL * 1024 * 1024 * 1024,
.max_file_bytes = 64ULL * 1024 * 1024 * 1024,
.max_ratio = 200,
.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,
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+82
View File
@@ -0,0 +1,82 @@
@echo off
REM Generate real RAR fixtures for the v1.9 host test suite.
REM Requires WinRAR command-line Rar.exe (ships with normal WinRAR install).
REM Outputs into tests\fixtures-real\ - kept OUT of tests\fixtures\ because
REM tests\make_fixtures.py wipes that directory on every test run.
REM Rerun any time fixture needs change. NOTE: keep this file pure ASCII.
setlocal EnableDelayedExpansion
set RAREXE=
if exist "%ProgramFiles%\WinRAR\Rar.exe" set RAREXE=%ProgramFiles%\WinRAR\Rar.exe
if exist "%ProgramFiles(x86)%\WinRAR\Rar.exe" set RAREXE=%ProgramFiles(x86)%\WinRAR\Rar.exe
if exist "%~dp0Rar.exe" set RAREXE=%~dp0Rar.exe
if "%RAREXE%"=="" (
echo [ERROR] Rar.exe not found. Install WinRAR or drop Rar.exe next to this script.
exit /b 1
)
echo Using: %RAREXE%
"%RAREXE%" 2>nul | findstr /c:"RAR " >nul || (echo [ERROR] %RAREXE% does not look like WinRAR & exit /b 1)
set FIX=%~dp0fixtures-real
if exist "%FIX%" rmdir /s /q "%FIX%"
mkdir "%FIX%"
set STAGE=%~dp0fixture-stage
if exist "%STAGE%" rmdir /s /q "%STAGE%"
mkdir "%STAGE%\dir" 2>nul
echo ############### > "%STAGE%\root.txt"
echo rar v1.9 fixture root content >> "%STAGE%\root.txt"
echo nested payload line one > "%STAGE%\dir\nested.txt"
echo nested payload line two >> "%STAGE%\dir\nested.txt"
echo file-b 0102030405060708090a > "%STAGE%\b.bin"
REM Work from inside the stage dir with RELATIVE names so the archive keeps
REM the dir\ structure (no -ep1 stripping).
pushd "%STAGE%"
echo.
echo [1/4] RAR5 v6 single volume - basic-v6.rar
"%RAREXE%" a -m2 -ma5 -idq "%FIX%\basic-v6.rar" root.txt dir\nested.txt b.bin
if errorlevel 1 echo [FAIL] & goto :badpop
echo [2/4] RAR5 v6 multi-volume - vol.part1.rar + vol.part2.rar + ...
REM Create a 512 KB incompressible payload so -v200k actually splits it.
fsutil file createnew big.bin 524288 >nul 2>nul
if not exist big.bin (
echo [WARN] fsutil unavailable - cannot force multi-volume; skipping
goto :aftervol
)
REM rar a updates an existing archive instead of re-splitting it, so any
REM stale volume files must be removed first. Target name is plain
REM "vol.rar": RAR5 numbering then yields vol.part1/2/3.rar (naming the
REM target vol.part1.rar would double up to vol.part1.partN.rar).
del "%FIX%\vol*.rar" 2>nul
"%RAREXE%" a -m0 -ma5 -v200k -idq "%FIX%\vol.rar" big.bin root.txt
if errorlevel 1 echo [FAIL] & goto :badpop
echo wrote: & dir /b "%FIX%\vol.part*.rar" 2>nul
:aftervol
echo [3/4] RAR5 v6 encrypted (password: secret123) - enc-v6.rar
"%RAREXE%" a -m2 -ma5 -psecret123 -idq "%FIX%\enc-v6.rar" root.txt dir\nested.txt
if errorlevel 1 echo [FAIL] & goto :badpop
echo [4/4] RAR4 legacy (optional - some WinRAR builds dropped RAR4 writing)
"%RAREXE%" a -m2 -ma4 -idq "%FIX%\basic-rar4.rar" root.txt dir\nested.txt
if errorlevel 1 (
echo [WARN] RAR4 creation not supported by this Rar.exe - skipping basic-rar4.rar
) else (
echo basic-rar4.rar written
)
popd
rmdir /s /q "%STAGE%" 2>nul
echo.
echo OK. Fixtures written to %FIX%:
dir /b "%FIX%\*.rar" 2>nul
exit /b 0
:badpop
popd
:bad
echo [ERROR] WinRAR command failed. Is this WinRAR 5+ with command line support?
exit /b 1
+29 -9
View File
@@ -39,9 +39,19 @@ for src in "$ROOT"/third_party/zlib/src/*.c "$ROOT"/third_party/minizip-ng/src/*
"${MZ_CFLAGS[@]}" "${extra[@]}" -o "$BUILD/$name.o" "$src"
done
# dmc_unrar (single-file; uses stdio fopen by default on non-Windows)
"$CC" -c -O2 -w "${RAR_CFLAGS[@]}" \
-o "$BUILD/dmc_unrar.o" "$ROOT/third_party/unrar/dmc_unrar.c"
# unrar 7.20.1 (RARDLL source set; compiled with the host C++ compiler).
UNRAR7_SRCS="$ROOT/third_party/unrar7"
UNRAR7_CFLAGS=(-O2 -w -std=c++17 -DRARDLL -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE)
CXX="${CXX:-g++}"
for src in \
archive arcread blake2s cmddata consio crc crypt dll encname errhnd extinfo \
extract filcreat file filefn filestr find getbits global hash headers isnt \
largepage match motw options pathfn qopen rar rarpch rarvm rawread rdwrfn \
rijndael rs rs16 scantree secpassword sha1 sha256 smallfn strfn strlist \
system threadpool timefn ui unicode unpack volume; do
"$CXX" -c "${UNRAR7_CFLAGS[@]}" -o "$BUILD/unrar7_$src.o" \
"$UNRAR7_SRCS/$src.cpp" || exit 1
done
COMPAT_INC="$ROOT/tests/compat"
@@ -52,8 +62,8 @@ COMPAT_INC="$ROOT/tests/compat"
-o "$BUILD/zip_extract.o" "$ROOT/src/zip_extract.c"
"$CC" -c -O2 -Wall -Wextra -Wno-unused-parameter \
-I"$ROOT/third_party/minizip-ng/include" -I"$ROOT/src" -I"$COMPAT_INC" \
"${RAR_CFLAGS[@]}" -include "$ROOT/tests/posix_compat.h" \
-I"$ROOT/third_party/minizip-ng/include" -I"$ROOT/third_party/unrar7" -I"$ROOT/src" -I"$COMPAT_INC" \
-include "$ROOT/tests/posix_compat.h" \
-o "$BUILD/rar_extract.o" "$ROOT/src/rar_extract.c"
"$CC" -c -O2 -Wall -Wextra -Wno-unused-parameter -I"$ROOT/src" \
@@ -61,13 +71,15 @@ COMPAT_INC="$ROOT/tests/compat"
-o "$BUILD/test_zip_extract.o" "$ROOT/tests/test_zip_extract.c"
"$CC" -c -O2 -Wall -Wextra -Wno-unused-parameter -I"$ROOT/src" \
-I"$COMPAT_INC" "${RAR_CFLAGS[@]}" -include "$ROOT/tests/posix_compat.h" \
-I"$COMPAT_INC" -include "$ROOT/tests/posix_compat.h" \
-o "$BUILD/test_rar_extract.o" "$ROOT/tests/test_rar_extract.c"
objs=()
rar_objs=()
for obj in "$BUILD"/*.o; do
case "$obj" in
*/zip_extract.o|*/rar_extract.o|*/test_zip_extract.o|*/test_rar_extract.o) continue ;;
*/unrar7_*.o) rar_objs+=("$obj"); continue ;;
esac
objs+=("$obj")
done
@@ -75,9 +87,17 @@ done
"$CC" -O2 -o "$BUILD/test-zip-extract" \
"$BUILD/zip_extract.o" "$BUILD/test_zip_extract.o" "${objs[@]}"
"$CC" -O2 -o "$BUILD/test-rar-extract" \
# The RAR test links the unrar7 objects, so it needs the C++ driver.
# Windows unrar system.cpp references SetSuspendState (PowrProf).
RAR_LIBS=()
[ "$HOST_KIND" = windows ] && RAR_LIBS=(-lpowrprof)
"$CXX" -O2 -o "$BUILD/test-rar-extract" \
"$BUILD/rar_extract.o" "$BUILD/test_rar_extract.o" \
"$BUILD/zip_extract.o" "${objs[@]}"
"$BUILD/zip_extract.o" "${objs[@]}" "${rar_objs[@]}" "${RAR_LIBS[@]}"
"$BUILD/test-zip-extract" "$ROOT/tests/fixtures" "$BUILD/work-zip"
"$BUILD/test-rar-extract" "$ROOT/tests/fixtures" "$BUILD/work-rar"
# Real RAR fixtures (v6 / multi-volume / encrypted) live in fixtures-real/,
# generated by tests/make-rar-fixtures.bat (WinRAR required); fixtures/
# itself is wiped by make_fixtures.py on every run.
"$BUILD/test-rar-extract" "$ROOT/tests/fixtures" "$BUILD/work-rar" \
"$ROOT/tests/fixtures-real"
+127 -1
View File
@@ -28,6 +28,7 @@
#include "posix_compat.h"
static const char *g_fixtures;
static const char *g_fixtures_real;
static char g_work[4096];
static int g_failures;
static int g_checks;
@@ -37,6 +38,11 @@ fixture_path(char *out, size_t size, const char *name) {
snprintf(out, size, "%s/%s", g_fixtures, name);
}
static void
fixture_real_path(char *out, size_t size, const char *name) {
snprintf(out, size, "%s/%s", g_fixtures_real, name);
}
static void
work_path(char *out, size_t size, const char *name) {
snprintf(out, size, "%s/%s", g_work, name);
@@ -298,13 +304,132 @@ test_limits_handoff(void) {
"default() matches ZIPX_LIMITS_DEFAULT");
}
/* Real-archive happy paths (v1.9, unrar 7.20.1 engine).
The fixtures are produced by tests/make-rar-fixtures.bat on a machine with
WinRAR; when they are missing (plain CI checkout) these checks SKIP. */
static void
test_real_archives(void) {
char dst[4096];
char checkp[4096];
printf("test_real_archives\n");
/* RAR5 (WinRAR 6/7 "v6" compression) single volume. */
work_path(dst, sizeof(dst), "rdst_v6");
remove_dir(dst);
{
zipx_result_t res;
zipx_status_t st;
char src[4096];
fixture_real_path(src, sizeof(src), "basic-v6.rar");
if(exists(src)) {
memset(&res, 0, sizeof(res));
st = rar_extract(src, dst, ZIPX_CONFLICT_FAIL,
zipx_default_limits(), NULL, NULL, NULL, &res);
check(st == ZIPX_OK, "basic-v6.rar extracts (v6 RAR5)");
if(st == ZIPX_OK) {
snprintf(checkp, sizeof(checkp), "%s/root.txt", dst);
check(exists(checkp), " root.txt present");
snprintf(checkp, sizeof(checkp), "%s/dir/nested.txt", dst);
check(exists(checkp), " dir/nested.txt present");
check(res.entries_done >= 3, " entries_done >= 3");
} else {
printf(" message=%s\n", res.message);
}
} else {
printf(" SKIP basic-v6.rar (missing — run tests/make-rar-fixtures.bat)\n");
}
}
remove_dir(dst);
/* RAR4 legacy single volume. */
work_path(dst, sizeof(dst), "rdst_r4");
remove_dir(dst);
{
zipx_result_t res;
zipx_status_t st;
char src[4096];
fixture_real_path(src, sizeof(src), "basic-rar4.rar");
if(exists(src)) {
memset(&res, 0, sizeof(res));
st = rar_extract(src, dst, ZIPX_CONFLICT_FAIL,
zipx_default_limits(), NULL, NULL, NULL, &res);
check(st == ZIPX_OK, "basic-rar4.rar extracts (RAR4)");
if(st == ZIPX_OK) {
snprintf(checkp, sizeof(checkp), "%s/root.txt", dst);
check(exists(checkp), " root.txt present");
} else {
printf(" message=%s\n", res.message);
}
} else {
printf(" SKIP basic-rar4.rar (missing)\n");
}
}
remove_dir(dst);
/* Multi-volume: opening vol.part1.rar must auto-merge part2 from the same
directory (unrar drives the volume chain). */
work_path(dst, sizeof(dst), "rdst_vol");
remove_dir(dst);
{
zipx_result_t res;
zipx_status_t st;
char src[4096];
fixture_real_path(src, sizeof(src), "vol.part1.rar");
if(exists(src)) {
memset(&res, 0, sizeof(res));
st = rar_extract(src, dst, ZIPX_CONFLICT_FAIL,
zipx_default_limits(), NULL, NULL, NULL, &res);
check(st == ZIPX_OK, "vol.part1.rar auto-merges volumes");
if(st == ZIPX_OK) {
snprintf(checkp, sizeof(checkp), "%s/root.txt", dst);
check(exists(checkp), " root.txt present across volumes");
snprintf(checkp, sizeof(checkp), "%s/big.bin", dst);
check(exists(checkp), " big.bin (split file) present and whole");
check(res.entries_done >= 2, " entries_done >= 2");
} else {
printf(" message=%s\n", res.message);
}
} else {
printf(" SKIP vol.part1.rar (missing)\n");
}
}
remove_dir(dst);
/* Encrypted: engine can decrypt but the password channel is not wired yet,
so encrypted entries must be rejected up front with UNSUPPORTED. */
work_path(dst, sizeof(dst), "rdst_enc");
remove_dir(dst);
{
zipx_result_t res;
zipx_status_t st;
char src[4096];
fixture_real_path(src, sizeof(src), "enc-v6.rar");
if(exists(src)) {
memset(&res, 0, sizeof(res));
st = rar_extract(src, dst, ZIPX_CONFLICT_FAIL,
zipx_default_limits(), NULL, NULL, NULL, &res);
check(st == ZIPX_ERR_UNSUPPORTED, "enc-v6.rar rejected (no password channel)");
check(!exists(dst), " no files written for encrypted archive");
if(st == ZIPX_OK) {
remove_dir(dst);
}
} else {
printf(" SKIP enc-v6.rar (missing)\n");
}
}
remove_dir(dst);
}
int
main(int argc, char **argv) {
if(argc < 3) {
fprintf(stderr, "usage: %s <fixtures-dir> <work-dir>\n", argv[0]);
fprintf(stderr, "usage: %s <fixtures-dir> <work-dir> [real-fixtures-dir]\n",
argv[0]);
return 2;
}
g_fixtures = argv[1];
g_fixtures_real = (argc >= 4) ? argv[3] : argv[1];
snprintf(g_work, sizeof(g_work), "%s", argv[2]);
mkdir(g_work, 0777);
@@ -341,6 +466,7 @@ main(int argc, char **argv) {
test_engine_dispatch();
test_format_translation();
test_limits_handoff();
test_real_archives();
printf("\nrar_extract: %d checks, %d failures\n", g_checks, g_failures);
return g_failures == 0 ? 0 : 1;
+642 -639
View File
File diff suppressed because it is too large. Load diff
-339
View File
@@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
-10
View File
@@ -1,10 +0,0 @@
dmc_unrar README
================
dmc_unrar is a dependency-free, single-file FLOSS library for unpacking
and decompressing [RAR](https://en.wikipedia.org/wiki/RAR_(file_format))
archives. dmc_unrar is licensed under the terms of the [GNU General
Public License version 2](https://www.gnu.org/licenses/gpl-2.0.html) (or
later).
Please see dmc_unrar.c for details.
-77
View File
@@ -1,77 +0,0 @@
# Vendored: dmc_unrar 1.7.0
## Source
- Repository: https://github.com/DrMcCoy/dmc_unrar
- Upstream commit: master @ 2026-09-05 (DRMcCoy/dmc_unrar master)
- Author: Sven Hesse (DrMcCoy) <drmccoy@drmccoy.de>
- License: GPL-2.0-or-later (see COPYING in this directory)
We vendor only the implementation source file `dmc_unrar.c` (11598 LOC, ~365 KiB).
The repository does not ship a separate header; the entire API is exposed
inline in the `.c` file (see top-of-file docstrings + `example.c`).
## What is supported
- RAR 1.5 / 2.0 / 2.6 / 2.9 / 3.0 / 3.6 / 4.0 / 5.0 archive formats
- Solid blocks, dictionary sizes up to 4 MiB (RAR4) / 32 MiB (RAR5)
- PPMd decompression (used by RAR 3.0+)
- Archive comments (ASCII / UTF-16LE)
- Encrypted archive/file *detection* (returns an explicit error)
## What is NOT supported (by upstream design)
- **Encrypted RAR files** — `DMC_UNRAR_(ARCHIVE|FILE)_UNSUPPORTED_ENCRYPTED`.
We surface this as `ZIPX_ERR_UNSUPPORTED` in the wrapper. Upstream
intentionally omits decryption to avoid patent complications.
- **Multi-volume (split) RAR archives** — `DMC_UNRAR_ARCHIVE_UNSUPPORTED_VOLUMES`.
We surface this as `ZIPX_ERR_UNSUPPORTED`. dmc_unrar does not chain
`.partNN.rar` siblings; users must join / unrar on a PC first.
- Symbolic links, FIFOs, sockets, devices — `DMC_UNRAR_FILE_UNSUPPORTED_LINK`.
## Why dmc_unrar and not the upstream rarlab UnRAR
The rarlab UnRAR source tree (https://www.rarlab.com/rar/unrarsrc-*.tar.gz,
mirrored at https://github.com/opello/unrar) supports encrypted and
multi-volume archives, but it is a 150-file C++17 codebase with its own
filesystem abstraction, Windows registry probe, and threading pool that
requires `-DRAR_SMP`. Integrating it cleanly into the PS5 toolchain would
need:
1. A separate CXX linker step in the Makefile.
2. Verification that `prospero-clang` (-std=c++17, freestanding) builds it
without libc++ dependencies beyond what `prospero-pkg-config` exposes.
3. A new exit-error contract for the `--strip` toolchain.
4. ~10× the audit surface (every `#include <windows.h>` etc.).
We decided the cost > benefit for v1.8: single-volume RAR is the dominant
PS5 Web File Manager use case (uploading an unzipped payload, dumping a
single .pkg as `.rar` for transfer). Users needing multi-volume /
encrypted RAR are explicitly informed via the web UI error message that
they should extract on a PC first.
## Upgrading to a fuller library (v1.9 plan)
When (if) demand justifies it, the recommended upgrade path is:
1. Vendor `https://github.com/opello/unrar` (a faithful mirror of rarlab's
UnRAR 7.x) into `third_party/unrar/` (replacing dmc_unrar.c).
2. Add a CXX compilation rule in `Makefile`:
```
THIRD_PARTY_CPP_SRCS := $(wildcard third_party/unrar/*.cpp)
PS5_TP_CPP_OBJS := $(patsubst %.cpp,ps5-obj/%.o,$(THIRD_PARTY_CPP_SRCS))
$(BIN): ... $(PS5_TP_CPP_OBJS)
$(CXX) -o $@ ... $(PS5_TP_CPP_OBJS) ...
```
3. Build dmc_unrar-style driver code against the DLL API
(`-DRARDLL`, link via `dll.cpp`). The RAR API entry points
`RAROpenArchiveEx`, `RARReadHeaderEx`, `RARProcessFileW` accept the
password via `RARSetPassword`.
4. Rewrite `src/rar_extract.c` to call the new engine while preserving
the `rar_extract()` signature and the `zipx_status_t` error mapping.
5. Tests in `tests/test_rar_extract.c` should not need to change — only
the engine behind `rar_extract()`.
## Modifications to dmc_unrar.c
None. We pin the upstream file verbatim to keep the upgrade path trivial.
-11598
View File
File diff suppressed because it is too large. Load diff
-139
View File
@@ -1,139 +0,0 @@
/* Facade header for dmc_unrar 1.7.0 (vendored at third_party/unrar/dmc_unrar.c).
*
* This file declares only the dmc_unrar entry points that src/rar_extract.c
* actually uses. It exists so that rar_extract.c does NOT have to
* `#include "dmc_unrar.c"` — that would otherwise pull the entire library
* into the same translation unit, where the host test build's
* tests/posix_compat.h renames `open` / `close` to `wfm_open` / `wfm_close`
* and breaks dmc_unrar's `dmc_unrar_io_handler` struct member access.
*
* On the PS5 the build (Makefile) compiles dmc_unrar.c separately into
* `ps5-obj/.../dmc_unrar.o` and links it with the rest. On the host test
* runner, `tests/run-tests.sh` does the same with `dmc_unrar.o` (built
* without the POSIX compat shim) and `rar_extract.o` (built with it). */
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
/* DMC_UNRAR_SIZE_T and DMC_UNRAR_OFFSET_T are normally typedef'd by
dmc_unrar.c. Mirror them here so consumers do not need to include it. */
#ifndef DMC_UNRAR_SIZE_T
typedef uint64_t dmc_unrar_size_t;
typedef int64_t dmc_unrar_offset_t;
#endif
typedef enum {
DMC_UNRAR_OK = 0,
DMC_UNRAR_NO_ALLOC,
DMC_UNRAR_ALLOC_FAIL,
DMC_UNRAR_OPEN_FAIL,
DMC_UNRAR_READ_FAIL,
DMC_UNRAR_WRITE_FAIL,
DMC_UNRAR_SEEK_FAIL,
DMC_UNRAR_INVALID_DATA,
DMC_UNRAR_ARCHIVE_EMPTY,
DMC_UNRAR_ARCHIVE_IS_NULL,
DMC_UNRAR_ARCHIVE_NOT_CLEARED,
DMC_UNRAR_ARCHIVE_MISSING_FIELDS,
DMC_UNRAR_ARCHIVE_NOT_RAR,
DMC_UNRAR_ARCHIVE_UNSUPPORTED_ANCIENT,
DMC_UNRAR_ARCHIVE_UNSUPPORTED_VOLUMES,
DMC_UNRAR_ARCHIVE_UNSUPPORTED_ENCRYPTED,
DMC_UNRAR_FILE_IS_INVALID,
DMC_UNRAR_FILE_IS_DIRECTORY,
DMC_UNRAR_FILE_SOLID_BROKEN,
DMC_UNRAR_FILE_CRC32_FAIL,
DMC_UNRAR_FILE_UNSUPPORTED_VERSION,
DMC_UNRAR_FILE_UNSUPPORTED_METHOD,
DMC_UNRAR_FILE_UNSUPPORTED_ENCRYPTED,
DMC_UNRAR_FILE_UNSUPPORTED_SPLIT,
DMC_UNRAR_FILE_UNSUPPORTED_LINK,
DMC_UNRAR_FILE_UNSUPPORTED_LARGE
/* Values after DMC_UNRAR_FILE_UNSUPPORTED_LARGE (HUFF_*, PPMD_*, FILTERS_*,
*_DISABLED_FEATURE_*, RAR15/20/30/50 specific codes) are intentionally
omitted — rar_extract.c only translates the codes it knows about and
groups the rest into ZIPX_ERR_FORMAT via the default case. */
} dmc_unrar_return;
typedef enum {
DMC_UNRAR_HOSTOS_DOS = 0,
DMC_UNRAR_HOSTOS_OS2 = 1,
DMC_UNRAR_HOSTOS_WIN32 = 2,
DMC_UNRAR_HOSTOS_UNIX = 3,
DMC_UNRAR_HOSTOS_MACOS = 4,
DMC_UNRAR_HOSTOS_BEOS = 5
} dmc_unrar_host_os;
typedef struct dmc_unrar_file_tag {
uint64_t compressed_size;
uint64_t uncompressed_size;
dmc_unrar_host_os host_os;
bool has_crc;
uint32_t crc;
uint64_t unix_time;
uint64_t attrs;
} dmc_unrar_file;
/* dmc_unrar_archive is an opaque struct in the vendored library. We expose
it here so callers can stack-allocate one and pass &rar to the API. The
field list is *not* required — the dmc_unrar API treats the struct as a
token, but we need a complete type so callers can declare it as a local
variable. The layout is irrelevant because the dmc_unrar API only uses
pointers and the real definition lives inside dmc_unrar.c. */
typedef struct dmc_unrar_archive_tag {
void *alloc;
void *io;
void *internal_state;
} dmc_unrar_archive;
const char *dmc_unrar_strerror(dmc_unrar_return code);
bool dmc_unrar_is_rar_path(const char *path);
dmc_unrar_return dmc_unrar_archive_init(dmc_unrar_archive *archive);
void dmc_unrar_archive_close(dmc_unrar_archive *archive);
dmc_unrar_return dmc_unrar_archive_open_path(dmc_unrar_archive *archive,
const char *path);
dmc_unrar_size_t dmc_unrar_get_file_count(dmc_unrar_archive *archive);
const dmc_unrar_file *dmc_unrar_get_file_stat(dmc_unrar_archive *archive,
dmc_unrar_size_t index);
dmc_unrar_size_t dmc_unrar_get_filename(dmc_unrar_archive *archive,
dmc_unrar_size_t index,
char *filename,
dmc_unrar_size_t filename_size);
bool dmc_unrar_file_is_directory(dmc_unrar_archive *archive,
dmc_unrar_size_t index);
dmc_unrar_return dmc_unrar_file_is_supported(dmc_unrar_archive *archive,
dmc_unrar_size_t index);
bool dmc_unrar_unicode_make_valid_utf8(char *str);
dmc_unrar_return dmc_unrar_extract_file_to_path(dmc_unrar_archive *archive,
dmc_unrar_size_t index,
const char *path,
dmc_unrar_size_t *uncompressed_size,
bool validate_crc);
#ifdef __cplusplus
}
#endif
-239
View File
@@ -1,239 +0,0 @@
/* dmc_unrar - A dependency-free, single-file FLOSS unrar library
*
* Copyright (c) 2017 by Sven Hesse (DrMcCoy) <drmccoy@drmccoy.de>
*
* dmc_unrar is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 2 of
* the License, or (at your option) any later version.
*
* dmc_unrar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* For the full text of the GNU General Public License version 2,
* see <https://www.gnu.org/licenses/gpl-2.0.html>
*/
/* This is a small example program to show how to use dmc_unrar. */
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdio.h>
#include "dmc_unrar.c"
int display_help(const char *name);
int display_error(const char *where, const char *str);
char get_command(const char *param);
const char *get_archive_comment(dmc_unrar_archive *archive);
const char *get_file_comment(dmc_unrar_archive *archive, dmc_unrar_size_t i);
const char *get_filename(dmc_unrar_archive *archive, dmc_unrar_size_t i);
const char *get_filename_no_directory(const char *filename);
int main(int argc, char **argv) {
dmc_unrar_size_t i;
char cmd;
const char *comment = NULL;
dmc_unrar_archive archive;
dmc_unrar_return return_code;
if (argc < 3)
return display_help(argv[0]);
cmd = get_command(argv[1]);
if (cmd == 0)
return display_help(argv[0]);
if (!dmc_unrar_is_rar_path(argv[2]))
return display_help(argv[0]);
return_code = dmc_unrar_archive_init(&archive);
if (return_code != DMC_UNRAR_OK)
return display_error("Clear", dmc_unrar_strerror(return_code));
return_code = dmc_unrar_archive_open_path(&archive, argv[2]);
if (return_code != DMC_UNRAR_OK)
return display_error("Open", dmc_unrar_strerror(return_code));
comment = get_archive_comment(&archive);
if (comment) {
printf(".--- Archive comment:\n");
printf("%s\n", comment);
printf("'---\n\n");
free((char *)comment);
}
for (i = 0; i < dmc_unrar_get_file_count(&archive); i++) {
const char *name = get_filename(&archive, i), *file_comment = NULL;
const dmc_unrar_file *file = dmc_unrar_get_file_stat(&archive, i);
printf("%u/%u: \"%s\" - %llu bytes\n",
(unsigned int)(i+1), (unsigned int)dmc_unrar_get_file_count(&archive),
name ? name : "", file ? (unsigned long long)file->uncompressed_size : (unsigned long long)0);
file_comment = get_file_comment(&archive, i);
if (file_comment) {
printf(".--- File comment:\n");
printf("%s\n", file_comment);
printf("'---\n\n");
free((char *)file_comment);
}
if (cmd == 'e') {
const char *filename = get_filename_no_directory(name);
if (filename && !dmc_unrar_file_is_directory(&archive, i)) {
dmc_unrar_return supported = dmc_unrar_file_is_supported(&archive, i);
if (supported == DMC_UNRAR_OK) {
dmc_unrar_return extracted = dmc_unrar_extract_file_to_path(&archive, i, filename, NULL, true);
if (extracted != DMC_UNRAR_OK)
fprintf(stderr, "Error: %s\n", dmc_unrar_strerror(extracted));
} else
fprintf(stderr, "Not supported: %s\n", dmc_unrar_strerror(supported));
}
}
free((char *)name);
}
dmc_unrar_archive_close(&archive);
return 0;
}
int display_help(const char *name) {
fprintf(stderr, "dmc_unrar - A dependency-free, single-file FLOSS unrar library\n");
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s <command> <rarfile>\n", name);
fprintf(stderr, "\n");
fprintf(stderr, "Commands:\n");
fprintf(stderr, " l List RAR archive contents\n");
fprintf(stderr, " e Extract RAR archive to current directory\n");
fprintf(stderr, "\n");
return 1;
}
int display_error(const char *where, const char *str) {
fprintf(stderr, "%s failed: %s\n", where, str);
return 1;
}
char get_command(const char *param) {
char cmd;
if (!param || (param[0] == '\0') || (param[1] != '\0'))
return 0;
cmd = param[0];
if ((cmd != 'l') && (cmd != 'e'))
return 0;
return cmd;
}
static const char *convert_comment(uint8_t *data, dmc_unrar_size_t size) {
const dmc_unrar_unicode_encoding encoding = dmc_unrar_unicode_detect_encoding(data, size);
if (encoding == DMC_UNRAR_UNICODE_ENCODING_UTF8) {
data[size] = '\0';
return (const char *)data;
}
if (encoding == DMC_UNRAR_UNICODE_ENCODING_UTF16LE) {
char *utf8_data = NULL;
dmc_unrar_size_t utf8_size = dmc_unrar_unicode_convert_utf16le_to_utf8(data, size, NULL, 0);
if (utf8_size) {
utf8_data = (char *)malloc(utf8_size);
if (utf8_data) {
utf8_size = dmc_unrar_unicode_convert_utf16le_to_utf8(data, size, utf8_data, utf8_size);
if (utf8_size) {
free(data);
return utf8_data;
}
}
}
free(data);
free(utf8_data);
return NULL;
}
free(data);
return NULL;
}
const char *get_archive_comment(dmc_unrar_archive *archive) {
uint8_t *data = NULL;
dmc_unrar_size_t size = dmc_unrar_get_archive_comment(archive, NULL, 0);
if (!size)
return NULL;
data = (uint8_t *)malloc(size + 1);
if (!data)
return NULL;
size = dmc_unrar_get_archive_comment(archive, data, size);
return convert_comment(data, size);
}
const char *get_file_comment(dmc_unrar_archive *archive, dmc_unrar_size_t i) {
uint8_t *data = NULL;
dmc_unrar_size_t size = dmc_unrar_get_file_comment(archive, i, NULL, 0);
if (!size)
return NULL;
data = (uint8_t *)malloc(size + 1);
if (!data)
return NULL;
size = dmc_unrar_get_file_comment(archive, i, data, size);
return convert_comment(data, size);
}
const char *get_filename(dmc_unrar_archive *archive, dmc_unrar_size_t i) {
char *filename = 0;
dmc_unrar_size_t size = dmc_unrar_get_filename(archive, i, NULL, 0);
if (!size)
return NULL;
filename = (char *)malloc(size);
if (!filename)
return NULL;
size = dmc_unrar_get_filename(archive, i, filename, size);
if (!size) {
free(filename);
return NULL;
}
dmc_unrar_unicode_make_valid_utf8(filename);
if (filename[0] == '\0') {
free(filename);
return NULL;
}
return filename;
}
const char *get_filename_no_directory(const char *filename) {
char *p = NULL;
if (!filename)
return NULL;
p = (char *) strrchr(filename, '/');
if (!p)
return filename;
if (p[1] == '\0')
return NULL;
return p + 1;
}
+281
View File
@@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{95CC809B-03FC-4EDB-BB20-FD07A698C05F}</ProjectGuid>
<RootNamespace>UnRAR</RootNamespace>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v140_xp</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v140_xp</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v140_xp</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>false</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v140_xp</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>14.0.24720.0</_ProjectFileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>build\unrar32\$(Configuration)\</OutDir>
<IntDir>build\unrar32\$(Configuration)\obj\</IntDir>
<LinkIncremental>true</LinkIncremental>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>build\unrar64\$(Configuration)\</OutDir>
<IntDir>build\unrar64\$(Configuration)\obj\</IntDir>
<LinkIncremental>true</LinkIncremental>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>build\unrar32\$(Configuration)\</OutDir>
<IntDir>build\unrar32\$(Configuration)\obj\</IntDir>
<LinkIncremental>false</LinkIncremental>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>build\unrar64\$(Configuration)\</OutDir>
<IntDir>build\unrar64\$(Configuration)\obj\</IntDir>
<LinkIncremental>false</LinkIncremental>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>UNRAR;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>rar.hpp</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CallingConvention>StdCall</CallingConvention>
<DisableSpecificWarnings>4007;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<TargetMachine>MachineX86</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>UNRAR;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>rar.hpp</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CallingConvention>StdCall</CallingConvention>
<DisableSpecificWarnings>4007;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<TargetMachine>MachineX64</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Neither</FavorSizeOrSpeed>
<OmitFramePointers>true</OmitFramePointers>
<WholeProgramOptimization>false</WholeProgramOptimization>
<PreprocessorDefinitions>UNRAR;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<StructMemberAlignment>Default</StructMemberAlignment>
<BufferSecurityCheck>true</BufferSecurityCheck>
<FunctionLevelLinking>true</FunctionLevelLinking>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
<FloatingPointModel>Precise</FloatingPointModel>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>rar.hpp</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CallingConvention>StdCall</CallingConvention>
<DisableSpecificWarnings>4007;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<LinkTimeCodeGeneration />
<TargetMachine>MachineX86</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
<Optimization>MinSpace</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Neither</FavorSizeOrSpeed>
<OmitFramePointers>true</OmitFramePointers>
<WholeProgramOptimization>false</WholeProgramOptimization>
<PreprocessorDefinitions>UNRAR;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>false</StringPooling>
<MinimalRebuild>false</MinimalRebuild>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<FunctionLevelLinking>true</FunctionLevelLinking>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>rar.hpp</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CallingConvention>StdCall</CallingConvention>
<DisableSpecificWarnings>4007;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<LinkTimeCodeGeneration />
<TargetMachine>MachineX64</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="archive.cpp" />
<ClCompile Include="arcread.cpp" />
<ClCompile Include="blake2s.cpp" />
<ClCompile Include="cmddata.cpp" />
<ClCompile Include="consio.cpp" />
<ClCompile Include="crc.cpp" />
<ClCompile Include="crypt.cpp" />
<ClCompile Include="encname.cpp" />
<ClCompile Include="errhnd.cpp" />
<ClCompile Include="extinfo.cpp" />
<ClCompile Include="extract.cpp" />
<ClCompile Include="filcreat.cpp" />
<ClCompile Include="file.cpp" />
<ClCompile Include="filefn.cpp" />
<ClCompile Include="filestr.cpp" />
<ClCompile Include="find.cpp" />
<ClCompile Include="getbits.cpp" />
<ClCompile Include="global.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
</PrecompiledHeader>
</ClCompile>
<ClCompile Include="hash.cpp" />
<ClCompile Include="headers.cpp" />
<ClCompile Include="isnt.cpp" />
<ClCompile Include="largepage.cpp" />
<ClCompile Include="list.cpp" />
<ClCompile Include="match.cpp" />
<ClCompile Include="motw.cpp" />
<ClCompile Include="options.cpp" />
<ClCompile Include="pathfn.cpp" />
<ClCompile Include="qopen.cpp" />
<ClCompile Include="rar.cpp" />
<ClCompile Include="rarpch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="rarvm.cpp" />
<ClCompile Include="rawread.cpp" />
<ClCompile Include="rdwrfn.cpp" />
<ClCompile Include="recvol.cpp" />
<ClCompile Include="resource.cpp" />
<ClCompile Include="rijndael.cpp" />
<ClCompile Include="rs.cpp" />
<ClCompile Include="rs16.cpp" />
<ClCompile Include="scantree.cpp" />
<ClCompile Include="secpassword.cpp" />
<ClCompile Include="sha1.cpp" />
<ClCompile Include="sha256.cpp" />
<ClCompile Include="smallfn.cpp" />
<ClCompile Include="strfn.cpp" />
<ClCompile Include="strlist.cpp" />
<ClCompile Include="system.cpp" />
<ClCompile Include="threadpool.cpp" />
<ClCompile Include="timefn.cpp" />
<ClCompile Include="ui.cpp" />
<ClCompile Include="unicode.cpp" />
<ClCompile Include="unpack.cpp" />
<ClCompile Include="volume.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
+422
View File
@@ -0,0 +1,422 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="release_nocrypt|Win32">
<Configuration>release_nocrypt</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="release_nocrypt|x64">
<Configuration>release_nocrypt</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectName>UnRAR</ProjectName>
<ProjectGuid>{E815C46C-36C4-499F-BBC2-E772C6B17971}</ProjectGuid>
<RootNamespace>UnRAR</RootNamespace>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='release_nocrypt|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v140_xp</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v140_xp</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v140_xp</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='release_nocrypt|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v140_xp</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>false</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v140_xp</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>false</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v140_xp</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='release_nocrypt|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='release_nocrypt|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>14.0.24720.0</_ProjectFileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>build\unrardll32\$(Configuration)\</OutDir>
<IntDir>build\unrardll32\$(Configuration)\obj\</IntDir>
<LinkIncremental>true</LinkIncremental>
<GenerateManifest>true</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>build\unrardll64\$(Configuration)\</OutDir>
<IntDir>build\unrardll64\$(Configuration)\obj\</IntDir>
<LinkIncremental>true</LinkIncremental>
<GenerateManifest>true</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>build\unrardll32\$(Configuration)\</OutDir>
<IntDir>build\unrardll32\$(Configuration)\obj\</IntDir>
<LinkIncremental>false</LinkIncremental>
<GenerateManifest>true</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>build\unrardll64\$(Configuration)\</OutDir>
<IntDir>build\unrardll64\$(Configuration)\obj\</IntDir>
<LinkIncremental>false</LinkIncremental>
<GenerateManifest>true</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='release_nocrypt|Win32'">
<OutDir>build\unrardll32\$(Configuration)\</OutDir>
<IntDir>build\unrardll32\$(Configuration)\obj\</IntDir>
<LinkIncremental>false</LinkIncremental>
<GenerateManifest>true</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='release_nocrypt|x64'">
<OutDir>build\unrardll64\$(Configuration)\</OutDir>
<IntDir>build\unrardll64\$(Configuration)\obj\</IntDir>
<LinkIncremental>false</LinkIncremental>
<GenerateManifest>true</GenerateManifest>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>RARDLL;UNRAR;SILENT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<ExceptionHandling>Sync</ExceptionHandling>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<StructMemberAlignment>Default</StructMemberAlignment>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>rar.hpp</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CallingConvention>Cdecl</CallingConvention>
<DisableSpecificWarnings>4007;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
</ClCompile>
<Link>
<OutputFile>$(OutDir)unrar.dll</OutputFile>
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<TargetMachine>MachineX86</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>RARDLL;UNRAR;SILENT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<ExceptionHandling>Sync</ExceptionHandling>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<StructMemberAlignment>Default</StructMemberAlignment>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>rar.hpp</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CallingConvention>Cdecl</CallingConvention>
<DisableSpecificWarnings>4007;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
</ClCompile>
<Link>
<OutputFile>$(OutDir)unrar.dll</OutputFile>
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<TargetMachine>MachineX64</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Neither</FavorSizeOrSpeed>
<OmitFramePointers>true</OmitFramePointers>
<WholeProgramOptimization>false</WholeProgramOptimization>
<PreprocessorDefinitions>RARDLL;UNRAR;SILENT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<ExceptionHandling>Sync</ExceptionHandling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<StructMemberAlignment>Default</StructMemberAlignment>
<BufferSecurityCheck>true</BufferSecurityCheck>
<FunctionLevelLinking>true</FunctionLevelLinking>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
<FloatingPointModel>Precise</FloatingPointModel>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>rar.hpp</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CallingConvention>Cdecl</CallingConvention>
<DisableSpecificWarnings>4007;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<AdditionalOptions>/SAFESEH %(AdditionalOptions)</AdditionalOptions>
<OutputFile>$(OutDir)unrar.dll</OutputFile>
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<LinkTimeCodeGeneration />
<TargetMachine>MachineX86</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Neither</FavorSizeOrSpeed>
<OmitFramePointers>true</OmitFramePointers>
<WholeProgramOptimization>false</WholeProgramOptimization>
<PreprocessorDefinitions>RARDLL;UNRAR;SILENT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>false</StringPooling>
<MinimalRebuild>false</MinimalRebuild>
<ExceptionHandling>Sync</ExceptionHandling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<StructMemberAlignment>Default</StructMemberAlignment>
<BufferSecurityCheck>true</BufferSecurityCheck>
<FunctionLevelLinking>true</FunctionLevelLinking>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>rar.hpp</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CallingConvention>Cdecl</CallingConvention>
<DisableSpecificWarnings>4007;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
</ClCompile>
<Link>
<OutputFile>$(OutDir)unrar.dll</OutputFile>
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<LinkTimeCodeGeneration />
<TargetMachine>MachineX64</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='release_nocrypt|Win32'">
<ClCompile>
<AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Neither</FavorSizeOrSpeed>
<OmitFramePointers>true</OmitFramePointers>
<WholeProgramOptimization>false</WholeProgramOptimization>
<PreprocessorDefinitions>RARDLL;UNRAR;SILENT;RAR_NOCRYPT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<ExceptionHandling>Sync</ExceptionHandling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<StructMemberAlignment>Default</StructMemberAlignment>
<BufferSecurityCheck>true</BufferSecurityCheck>
<FunctionLevelLinking>true</FunctionLevelLinking>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
<FloatingPointModel>Precise</FloatingPointModel>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>rar.hpp</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CallingConvention>Cdecl</CallingConvention>
<DisableSpecificWarnings>4007;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<AdditionalOptions>/SAFESEH %(AdditionalOptions)</AdditionalOptions>
<OutputFile>$(OutDir)unrar.dll</OutputFile>
<ModuleDefinitionFile>dll_nocrypt.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<LinkTimeCodeGeneration />
<TargetMachine>MachineX86</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='release_nocrypt|x64'">
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Neither</FavorSizeOrSpeed>
<OmitFramePointers>true</OmitFramePointers>
<WholeProgramOptimization>false</WholeProgramOptimization>
<PreprocessorDefinitions>RARDLL;UNRAR;SILENT;RAR_NOCRYPT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>false</StringPooling>
<MinimalRebuild>false</MinimalRebuild>
<ExceptionHandling>Sync</ExceptionHandling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<StructMemberAlignment>Default</StructMemberAlignment>
<BufferSecurityCheck>true</BufferSecurityCheck>
<FunctionLevelLinking>true</FunctionLevelLinking>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>rar.hpp</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CallingConvention>StdCall</CallingConvention>
<DisableSpecificWarnings>4007;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
</ClCompile>
<Link>
<OutputFile>$(OutDir)unrar.dll</OutputFile>
<ModuleDefinitionFile>dll_nocrypt.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<LinkTimeCodeGeneration />
<TargetMachine>MachineX64</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="archive.cpp" />
<ClCompile Include="arcread.cpp" />
<ClCompile Include="blake2s.cpp" />
<ClCompile Include="cmddata.cpp" />
<ClCompile Include="consio.cpp" />
<ClCompile Include="crc.cpp" />
<ClCompile Include="crypt.cpp" />
<ClCompile Include="dll.cpp" />
<ClCompile Include="encname.cpp" />
<ClCompile Include="errhnd.cpp" />
<ClCompile Include="extinfo.cpp" />
<ClCompile Include="extract.cpp" />
<ClCompile Include="filcreat.cpp" />
<ClCompile Include="file.cpp" />
<ClCompile Include="filefn.cpp" />
<ClCompile Include="filestr.cpp" />
<ClCompile Include="find.cpp" />
<ClCompile Include="getbits.cpp" />
<ClCompile Include="global.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nocrypt|Win32'">
</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nocrypt|x64'">
</PrecompiledHeader>
</ClCompile>
<ClCompile Include="hash.cpp" />
<ClCompile Include="headers.cpp" />
<ClCompile Include="isnt.cpp" />
<ClCompile Include="largepage.cpp" />
<ClCompile Include="match.cpp" />
<ClCompile Include="motw.cpp" />
<ClCompile Include="options.cpp" />
<ClCompile Include="pathfn.cpp" />
<ClCompile Include="qopen.cpp" />
<ClCompile Include="rar.cpp" />
<ClCompile Include="rarpch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nocrypt|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nocrypt|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="rarvm.cpp" />
<ClCompile Include="rawread.cpp" />
<ClCompile Include="rdwrfn.cpp" />
<ClCompile Include="rijndael.cpp" />
<ClCompile Include="rs.cpp" />
<ClCompile Include="rs16.cpp" />
<ClCompile Include="scantree.cpp" />
<ClCompile Include="secpassword.cpp" />
<ClCompile Include="sha1.cpp" />
<ClCompile Include="sha256.cpp" />
<ClCompile Include="smallfn.cpp" />
<ClCompile Include="strfn.cpp" />
<ClCompile Include="strlist.cpp" />
<ClCompile Include="system.cpp" />
<ClCompile Include="threadpool.cpp" />
<ClCompile Include="timefn.cpp" />
<ClCompile Include="ui.cpp" />
<ClCompile Include="unicode.cpp" />
<ClCompile Include="unpack.cpp" />
<ClCompile Include="volume.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="rar.hpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="dll.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
+59
View File
@@ -0,0 +1,59 @@
# Vendored: unrar 7.20.1 (RARLab UnRAR source)
Replaces `dmc_unrar` (see git history under `third_party/unrar/`) as the RAR
engine. This is the official Alexander Roshal UnRAR source, packaged on
GitHub by opello for easier vendoring.
## Source
- Upstream: https://github.com/opello/unrar — commit `97e1780310`
("v7.20.1: Extracted from https://www.rarlab.com/rar/unrarsrc-...", 2025-10-30)
- Version: 7.20.1 (see `version.hpp`: RARVER_MAJOR 7, RARVER_MINOR 20, BETA 1,
built 2025-10-28)
- Mirror of RARLab https://www.rarlab.com/rar_add.htm — this directory is a
verbatim copy of the upstream tree (159 files), no modifications except the
additions listed below.
## What this enables that dmc_unrar could not
- **RAR5 compression version 6** (WinRAR 6.x / 7.x archives, "v6:" algorithm
string). dmc_unrar 1.7.0 only dispatched `0x5000` (v5) in
`dmc_unrar_file_unpack`, so any WinRAR 6+ archive hit
`DMC_UNRAR_FILE_UNSUPPORTED_VERSION` and surfaced as "corrupt archive".
- **Encrypted RAR** (headers and/or entries) — password via `RARSetPassword`.
- **Multi-volume RAR** (`.partNN.rar` chains) — via open-mode + volume
callback handling.
- RAR4 (all sub-versions) continues to work.
## Build model
Compile the upstream DLL source set as a **static library**, define
`-DRARDLL` (suppresses `rar.cpp`'s `main()` via `#if !defined(RARDLL)`).
Source set (49 .cpp, matching `UnRARDll.vcxproj` ClCompile list):
archive arcread blake2s cmddata consio crc crypt dll encname errhnd extinfo
extract filcreat file filefn filestr find getbits global hash headers isnt
largepage match motw options pathfn qopen rar rarpch rarvm rawread rdwrfn
rijndael rs rs16 scantree secpassword sha1 sha256 smallfn strfn strlist
system threadpool timefn ui unicode unpack volume
Compile flags:
- `-std=c++17 -DRARDLL -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE`
- PS5 (`prospero-clang++`): the toolchain defaults to `-stdlib=libc++`
(FreeBSD-style sysroot; there is no GNU libstdc++). No explicit `-lc++`
needed. `_UNIX` is defined by the platform headers.
- Host tests (Windows/MinGW `g++`): libstdc++ default is fine.
- Windows link needs `-lpowrprof` (SystemSuspend in `system.cpp`);
`SetSuspendState` is referenced only on `_WIN_ALL`.
The upstream C API (`dll.hpp`) is C-compatible (extern "C" + plain C
structs). C translation units include `unrar_c_api.h` (platform shim +
re-export of the entry points used by `src/rar_extract.c`), never the unrar
C++ headers.
## Additions in this directory (not upstream)
- `unrar_c_api.h` — WFM facade shim (see file header).
- `VENDORED.md` — this file.
## License
UnRAR freeware license (`license.txt`): free to use in any software to
handle RAR archives; may NOT be used to build a RAR-compatible *archiver* or
re-create the RAR compression algorithm. Full text must stay with the
source. This replaces the GPL-2.0 dmc_unrar notice in THIRD_PARTY_NOTICES.
+59
View File
@@ -0,0 +1,59 @@
ACKNOWLEDGMENTS
* We used "Screaming Fast Galois Field Arithmetic Using Intel
SIMD Instructions" paper by James S. Plank, Kevin M. Greenan
and Ethan L. Miller to improve Reed-Solomon coding performance.
Also we are grateful to Artem Drobanov and Bulat Ziganshin
for samples and ideas allowed to make Reed-Solomon coding
more efficient.
* RAR4 text compression algorithm is based on Dmitry Shkarin PPMII
and Dmitry Subbotin carryless rangecoder public domain source code.
You can find it in ftp.elf.stuba.sk/pub/pc/pack.
* RAR encryption includes parts of public domain code
from Szymon Stefanek AES and Steve Reid SHA-1 implementations.
* With exception of SFX modules, RAR uses CRC32 function based
on Intel Slicing-by-8 algorithm. Original Intel Slicing-by-8 code
is available here:
https://sourceforge.net/projects/slicing-by-8/
Original Intel Slicing-by-8 code is licensed under BSD License
available at http://www.opensource.org/licenses/bsd-license.html
Copyright (c) 2004-2006 Intel Corporation.
All Rights Reserved
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with
the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
* RAR archives may optionally include BLAKE2sp hash ( https://blake2.net ),
designed by Jean-Philippe Aumasson, Samuel Neves, Zooko Wilcox-O'Hearn
and Christian Winnerlein.
* Useful hints provided by Alexander Khoroshev and Bulat Ziganshin allowed
to significantly improve RAR compression and speed.
+187
View File
@@ -0,0 +1,187 @@
static bool IsAnsiEscComment(const wchar *Data,size_t Size);
bool Archive::GetComment(std::wstring &CmtData)
{
if (!MainComment)
return false;
int64 SavePos=Tell();
bool Success=DoGetComment(CmtData);
Seek(SavePos,SEEK_SET);
return Success;
}
bool Archive::DoGetComment(std::wstring &CmtData)
{
#ifndef SFX_MODULE
uint CmtLength;
if (Format==RARFMT14)
{
Seek(SFXSize+SIZEOF_MAINHEAD14,SEEK_SET);
CmtLength=GetByte();
CmtLength+=(GetByte()<<8);
}
else
#endif
{
if (MainHead.CommentInHeader)
{
// Old style (RAR 2.9) archive comment embedded into the main
// archive header.
Seek(SFXSize+SIZEOF_MARKHEAD3+SIZEOF_MAINHEAD3,SEEK_SET);
if (!ReadHeader() || GetHeaderType()!=HEAD3_CMT)
return false;
}
else
{
// Current (RAR 3.0+) version of archive comment.
Seek(GetStartPos(),SEEK_SET);
if (SearchSubBlock(SUBHEAD_TYPE_CMT)!=0)
if (ReadCommentData(CmtData))
return true;
else
uiMsg(UIERROR_CMTBROKEN,FileName);
return false;
}
#ifndef SFX_MODULE
// Old style (RAR 2.9) comment header embedded into the main
// archive header.
if (BrokenHeader || CommHead.HeadSize<SIZEOF_COMMHEAD)
{
uiMsg(UIERROR_CMTBROKEN,FileName);
return false;
}
CmtLength=CommHead.HeadSize-SIZEOF_COMMHEAD;
#endif
}
#ifndef SFX_MODULE
if (Format==RARFMT14 && MainHead.PackComment || Format!=RARFMT14 && CommHead.Method!=0x30)
{
if (Format!=RARFMT14 && (CommHead.UnpVer < 15 || CommHead.UnpVer > VER_UNPACK || CommHead.Method > 0x35))
return false;
ComprDataIO DataIO;
DataIO.SetTestMode(true);
uint UnpCmtLength;
if (Format==RARFMT14)
{
#ifdef RAR_NOCRYPT
return false;
#else
UnpCmtLength=GetByte();
UnpCmtLength+=(GetByte()<<8);
if (CmtLength<2)
return false;
CmtLength-=2;
DataIO.SetCmt13Encryption();
CommHead.UnpVer=15;
#endif
}
else
UnpCmtLength=CommHead.UnpSize;
DataIO.SetFiles(this,NULL);
DataIO.EnableShowProgress(false);
DataIO.SetPackedSizeToRead(CmtLength);
DataIO.UnpHash.Init(HASH_CRC32,1);
DataIO.SetNoFileHeader(true); // this->FileHead is not filled yet.
Unpack CmtUnpack(&DataIO);
CmtUnpack.Init(0x10000,false);
CmtUnpack.SetDestSize(UnpCmtLength);
CmtUnpack.DoUnpack(CommHead.UnpVer,false);
if (Format!=RARFMT14 && (DataIO.UnpHash.GetCRC32()&0xffff)!=CommHead.CommCRC)
{
uiMsg(UIERROR_CMTBROKEN,FileName);
return false;
}
else
{
byte *UnpData;
size_t UnpDataSize;
DataIO.GetUnpackedData(&UnpData,&UnpDataSize);
if (UnpDataSize>0)
{
#ifdef _WIN_ALL
// If we ever decide to extend it to Android, we'll need to alloc
// 4x memory for OEM to UTF-8 output here.
OemToCharBuffA((char *)UnpData,(char *)UnpData,(DWORD)UnpDataSize);
#endif
std::string UnpStr((char*)UnpData,UnpDataSize);
CharToWide(UnpStr,CmtData);
}
}
}
else
{
if (CmtLength==0)
return false;
std::vector<byte> CmtRaw(CmtLength);
int ReadSize=Read(CmtRaw.data(),CmtLength);
if (ReadSize>=0 && (uint)ReadSize<CmtLength) // Comment is shorter than declared.
{
CmtLength=ReadSize;
CmtRaw.resize(CmtLength);
}
if (Format!=RARFMT14 && CommHead.CommCRC!=(~CRC32(0xffffffff,&CmtRaw[0],CmtLength)&0xffff))
{
uiMsg(UIERROR_CMTBROKEN,FileName);
return false;
}
// CmtData.resize(CmtLength+1);
CmtRaw.push_back(0);
#ifdef _WIN_ALL
// If we ever decide to extend it to Android, we'll need to alloc
// 4x memory for OEM to UTF-8 output here.
OemToCharA((char *)CmtRaw.data(),(char *)CmtRaw.data());
#endif
CharToWide((const char *)CmtRaw.data(),CmtData);
// CmtData->resize(wcslen(CmtData->data()));
}
#endif
return CmtData.size() > 0;
}
bool Archive::ReadCommentData(std::wstring &CmtData)
{
std::vector<byte> CmtRaw;
if (!ReadSubData(&CmtRaw,NULL,false))
return false;
size_t CmtSize=CmtRaw.size();
CmtRaw.push_back(0);
// CmtData->resize(CmtSize+1);
if (Format==RARFMT50)
UtfToWide((char *)CmtRaw.data(),CmtData);
else
if ((SubHead.SubFlags & SUBHEAD_FLAGS_CMT_UNICODE)!=0)
{
CmtData=RawToWide(CmtRaw);
}
else
{
CharToWide((const char *)CmtRaw.data(),CmtData);
}
// CmtData->resize(wcslen(CmtData->data())); // Set buffer size to actual comment length.
return true;
}
void Archive::ViewComment()
{
if (Cmd->DisableComment)
return;
std::wstring CmtBuf;
if (GetComment(CmtBuf)) // In GUI too, so "Test" command detects broken comments.
{
size_t CmtSize=CmtBuf.size();
auto EndPos=CmtBuf.find(0x1A);
if (EndPos!=std::wstring::npos)
CmtSize=EndPos;
mprintf(St(MArcComment));
mprintf(L":\n");
OutComment(CmtBuf);
}
}
+361
View File
@@ -0,0 +1,361 @@
#include "rar.hpp"
#include "arccmt.cpp"
Archive::Archive(CommandData *InitCmd)
{
Cmd=NULL; // Just in case we'll have an exception in 'new' below.
DummyCmd=(InitCmd==NULL);
Cmd=DummyCmd ? (new CommandData):InitCmd;
OpenShared=Cmd->OpenShared;
Format=RARFMT_NONE;
Solid=false;
Volume=false;
MainComment=false;
Locked=false;
Signed=false;
FirstVolume=false;
NewNumbering=false;
SFXSize=0;
LatestTime.Reset();
Protected=false;
Encrypted=false;
FailedHeaderDecryption=false;
BrokenHeader=false;
LastReadBlock=0;
CurHeaderType=HEAD_UNKNOWN;
CurBlockPos=0;
NextBlockPos=0;
RecoveryPercent=-1;
MainHead.Reset();
CryptHead={};
EndArcHead.Reset();
VolNumber=0;
VolWrite=0;
AddingFilesSize=0;
AddingHeadersSize=0;
Splitting=false;
NewArchive=false;
SilentOpen=false;
#ifdef USE_QOPEN
ProhibitQOpen=false;
#endif
}
Archive::~Archive()
{
if (DummyCmd)
delete Cmd;
}
void Archive::CheckArc(bool EnableBroken)
{
if (!IsArchive(EnableBroken))
{
// If FailedHeaderDecryption is set, we already reported that archive
// password is incorrect.
if (!FailedHeaderDecryption)
uiMsg(UIERROR_BADARCHIVE,FileName);
ErrHandler.Exit(RARX_BADARC);
}
}
#if !defined(SFX_MODULE)
void Archive::CheckOpen(const std::wstring &Name)
{
TOpen(Name);
CheckArc(false);
}
#endif
bool Archive::WCheckOpen(const std::wstring &Name)
{
if (!WOpen(Name))
return false;
if (!IsArchive(false))
{
uiMsg(UIERROR_BADARCHIVE,FileName);
Close();
return false;
}
return true;
}
RARFORMAT Archive::IsSignature(const byte *D,size_t Size)
{
RARFORMAT Type=RARFMT_NONE;
if (Size>=1 && D[0]==0x52)
#ifndef SFX_MODULE
if (Size>=4 && D[1]==0x45 && D[2]==0x7e && D[3]==0x5e)
Type=RARFMT14;
else
#endif
if (Size>=7 && D[1]==0x61 && D[2]==0x72 && D[3]==0x21 && D[4]==0x1a && D[5]==0x07)
{
// We check the last signature byte, so we can return a sensible
// warning in case we'll want to change the archive format
// sometimes in the future.
#ifndef SFX_MODULE
if (D[6]==0)
Type=RARFMT15;
else
#endif
if (D[6]==1)
Type=RARFMT50;
else
if (D[6]>1 && D[6]<5)
Type=RARFMT_FUTURE;
}
return Type;
}
bool Archive::IsArchive(bool EnableBroken)
{
Encrypted=false;
BrokenHeader=false; // Might be left from previous volume.
#ifndef SFX_MODULE
if (IsDevice())
{
uiMsg(UIERROR_INVALIDNAME,FileName,FileName);
return false;
}
#endif
if (Read(MarkHead.Mark,SIZEOF_MARKHEAD3)!=SIZEOF_MARKHEAD3)
return false;
SFXSize=0;
RARFORMAT Type;
if ((Type=IsSignature(MarkHead.Mark,SIZEOF_MARKHEAD3))!=RARFMT_NONE)
{
Format=Type;
if (Format==RARFMT14)
Seek(Tell()-SIZEOF_MARKHEAD3,SEEK_SET);
}
else
{
std::vector<char> Buffer(MAXSFXSIZE);
long CurPos=(long)Tell();
int ReadSize=Read(Buffer.data(),Buffer.size()-16);
for (int I=0;I<ReadSize;I++)
if (Buffer[I]==0x52 && (Type=IsSignature((byte *)&Buffer[I],ReadSize-I))!=RARFMT_NONE)
{
Format=Type;
if (Format==RARFMT14 && I>0 && CurPos<28 && ReadSize>31)
{
char *D=&Buffer[28-CurPos];
if (D[0]!=0x52 || D[1]!=0x53 || D[2]!=0x46 || D[3]!=0x58)
continue;
}
SFXSize=CurPos+I;
Seek(SFXSize,SEEK_SET);
if (Format==RARFMT15 || Format==RARFMT50)
Read(MarkHead.Mark,SIZEOF_MARKHEAD3);
break;
}
if (SFXSize==0)
return false;
}
if (Format==RARFMT_FUTURE)
{
uiMsg(UIERROR_NEWRARFORMAT,FileName);
return false;
}
if (Format==RARFMT50) // RAR 5.0 signature is by one byte longer.
{
if (Read(MarkHead.Mark+SIZEOF_MARKHEAD3,1)!=1 || MarkHead.Mark[SIZEOF_MARKHEAD3]!=0)
return false;
MarkHead.HeadSize=SIZEOF_MARKHEAD5;
}
else
MarkHead.HeadSize=SIZEOF_MARKHEAD3;
#ifdef RARDLL
// If callback function is not set, we cannot get the password,
// so we skip the initial header processing for encrypted header archive.
// It leads to skipped archive comment, but the rest of archive data
// is processed correctly.
if (Cmd->Callback==NULL)
SilentOpen=true;
#endif
bool HeadersLeft; // Any headers left to read.
bool StartFound=false; // Main or encryption headers found.
// Skip the archive encryption header if any and read the main header.
while ((HeadersLeft=(ReadHeader()!=0))==true) // Additional parentheses to silence Clang.
{
SeekToNext();
HEADER_TYPE Type=GetHeaderType();
// In RAR 5.0 we need to quit after reading HEAD_CRYPT if we wish to
// avoid the password prompt.
StartFound=Type==HEAD_MAIN || SilentOpen && Type==HEAD_CRYPT;
if (StartFound)
break;
}
// We should not do it for EnableBroken or we'll get 'not RAR archive'
// messages when extracting encrypted archives with wrong password.
if (FailedHeaderDecryption && !EnableBroken)
return false;
if (BrokenHeader || !StartFound) // Main archive header is corrupt or missing.
{
if (!FailedHeaderDecryption) // If not reported a wrong password already.
uiMsg(UIERROR_MHEADERBROKEN,FileName);
if (!EnableBroken)
return false;
}
MainComment=MainHead.CommentInHeader;
// If we process non-encrypted archive or can request a password,
// we set 'first volume' flag based on file attributes below.
// It is necessary for RAR 2.x archives, which did not have 'first volume'
// flag in main header. Also for all RAR formats we need to scan until
// first file header to set "comment" flag when reading service header.
// Unless we are in silent mode, we need to know about presence of comment
// immediately after IsArchive call.
if (HeadersLeft && (!SilentOpen || !Encrypted) && IsSeekable())
{
int64 SavePos=Tell();
int64 SaveCurBlockPos=CurBlockPos,SaveNextBlockPos=NextBlockPos;
HEADER_TYPE SaveCurHeaderType=CurHeaderType;
while (ReadHeader()!=0)
{
HEADER_TYPE HeaderType=GetHeaderType();
if (HeaderType==HEAD_SERVICE)
{
// If we have a split service headers, it surely indicates non-first
// volume. But not split service header does not guarantee the first
// volume, because we can have split file after non-split archive
// comment. So we do not quit from loop here.
FirstVolume=Volume && !SubHead.SplitBefore;
}
else
if (HeaderType==HEAD_FILE)
{
FirstVolume=Volume && !FileHead.SplitBefore;
break;
}
else
if (HeaderType==HEAD_ENDARC) // Might happen if archive contains only a split service header.
break;
SeekToNext();
}
CurBlockPos=SaveCurBlockPos;
NextBlockPos=SaveNextBlockPos;
CurHeaderType=SaveCurHeaderType;
Seek(SavePos,SEEK_SET);
}
if (!Volume || FirstVolume)
FirstVolumeName=FileName;
return true;
}
void Archive::SeekToNext()
{
Seek(NextBlockPos,SEEK_SET);
}
// Calculate the block size including encryption fields and padding if any.
uint Archive::FullHeaderSize(size_t Size)
{
if (Encrypted)
{
Size = ALIGN_VALUE(Size, CRYPT_BLOCK_SIZE); // Align to encryption block size.
if (Format == RARFMT50)
Size += SIZE_INITV;
else
Size += SIZE_SALT30;
}
return uint(Size);
}
#ifdef USE_QOPEN
bool Archive::Open(const std::wstring &Name,uint Mode)
{
// Important if we reuse Archive object and it has virtual QOpen
// file position not matching real. For example, for 'l -v volname'.
QOpen.Unload();
return File::Open(Name,Mode);
}
int Archive::Read(void *Data,size_t Size)
{
size_t Result;
if (QOpen.Read(Data,Size,Result))
return (int)Result;
return File::Read(Data,Size);
}
void Archive::Seek(int64 Offset,int Method)
{
if (!QOpen.Seek(Offset,Method))
File::Seek(Offset,Method);
}
int64 Archive::Tell()
{
int64 QPos;
if (QOpen.Tell(&QPos))
return QPos;
return File::Tell();
}
#endif
// Return 0 if dictionary size is invalid. If size is RAR7 only, return
// the adjusted nearest bottom value. Return header flags in Flags.
uint64 Archive::GetWinSize(uint64 Size,uint &Flags)
{
Flags=0;
// Allow 128 KB - 1 TB range.
if (Size<0x20000 || Size>0x10000000000ULL)
return 0;
uint64 Pow2=0x20000; // Power of 2 dictionary size.
for (;2*Pow2<=Size;Pow2*=2)
Flags+=FCI_DICT_BIT0;
if (Size==Pow2)
return Size; // If 'Size' is the power of 2, return it as is.
// Get the number of Pow2/32 to add to Pow2 for nearest value not exceeding 'Size'.
uint64 Fraction=(Size-Pow2)/(Pow2/32);
Flags+=(uint)Fraction*FCI_DICT_FRACT0;
return Pow2+Fraction*(Pow2/32);
}
+158
View File
@@ -0,0 +1,158 @@
#ifndef _RAR_ARCHIVE_
#define _RAR_ARCHIVE_
class PPack;
class RawRead;
class RawWrite;
enum NOMODIFY_FLAGS
{
NMDF_ALLOWLOCK=1,NMDF_ALLOWANYVOLUME=2,NMDF_ALLOWFIRSTVOLUME=4
};
enum RARFORMAT {RARFMT_NONE,RARFMT14,RARFMT15,RARFMT50,RARFMT_FUTURE};
enum ADDSUBDATA_FLAGS
{
ASDF_SPLIT = 1, // Allow to split archive just before header if necessary.
ASDF_COMPRESS = 2, // Allow to compress data following subheader.
ASDF_CRYPT = 4, // Encrypt data after subheader if password is set.
ASDF_CRYPTIFHEADERS = 8 // Encrypt data after subheader only in -hp mode.
};
// RAR5 headers must not exceed 2 MB.
#define MAX_HEADER_SIZE_RAR5 0x200000
class Archive:public File
{
private:
void UpdateLatestTime(FileHeader *CurBlock);
void ConvertNameCase(std::wstring &Name);
void ConvertFileHeader(FileHeader *hd);
size_t ReadHeader14();
size_t ReadHeader15();
size_t ReadHeader50();
void ProcessExtra50(RawRead *Raw,size_t ExtraSize,const BaseBlock *bb);
void RequestArcPassword(RarCheckPassword *SelPwd);
void UnexpEndArcMsg();
void BrokenHeaderMsg();
void UnkEncVerMsg(const std::wstring &Name,const std::wstring &Info);
bool DoGetComment(std::wstring &CmtData);
bool ReadCommentData(std::wstring &CmtData);
#if !defined(RAR_NOCRYPT)
CryptData HeadersCrypt;
#endif
ComprDataIO SubDataIO;
bool DummyCmd;
CommandData *Cmd;
int RecoveryPercent;
RarTime LatestTime;
int LastReadBlock;
HEADER_TYPE CurHeaderType;
bool SilentOpen;
#ifdef USE_QOPEN
QuickOpen QOpen;
bool ProhibitQOpen;
#endif
public:
Archive(CommandData *InitCmd=nullptr);
~Archive();
static RARFORMAT IsSignature(const byte *D,size_t Size);
bool IsArchive(bool EnableBroken);
size_t SearchBlock(HEADER_TYPE HeaderType);
size_t SearchSubBlock(const wchar *Type);
size_t SearchRR();
int GetRecoveryPercent() {return RecoveryPercent;}
size_t ReadHeader();
void CheckArc(bool EnableBroken);
void CheckOpen(const std::wstring &Name);
bool WCheckOpen(const std::wstring &Name);
bool GetComment(std::wstring &CmtData);
void ViewComment();
void SetLatestTime(RarTime *NewTime);
void SeekToNext();
bool CheckAccess();
bool IsArcDir();
void ConvertAttributes();
void VolSubtractHeaderSize(size_t SubSize);
uint FullHeaderSize(size_t Size);
int64 GetStartPos();
void AddSubData(const byte *SrcData,uint64 DataSize,File *SrcFile,
const wchar *Name,uint Flags);
bool ReadSubData(std::vector<byte> *UnpData,File *DestFile,bool TestMode);
HEADER_TYPE GetHeaderType() {return CurHeaderType;}
CommandData* GetCommandData() {return Cmd;}
void SetSilentOpen(bool Mode) {SilentOpen=Mode;}
#ifdef USE_QOPEN
bool Open(const std::wstring &Name,uint Mode=FMF_READ) override;
int Read(void *Data,size_t Size) override;
void Seek(int64 Offset,int Method) override;
int64 Tell() override;
void QOpenUnload() {QOpen.Unload();}
void SetProhibitQOpen(bool Mode) {ProhibitQOpen=Mode;}
#endif
static uint64 GetWinSize(uint64 Size,uint &Flags);
// Needed to see wstring based Open from File. Otherwise compiler finds
// Open in Archive and doesn't check the base class overloads.
using File::Open;
BaseBlock ShortBlock;
MarkHeader MarkHead;
MainHeader MainHead;
CryptHeader CryptHead;
FileHeader FileHead;
EndArcHeader EndArcHead;
SubBlockHeader SubBlockHead;
FileHeader SubHead;
CommentHeader CommHead;
ProtectHeader ProtectHead;
EAHeader EAHead;
StreamHeader StreamHead;
int64 CurBlockPos;
int64 NextBlockPos;
RARFORMAT Format;
bool Solid;
bool Volume;
bool MainComment;
bool Locked;
bool Signed;
bool FirstVolume;
bool NewNumbering;
bool Protected;
bool Encrypted;
size_t SFXSize;
bool BrokenHeader;
bool FailedHeaderDecryption;
#if !defined(RAR_NOCRYPT)
byte ArcSalt[SIZE_SALT50];
#endif
bool Splitting;
uint VolNumber;
int64 VolWrite;
// Total size of files adding to archive. Might also include the size of
// files repacked in solid archive.
uint64 AddingFilesSize;
uint64 AddingHeadersSize;
bool NewArchive;
std::wstring FirstVolumeName;
#ifdef PROPAGATE_MOTW
MarkOfTheWeb Motw;
#endif
};
#endif
+1553
View File
File diff suppressed because it is too large. Load diff
+179
View File
@@ -0,0 +1,179 @@
// Based on public domain code written in 2012 by Samuel Neves
#include "rar.hpp"
static const byte blake2s_sigma[10][16] =
{
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } ,
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } ,
{ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } ,
{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } ,
{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } ,
{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } ,
{ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } ,
{ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } ,
{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } ,
{ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } ,
};
#ifdef USE_SSE
#include "blake2s_sse.cpp"
#endif
static void blake2s_init_param( blake2s_state *S, uint32 node_offset, uint32 node_depth);
static void blake2s_update( blake2s_state *S, const byte *in, size_t inlen );
static void blake2s_final( blake2s_state *S, byte *digest );
#include "blake2sp.cpp"
static const uint32 blake2s_IV[8] =
{
0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL
};
static inline void blake2s_set_lastnode( blake2s_state *S )
{
S->f[1] = ~0U;
}
/* Some helper functions, not necessarily useful */
static inline void blake2s_set_lastblock( blake2s_state *S )
{
if( S->last_node ) blake2s_set_lastnode( S );
S->f[0] = ~0U;
}
static inline void blake2s_increment_counter( blake2s_state *S, const uint32 inc )
{
S->t[0] += inc;
S->t[1] += ( S->t[0] < inc );
}
/* init2 xors IV with input parameter block */
void blake2s_init_param( blake2s_state *S, uint32 node_offset, uint32 node_depth)
{
#ifdef USE_SSE
if (_SSE_Version>=SSE_SSE2)
blake2s_init_sse();
#endif
S->init(); // Clean data.
for( int i = 0; i < 8; ++i )
S->h[i] = blake2s_IV[i];
S->h[0] ^= 0x02080020; // We use BLAKE2sp parameters block.
S->h[2] ^= node_offset;
S->h[3] ^= (node_depth<<16)|0x20000000;
}
#define G(r,i,m,a,b,c,d) \
a = a + b + m[blake2s_sigma[r][2*i+0]]; \
d = rotr32(d ^ a, 16); \
c = c + d; \
b = rotr32(b ^ c, 12); \
a = a + b + m[blake2s_sigma[r][2*i+1]]; \
d = rotr32(d ^ a, 8); \
c = c + d; \
b = rotr32(b ^ c, 7);
static void blake2s_compress( blake2s_state *S, const byte block[BLAKE2S_BLOCKBYTES] )
{
uint32 m[16];
uint32 v[16];
for( size_t i = 0; i < 16; ++i )
m[i] = RawGet4( block + i * 4 );
for( size_t i = 0; i < 8; ++i )
v[i] = S->h[i];
v[ 8] = blake2s_IV[0];
v[ 9] = blake2s_IV[1];
v[10] = blake2s_IV[2];
v[11] = blake2s_IV[3];
v[12] = S->t[0] ^ blake2s_IV[4];
v[13] = S->t[1] ^ blake2s_IV[5];
v[14] = S->f[0] ^ blake2s_IV[6];
v[15] = S->f[1] ^ blake2s_IV[7];
for ( uint r = 0; r <= 9; ++r ) // No gain on i7 if unrolled, but exe size grows.
{
G(r,0,m,v[ 0],v[ 4],v[ 8],v[12]);
G(r,1,m,v[ 1],v[ 5],v[ 9],v[13]);
G(r,2,m,v[ 2],v[ 6],v[10],v[14]);
G(r,3,m,v[ 3],v[ 7],v[11],v[15]);
G(r,4,m,v[ 0],v[ 5],v[10],v[15]);
G(r,5,m,v[ 1],v[ 6],v[11],v[12]);
G(r,6,m,v[ 2],v[ 7],v[ 8],v[13]);
G(r,7,m,v[ 3],v[ 4],v[ 9],v[14]);
}
for( size_t i = 0; i < 8; ++i )
S->h[i] = S->h[i] ^ v[i] ^ v[i + 8];
}
void blake2s_update( blake2s_state *S, const byte *in, size_t inlen )
{
while( inlen > 0 )
{
size_t left = S->buflen;
size_t fill = 2 * BLAKE2S_BLOCKBYTES - left;
if( inlen > fill )
{
memcpy( S->buf + left, in, fill ); // Fill buffer
S->buflen += fill;
blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES );
#ifdef USE_SSE
if (_SSE_Version>=SSE_SSSE3)
blake2s_compress_sse( S, S->buf );
else
blake2s_compress( S, S->buf ); // Compress
#else
blake2s_compress( S, S->buf ); // Compress
#endif
memcpy( S->buf, S->buf + BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES ); // Shift buffer left
S->buflen -= BLAKE2S_BLOCKBYTES;
in += fill;
inlen -= fill;
}
else // inlen <= fill
{
memcpy( S->buf + left, in, (size_t)inlen );
S->buflen += (size_t)inlen; // Be lazy, do not compress
in += inlen;
inlen = 0;
}
}
}
void blake2s_final( blake2s_state *S, byte *digest )
{
if( S->buflen > BLAKE2S_BLOCKBYTES )
{
blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES );
blake2s_compress( S, S->buf );
S->buflen -= BLAKE2S_BLOCKBYTES;
memcpy( S->buf, S->buf + BLAKE2S_BLOCKBYTES, S->buflen );
}
blake2s_increment_counter( S, ( uint32 )S->buflen );
blake2s_set_lastblock( S );
memset( S->buf + S->buflen, 0, 2 * BLAKE2S_BLOCKBYTES - S->buflen ); /* Padding */
blake2s_compress( S, S->buf );
for( int i = 0; i < 8; ++i ) /* Output full hash */
RawPut4( S->h[i], digest + 4 * i );
}
+104
View File
@@ -0,0 +1,104 @@
// Based on public domain code written in 2012 by Samuel Neves
#ifndef _RAR_BLAKE2_
#define _RAR_BLAKE2_
#define BLAKE2_DIGEST_SIZE 32
#define BLAKE2_THREADS_NUMBER 8
// Use constexpr instead of enums for -std=c++20 compatibility.
constexpr size_t BLAKE2S_BLOCKBYTES = 64;
constexpr size_t BLAKE2S_OUTBYTES = 32;
// Alignment to 64 improves performance of both SSE and non-SSE versions.
// Alignment to n*16 is required for SSE version, so we selected 64.
// We use the custom alignment scheme instead of __declspec(align(x)),
// because it is less compiler dependent. Also the compiler directive
// does not help if structure is a member of class allocated through
// 'new' operator.
struct blake2s_state
{
// Use constexpr instead of enums, because otherwise clang -std=c++20
// issues a warning about "arithmetic between different enumeration types"
// in ubuf[BLAKE_DATA_SIZE + BLAKE_ALIGNMENT] declaration.
static constexpr size_t BLAKE_ALIGNMENT = 64;
// buffer and uint32 h[8], t[2], f[2];
// 2 * BLAKE2S_BLOCKBYTES is the buf size in blake2_code_20140114.zip.
// It might differ in later versions.
static constexpr size_t BLAKE_DATA_SIZE = 48 + 2 * BLAKE2S_BLOCKBYTES;
byte ubuf[BLAKE_DATA_SIZE + BLAKE_ALIGNMENT];
byte *buf; // byte buf[2 * BLAKE2S_BLOCKBYTES].
uint32 *h, *t, *f; // uint32 h[8], t[2], f[2].
size_t buflen;
byte last_node;
blake2s_state()
{
set_pointers();
}
// Required when we declare and assign in the same command.
blake2s_state(blake2s_state &st)
{
set_pointers();
*this=st;
}
void set_pointers()
{
// Set aligned pointers. Must be done in constructor, not in Init(),
// so assignments like 'blake2sp_state res=blake2ctx' work correctly
// even if blake2sp_init is not called for 'res'.
buf = (byte *) ALIGN_VALUE(ubuf, BLAKE_ALIGNMENT);
h = (uint32 *) (buf + 2 * BLAKE2S_BLOCKBYTES);
t = h + 8;
f = t + 2;
}
void init()
{
memset( ubuf, 0, sizeof( ubuf ) );
buflen = 0;
last_node = 0;
}
// Since we use pointers, the default = would work incorrectly.
blake2s_state& operator = (blake2s_state &st)
{
if (this != &st)
{
memcpy(buf, st.buf, BLAKE_DATA_SIZE);
buflen = st.buflen;
last_node = st.last_node;
}
return *this;
}
};
#ifdef RAR_SMP
class ThreadPool;
#endif
struct blake2sp_state
{
blake2s_state S[8];
blake2s_state R;
byte buf[8 * BLAKE2S_BLOCKBYTES];
size_t buflen;
#ifdef RAR_SMP
ThreadPool *ThPool;
uint MaxThreads;
#endif
};
void blake2sp_init( blake2sp_state *S );
void blake2sp_update( blake2sp_state *S, const byte *in, size_t inlen );
void blake2sp_final( blake2sp_state *S, byte *digest );
#endif
+113
View File
@@ -0,0 +1,113 @@
// Based on public domain code written in 2012 by Samuel Neves
// Initialization vector.
static __m128i blake2s_IV_0_3, blake2s_IV_4_7;
// Constants for cyclic rotation.
static __m128i crotr8, crotr16;
#ifdef __GNUC__
__attribute__((target("sse2")))
#endif
static void blake2s_init_sse()
{
// We cannot initialize these 128 bit variables in place when declaring
// them globally, because global scope initialization is performed before
// our SSE check and it would make code incompatible with older non-SSE2
// CPUs. Also we cannot initialize them as static inside of function
// using these variables, because SSE static initialization is not thread
// safe: first thread starts initialization and sets "init done" flag even
// if it is not done yet, second thread can attempt to access half-init
// SSE data. So we moved init code here.
blake2s_IV_0_3 = _mm_setr_epi32( 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A );
blake2s_IV_4_7 = _mm_setr_epi32( 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 );
crotr8 = _mm_set_epi8( 12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1 );
crotr16 = _mm_set_epi8( 13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2 );
}
#define LOAD(p) _mm_load_si128( (__m128i *)(p) )
#define STORE(p,r) _mm_store_si128((__m128i *)(p), r)
#define mm_rotr_epi32(r, c) ( \
c==8 ? _mm_shuffle_epi8(r,crotr8) \
: c==16 ? _mm_shuffle_epi8(r,crotr16) \
: _mm_xor_si128(_mm_srli_epi32( (r), c ),_mm_slli_epi32( (r), 32-c )) )
#define G1(row1,row2,row3,row4,buf) \
row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \
row4 = _mm_xor_si128( row4, row1 ); \
row4 = mm_rotr_epi32(row4, 16); \
row3 = _mm_add_epi32( row3, row4 ); \
row2 = _mm_xor_si128( row2, row3 ); \
row2 = mm_rotr_epi32(row2, 12);
#define G2(row1,row2,row3,row4,buf) \
row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \
row4 = _mm_xor_si128( row4, row1 ); \
row4 = mm_rotr_epi32(row4, 8); \
row3 = _mm_add_epi32( row3, row4 ); \
row2 = _mm_xor_si128( row2, row3 ); \
row2 = mm_rotr_epi32(row2, 7);
#define DIAGONALIZE(row1,row2,row3,row4) \
row4 = _mm_shuffle_epi32( row4, _MM_SHUFFLE(2,1,0,3) ); \
row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(1,0,3,2) ); \
row2 = _mm_shuffle_epi32( row2, _MM_SHUFFLE(0,3,2,1) );
#define UNDIAGONALIZE(row1,row2,row3,row4) \
row4 = _mm_shuffle_epi32( row4, _MM_SHUFFLE(0,3,2,1) ); \
row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(1,0,3,2) ); \
row2 = _mm_shuffle_epi32( row2, _MM_SHUFFLE(2,1,0,3) );
// Original BLAKE2 SSE4.1 message loading code was a little slower in x86 mode
// and about the same in x64 mode in our test. Perhaps depends on compiler.
// We also tried _mm_i32gather_epi32 and _mm256_i32gather_epi32 AVX2 gather
// instructions here, but they did not show any speed gain on i7-6700K.
#define SSE_ROUND(m,row,r) \
{ \
__m128i buf; \
buf=_mm_set_epi32(m[blake2s_sigma[r][6]],m[blake2s_sigma[r][4]],m[blake2s_sigma[r][2]],m[blake2s_sigma[r][0]]); \
G1(row[0],row[1],row[2],row[3],buf); \
buf=_mm_set_epi32(m[blake2s_sigma[r][7]],m[blake2s_sigma[r][5]],m[blake2s_sigma[r][3]],m[blake2s_sigma[r][1]]); \
G2(row[0],row[1],row[2],row[3],buf); \
DIAGONALIZE(row[0],row[1],row[2],row[3]); \
buf=_mm_set_epi32(m[blake2s_sigma[r][14]],m[blake2s_sigma[r][12]],m[blake2s_sigma[r][10]],m[blake2s_sigma[r][8]]); \
G1(row[0],row[1],row[2],row[3],buf); \
buf=_mm_set_epi32(m[blake2s_sigma[r][15]],m[blake2s_sigma[r][13]],m[blake2s_sigma[r][11]],m[blake2s_sigma[r][9]]); \
G2(row[0],row[1],row[2],row[3],buf); \
UNDIAGONALIZE(row[0],row[1],row[2],row[3]); \
}
#ifdef __GNUC__
__attribute__((target("ssse3")))
#endif
static int blake2s_compress_sse( blake2s_state *S, const byte block[BLAKE2S_BLOCKBYTES] )
{
__m128i row[4];
__m128i ff0, ff1;
const uint32 *m = ( uint32 * )block;
row[0] = ff0 = LOAD( &S->h[0] );
row[1] = ff1 = LOAD( &S->h[4] );
row[2] = blake2s_IV_0_3;
row[3] = _mm_xor_si128( blake2s_IV_4_7, LOAD( &S->t[0] ) );
SSE_ROUND( m, row, 0 );
SSE_ROUND( m, row, 1 );
SSE_ROUND( m, row, 2 );
SSE_ROUND( m, row, 3 );
SSE_ROUND( m, row, 4 );
SSE_ROUND( m, row, 5 );
SSE_ROUND( m, row, 6 );
SSE_ROUND( m, row, 7 );
SSE_ROUND( m, row, 8 );
SSE_ROUND( m, row, 9 );
STORE( &S->h[0], _mm_xor_si128( ff0, _mm_xor_si128( row[0], row[2] ) ) );
STORE( &S->h[4], _mm_xor_si128( ff1, _mm_xor_si128( row[1], row[3] ) ) );
return 0;
}
+155
View File
@@ -0,0 +1,155 @@
/*
BLAKE2 reference source code package - reference C implementations
Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
To the extent possible under law, the author(s) have dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.
You should have received a copy of the CC0 Public Domain Dedication along with
this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#define PARALLELISM_DEGREE 8
void blake2sp_init( blake2sp_state *S )
{
memset( S->buf, 0, sizeof( S->buf ) );
S->buflen = 0;
blake2s_init_param( &S->R, 0, 1 ); // Init root.
for( uint32 i = 0; i < PARALLELISM_DEGREE; ++i )
blake2s_init_param( &S->S[i], i, 0 ); // Init leaf.
S->R.last_node = 1;
S->S[PARALLELISM_DEGREE - 1].last_node = 1;
}
struct Blake2ThreadData
{
void Update();
blake2s_state *S;
const byte *in;
size_t inlen;
};
void Blake2ThreadData::Update()
{
size_t inlen__ = inlen;
const byte *in__ = ( const byte * )in;
while( inlen__ >= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES )
{
#ifdef USE_SSE
// We gain 5% in i7 SSE mode by prefetching next data block.
if (_SSE_Version>=SSE_SSE && inlen__ >= 2 * PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES)
_mm_prefetch((char*)(in__ + PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES), _MM_HINT_T0);
#endif
// We tried to _forceinline blake2s_update and blake2s_compress_sse,
// but it didn't improve performance.
blake2s_update( S, in__, BLAKE2S_BLOCKBYTES );
in__ += PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES;
inlen__ -= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES;
}
}
#ifdef RAR_SMP
THREAD_PROC(Blake2Thread)
{
Blake2ThreadData *td=(Blake2ThreadData *)Data;
td->Update();
}
#endif
void blake2sp_update( blake2sp_state *S, const byte *in, size_t inlen )
{
size_t left = S->buflen;
size_t fill = sizeof( S->buf ) - left;
if( left && inlen >= fill )
{
memcpy( S->buf + left, in, fill );
for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
blake2s_update( &S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES );
in += fill;
inlen -= fill;
left = 0;
}
Blake2ThreadData btd_array[PARALLELISM_DEGREE];
#ifdef RAR_SMP
uint ThreadNumber = inlen < 0x1000 ? 1 : S->MaxThreads;
if (ThreadNumber==6 || ThreadNumber==7) // 6 and 7 threads work slower than 4 here.
ThreadNumber=4;
#else
uint ThreadNumber=1;
#endif
for (size_t id__=0;id__<PARALLELISM_DEGREE;)
{
for (uint Thread=0;Thread<ThreadNumber && id__<PARALLELISM_DEGREE;Thread++)
{
Blake2ThreadData *btd=btd_array+Thread;
btd->inlen = inlen;
btd->in = in + id__ * BLAKE2S_BLOCKBYTES;
btd->S = &S->S[id__];
#ifdef RAR_SMP
if (ThreadNumber>1)
S->ThPool->AddTask(Blake2Thread,(void*)btd);
else
btd->Update();
#else
btd->Update();
#endif
id__++;
}
#ifdef RAR_SMP
if (S->ThPool!=NULL) // Can be NULL in -mt1 mode.
S->ThPool->WaitDone();
#endif // RAR_SMP
}
in += inlen - inlen % ( PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES );
inlen %= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES;
if( inlen > 0 )
memcpy( S->buf + left, in, (size_t)inlen );
S->buflen = left + (size_t)inlen;
}
void blake2sp_final( blake2sp_state *S, byte *digest )
{
byte hash[PARALLELISM_DEGREE][BLAKE2S_OUTBYTES];
for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
{
if( S->buflen > i * BLAKE2S_BLOCKBYTES )
{
size_t left = S->buflen - i * BLAKE2S_BLOCKBYTES;
if( left > BLAKE2S_BLOCKBYTES ) left = BLAKE2S_BLOCKBYTES;
blake2s_update( &S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, left );
}
blake2s_final( &S->S[i], hash[i] );
}
for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
blake2s_update( &S->R, hash[i], BLAKE2S_OUTBYTES );
blake2s_final( &S->R, digest );
}
+1330
View File
File diff suppressed because it is too large. Load diff
+103
View File
@@ -0,0 +1,103 @@
#ifndef _RAR_CMDDATA_
#define _RAR_CMDDATA_
#if defined(_WIN_ALL) && !defined(SFX_MODULE)
// In Windows we implement our own command line parser to avoid replacing
// \" by " in standard parser. Such replacing corrupts destination paths
// like "dest path\" in extraction commands.
#define CUSTOM_CMDLINE_PARSER
#endif
#define DefaultStoreList L"7z;arj;bz2;cab;gz;jpeg;jpg;lha;lz;lzh;mp3;rar;taz;tbz;tbz2;tgz;txz;xz;z;zip;zipx;zst;tzst"
enum RAR_CMD_LIST_MODE {RCLM_AUTO,RCLM_REJECT_LISTS,RCLM_ACCEPT_LISTS};
enum IS_PROCESS_FILE_FLAGS {IPFF_EXCLUDE_PARENT=1};
class CommandData:public RAROptions
{
private:
void ProcessSwitch(const wchar *Switch);
void BadSwitch(const wchar *Switch);
uint GetExclAttr(const wchar *Str,bool &Dir);
#if !defined(SFX_MODULE)
void SetTimeFilters(const wchar *Mod,bool Before,bool Age);
void SetStoreTimeMode(const wchar *S);
#endif
int64 GetModSize(const wchar *S,uint DefMultiplier);
bool FileLists;
bool NoMoreSwitches;
RAR_CMD_LIST_MODE ListMode;
bool BareOutput;
public:
CommandData();
void Init();
void ParseCommandLine(bool Preprocess,int argc, char *argv[]);
void ParseArg(const wchar *ArgW);
void ParseDone();
void ParseEnvVar();
void ReadConfig();
void PreprocessArg(const wchar *Arg);
void ProcessSwitchesString(const std::wstring &Str);
void OutTitle();
void OutHelp(RAR_EXIT ExitCode);
bool IsSwitch(int Ch);
bool ExclCheck(const std::wstring &CheckName,bool Dir,bool CheckFullPath,bool CheckInclList);
static bool CheckArgs(StringList *Args,bool Dir,const std::wstring &CheckName,bool CheckFullPath,int MatchMode);
bool ExclDirByAttr(uint FileAttr);
bool TimeCheck(RarTime &ftm,RarTime &ftc,RarTime &fta);
bool SizeCheck(int64 Size);
bool AnyFiltersActive();
int IsProcessFile(FileHeader &FileHead,bool *ExactMatch,int MatchType,
bool Flags,std::wstring *MatchedArg);
void ProcessCommand();
void AddArcName(const std::wstring &Name);
bool GetArcName(wchar *Name,int MaxSize);
bool GetArcName(std::wstring &Name);
#ifndef SFX_MODULE
void ReportWrongSwitches(RARFORMAT Format);
#endif
void GetBriefMaskList(const std::wstring &Masks,StringList &Args);
std::wstring Command;
std::wstring ArcName;
std::wstring ExtrPath;
std::wstring TempPath;
std::wstring SFXModule;
std::wstring CommentFile;
std::wstring ArcPath; // For -ap<path>.
std::wstring ExclArcPath; // For -ep4<path> switch.
std::wstring LogName;
std::wstring EmailTo;
// Read data from stdin and store in archive under a name specified here
// when archiving. Read an archive from stdin if any non-empty string
// is specified here when extracting.
std::wstring UseStdin;
StringList FileArgs;
StringList ExclArgs;
StringList InclArgs;
StringList ArcNames;
StringList StoreArgs;
#ifdef PROPAGATE_MOTW
StringList MotwList; // Extensions to assign the mark of the web.
#endif
SecPassword Password;
std::vector<int64> NextVolSizes;
#ifdef RARDLL
std::wstring DllDestName;
#endif
};
#endif
+380
View File
@@ -0,0 +1,380 @@
// Return 'true' if we need to exclude the file from processing as result
// of -x switch. If CheckInclList is true, we also check the file against
// the include list created with -n switch.
bool CommandData::ExclCheck(const std::wstring &CheckName,bool Dir,bool CheckFullPath,bool CheckInclList)
{
if (CheckArgs(&ExclArgs,Dir,CheckName,CheckFullPath,MATCH_WILDSUBPATH))
return true;
if (!CheckInclList || InclArgs.ItemsCount()==0)
return false;
if (CheckArgs(&InclArgs,Dir,CheckName,CheckFullPath,MATCH_WILDSUBPATH))
return false;
return true;
}
bool CommandData::CheckArgs(StringList *Args,bool Dir,const std::wstring &CheckName,bool CheckFullPath,int MatchMode)
{
std::wstring Name,FullName,CurMask;
ConvertPath(&CheckName,&Name);
Args->Rewind();
while (Args->GetString(CurMask))
{
#ifdef _WIN_ALL
// 2025.09.11: Unix allows DOS slashes as a part of file name, so we do not
// convert it for Unix. In Windows we wish -xdir\file and -xdir/file both
// to exclude the file.
UnixSlashToDos(CurMask,CurMask);
#endif
wchar LastMaskChar=GetLastChar(CurMask);
bool DirMask=IsPathDiv(LastMaskChar); // Mask for directories only.
if (Dir)
{
// CheckName is a directory.
if (DirMask)
{
// We process the directory and have the directory exclusion mask.
// So let's convert "mask\" to "mask" and process it normally.
CurMask.pop_back();
}
else
{
// This code doesn't allow to apply -n and -x wildcard masks without
// trailing slash to folders unless these masks are * and *.*.
// See the changes history below.
// 2023.03.26: Previously we removed this code completely to let
// 'rar a arc dir -ndir\path\*' include empty folders in 'path' too.
// But then we received an email from user not willing -x*.avi to
// exclude folders like dir.avi with non-avi files. Also rar.txt
// mentions that masks like *.avi exclude only files. Initially
// we wanted masks like -npath\* or -xpath\* to match the entire
// contents of path including empty folders and added the special
// check for "*" and "*.*". But this is not very straightforward,
// when *.* and *.avi are processed differently, especially taking
// into account that we can specify the exact folder name without
// wildcards to process it and masks like 'dir*\' can be used to
// exclude folders. So we decided to skip all usual wildcard masks
// for folders.
// 2023.11.22: We returned the special check for "*" and "*.*",
// because users expected 'rar a arc dir -xdir\*' to exclude
// everything including subfolders in 'dir'. For now we returned it
// both for -n and -x, but we can limit it to -x only if needed.
std::wstring Name=PointToName(CurMask);
if (IsWildcard(Name) && Name!=L"*" && Name!=L"*.*")
continue;
}
}
else
{
// If we process a file inside of directory excluded by "dirmask\".
// we want to exclude such file too. So we convert "dirmask\" to
// "dirmask\*". It is important for operations other than archiving
// with -x. When archiving with -x, directory matched by "dirmask\"
// is excluded from further scanning.
if (DirMask)
CurMask+=L"*";
}
#ifndef SFX_MODULE
if (CheckFullPath && IsFullPath(CurMask))
{
// We do not need to do the special "*\" processing here, because
// unlike the "else" part of this "if", now we convert names to full
// format, so they all include the path, which is matched by "*\"
// correctly. Moreover, removing "*\" from mask would break
// the comparison, because now all names have the path.
if (FullName.empty())
ConvertNameToFull(CheckName,FullName);
if (CmpName(CurMask,FullName,MatchMode))
return true;
}
else
#endif
{
std::wstring CurName=Name;
// Important to convert before "*\" check below, so masks like
// d:*\something are processed properly.
size_t MaskOffset=ConvertPath(&CurMask,nullptr);
std::wstring CmpMask=CurMask.substr(MaskOffset);
if (CmpMask[0]=='*' && IsPathDiv(CmpMask[1]))
{
// We want "*\name" to match 'name' not only in subdirectories,
// but also in the current directory. We convert the name
// from 'name' to '.\name' to be matched by "*\" part even if it is
// in current directory.
CurName=L'.';
CurName+=CPATHDIVIDER;
CurName+=Name;
}
if (CmpName(CmpMask,CurName,MatchMode))
return true;
}
}
return false;
}
#ifndef SFX_MODULE
// Now this function performs only one task and only in Windows version:
// it skips symlinks to directories if -e1024 switch is specified.
// Symlinks are skipped in ScanTree class, so their entire contents
// is skipped too. Without this function we would check the attribute
// only directly before archiving, so we would skip the symlink record,
// but not the contents of symlinked directory.
bool CommandData::ExclDirByAttr(uint FileAttr)
{
#ifdef _WIN_ALL
if ((FileAttr & FILE_ATTRIBUTE_REPARSE_POINT)!=0 &&
(ExclFileAttr & FILE_ATTRIBUTE_REPARSE_POINT)!=0)
return true;
#endif
return false;
}
#endif
#if !defined(SFX_MODULE)
void CommandData::SetTimeFilters(const wchar *Mod,bool Before,bool Age)
{
bool ModeOR=false,TimeMods=false;
const wchar *S=Mod;
// Check if any 'mca' modifiers are present, set OR mode if 'o' is present,
// skip modifiers and set S to beginning of time string. Be sure to check
// *S!=0, because termination 0 is a part of string for wcschr.
for (;*S!=0 && wcschr(L"MCAOmcao",*S)!=NULL;S++)
if (*S=='o' || *S=='O')
ModeOR=true;
else
TimeMods=true;
if (!TimeMods) // Assume 'm' if no modifiers are specified.
Mod=L"m";
// Set the specified time for every modifier. Be sure to check *Mod!=0,
// because termination 0 is a part of string for wcschr. This check is
// important when we set Mod to "m" above.
for (;*Mod!=0 && wcschr(L"MCAOmcao",*Mod)!=NULL;Mod++)
switch(toupperw(*Mod))
{
case 'M':
if (Before)
{
Age ? FileMtimeBefore.SetAgeText(S):FileMtimeBefore.SetIsoText(S);
FileMtimeBeforeOR=ModeOR;
}
else
{
Age ? FileMtimeAfter.SetAgeText(S):FileMtimeAfter.SetIsoText(S);
FileMtimeAfterOR=ModeOR;
}
break;
case 'C':
if (Before)
{
Age ? FileCtimeBefore.SetAgeText(S):FileCtimeBefore.SetIsoText(S);
FileCtimeBeforeOR=ModeOR;
}
else
{
Age ? FileCtimeAfter.SetAgeText(S):FileCtimeAfter.SetIsoText(S);
FileCtimeAfterOR=ModeOR;
}
break;
case 'A':
if (Before)
{
Age ? FileAtimeBefore.SetAgeText(S):FileAtimeBefore.SetIsoText(S);
FileAtimeBeforeOR=ModeOR;
}
else
{
Age ? FileAtimeAfter.SetAgeText(S):FileAtimeAfter.SetIsoText(S);
FileAtimeAfterOR=ModeOR;
}
break;
}
}
#endif
#ifndef SFX_MODULE
// Return 'true' if we need to exclude the file from processing.
bool CommandData::TimeCheck(RarTime &ftm,RarTime &ftc,RarTime &fta)
{
bool FilterOR=false;
if (FileMtimeBefore.IsSet()) // Filter present.
if (ftm>=FileMtimeBefore) // Condition not matched.
if (FileMtimeBeforeOR)
FilterOR=true; // Not matched OR filter is present.
else
return true; // Exclude file in AND mode.
else // Condition matched.
if (FileMtimeBeforeOR)
return false; // Include file in OR mode.
if (FileMtimeAfter.IsSet()) // Filter present.
if (ftm<FileMtimeAfter) // Condition not matched.
if (FileMtimeAfterOR)
FilterOR=true; // Not matched OR filter is present.
else
return true; // Exclude file in AND mode.
else // Condition matched.
if (FileMtimeAfterOR)
return false; // Include file in OR mode.
if (FileCtimeBefore.IsSet()) // Filter present.
if (ftc>=FileCtimeBefore) // Condition not matched.
if (FileCtimeBeforeOR)
FilterOR=true; // Not matched OR filter is present.
else
return true; // Exclude file in AND mode.
else // Condition matched.
if (FileCtimeBeforeOR)
return false; // Include file in OR mode.
if (FileCtimeAfter.IsSet()) // Filter present.
if (ftc<FileCtimeAfter) // Condition not matched.
if (FileCtimeAfterOR)
FilterOR=true; // Not matched OR filter is present.
else
return true; // Exclude file in AND mode.
else // Condition matched.
if (FileCtimeAfterOR)
return false; // Include file in OR mode.
if (FileAtimeBefore.IsSet()) // Filter present.
if (fta>=FileAtimeBefore) // Condition not matched.
if (FileAtimeBeforeOR)
FilterOR=true; // Not matched OR filter is present.
else
return true; // Exclude file in AND mode.
else // Condition matched.
if (FileAtimeBeforeOR)
return false; // Include file in OR mode.
if (FileAtimeAfter.IsSet()) // Filter present.
if (fta<FileAtimeAfter) // Condition not matched.
if (FileAtimeAfterOR)
FilterOR=true; // Not matched OR filter is present.
else
return true; // Exclude file in AND mode.
else // Condition matched.
if (FileAtimeAfterOR)
return false; // Include file in OR mode.
return FilterOR; // Exclude if all OR filters are not matched.
}
#endif
#ifndef SFX_MODULE
// Return 'true' if we need to exclude the file from processing.
bool CommandData::SizeCheck(int64 Size)
{
if (Size==INT64NDF) // If called from archive formats like bzip2, not storing the file size.
return false;
if (FileSizeLess!=INT64NDF && Size>=FileSizeLess)
return true;
if (FileSizeMore!=INT64NDF && Size<=FileSizeMore)
return true;
return false;
}
#endif
// Return 0 if file must not be processed or a number of matched parameter otherwise.
int CommandData::IsProcessFile(FileHeader &FileHead,bool *ExactMatch,int MatchType,
bool Flags,std::wstring *MatchedArg)
{
if (MatchedArg!=NULL)
MatchedArg->clear();
bool Dir=FileHead.Dir;
if (ExclCheck(FileHead.FileName,Dir,false,true))
return 0;
#ifndef SFX_MODULE
if (TimeCheck(FileHead.mtime,FileHead.ctime,FileHead.atime))
return 0;
if ((FileHead.FileAttr & ExclFileAttr)!=0 || FileHead.Dir && ExclDir)
return 0;
if (InclAttrSet && (FileHead.FileAttr & InclFileAttr)==0 &&
(!FileHead.Dir || !InclDir))
return 0;
if (!Dir && SizeCheck(FileHead.UnpSize))
return 0;
#endif
std::wstring ArgName;
FileArgs.Rewind();
for (int StringCount=1;FileArgs.GetString(ArgName);StringCount++)
{
// Ensure that both parameters of CmpName are either C++ strings or
// pointers, so we avoid time consuming string construction for one of
// parameters in this expensive loop.
if (CmpName(ArgName,FileHead.FileName,MatchType))
{
if (ExactMatch!=NULL)
*ExactMatch=wcsicompc(ArgName,FileHead.FileName)==0;
if (MatchedArg!=NULL)
*MatchedArg=ArgName;
return StringCount;
}
}
return 0;
}
#if !defined(SFX_MODULE)
void CommandData::SetStoreTimeMode(const wchar *S)
{
if (*S==0 || IsDigit(*S) || *S=='-' || *S=='+')
{
// Apply -ts, -ts1, -ts-, -ts+ to all 3 times.
// Handle obsolete -ts[2,3,4] as ts+.
EXTTIME_MODE Mode=EXTTIME_MAX;
if (*S=='-')
Mode=EXTTIME_NONE;
if (*S=='1')
Mode=EXTTIME_1S;
xmtime=xctime=xatime=Mode;
S++;
}
while (*S!=0)
{
EXTTIME_MODE Mode=EXTTIME_MAX;
if (S[1]=='-')
Mode=EXTTIME_NONE;
if (S[1]=='1')
Mode=EXTTIME_1S;
switch(toupperw(*S))
{
case 'M':
xmtime=Mode;
break;
case 'C':
xctime=Mode;
break;
case 'A':
xatime=Mode;
break;
case 'P':
PreserveAtime=true;
break;
}
S++;
}
}
#endif
+125
View File
@@ -0,0 +1,125 @@
void CommandData::OutTitle()
{
if (BareOutput || DisableCopyright)
return;
#if defined(__GNUC__) && defined(SFX_MODULE)
mprintf(St(MCopyrightS));
#else
#ifndef SILENT
static bool TitleShown=false;
if (TitleShown)
return;
TitleShown=true;
wchar Version[80];
if (RARVER_BETA!=0)
swprintf(Version,ASIZE(Version),L"%d.%02d %ls %d",RARVER_MAJOR,RARVER_MINOR,St(MBeta),RARVER_BETA);
else
swprintf(Version,ASIZE(Version),L"%d.%02d",RARVER_MAJOR,RARVER_MINOR);
#if defined(_WIN_32) || defined(_WIN_64)
wcsncatz(Version,L" ",ASIZE(Version));
#endif
#ifdef _WIN_32
wcsncatz(Version,St(Mx86),ASIZE(Version));
#endif
#ifdef _WIN_64
wcsncatz(Version,St(Mx64),ASIZE(Version));
#endif
if (PrintVersion)
{
mprintf(L"%s",Version);
exit(0);
}
mprintf(St(MUCopyright),Version,RARVER_YEAR);
#endif
#endif
}
inline bool CmpMSGID(MSGID i1,MSGID i2)
{
#ifdef MSGID_INT
return i1==i2;
#else
// If MSGID is const char*, we cannot compare pointers only.
// Pointers to different instances of same string can differ,
// so we need to compare complete strings.
return wcscmp(i1,i2)==0;
#endif
}
void CommandData::OutHelp(RAR_EXIT ExitCode)
{
#if !defined(SILENT)
OutTitle();
static MSGID Help[]={
#ifdef SFX_MODULE
// Console SFX switches definition.
MCHelpCmd,MSHelpCmdE,MSHelpCmdT,MSHelpCmdV
#else
// UnRAR switches definition.
MUNRARTitle1,MRARTitle2,MCHelpCmd,MCHelpCmdE,MCHelpCmdL,
MCHelpCmdP,MCHelpCmdT,MCHelpCmdV,MCHelpCmdX,MCHelpSw,MCHelpSwm,
MCHelpSwAT,MCHelpSwAC,MCHelpSwAD,MCHelpSwAG,MCHelpSwAI,MCHelpSwAP,
MCHelpSwCm,MCHelpSwCFGm,MCHelpSwCL,MCHelpSwCU,MCHelpSwDH,MCHelpSwEP,
MCHelpSwEP3,MCHelpSwEP4,MCHelpSwF,MCHelpSwIDP,MCHelpSwIERR,
MCHelpSwINUL,MCHelpSwIOFF,MCHelpSwKB,MCHelpSwME,MCHelpSwMLP,
MCHelpSwN,MCHelpSwNa,MCHelpSwNal,MCHelpSwO,MCHelpSwOC,MCHelpSwOL,
MCHelpSwOM,MCHelpSwOP,MCHelpSwOR,MCHelpSwOW,MCHelpSwP,MCHelpSwR,
MCHelpSwRI,MCHelpSwSC,MCHelpSwSI,MCHelpSwSL,MCHelpSwTA,
MCHelpSwTB,MCHelpSwTN,MCHelpSwTO,MCHelpSwTS,MCHelpSwU,MCHelpSwVUnr,
MCHelpSwVER,MCHelpSwVP,MCHelpSwX,MCHelpSwXa,MCHelpSwXal,MCHelpSwY
#endif
};
for (uint I=0;I<ASIZE(Help);I++)
{
#ifndef SFX_MODULE
if (CmpMSGID(Help[I],MCHelpSwV))
continue;
#ifndef _WIN_ALL
static MSGID Win32Only[]={
MCHelpSwIEML,MCHelpSwVD,MCHelpSwAO,MCHelpSwOS,MCHelpSwIOFF,
MCHelpSwEP2,MCHelpSwMLP,MCHelpSwOC,MCHelpSwONI,MCHelpSwDR,MCHelpSwRI
};
bool Found=false;
for (uint J=0;J<ASIZE(Win32Only);J++)
if (CmpMSGID(Help[I],Win32Only[J]))
{
Found=true;
break;
}
if (Found)
continue;
#endif
#ifdef _UNIX
if (CmpMSGID(Help[I],MRARTitle2))
{
mprintf(St(MFwrSlTitle2));
continue;
}
#endif
#if !defined(_UNIX) && !defined(_WIN_ALL)
if (CmpMSGID(Help[I],MCHelpSwOW))
continue;
#endif
#ifndef _WIN_ALL
if (CmpMSGID(Help[I],MCHelpSwAC))
continue;
#endif
#ifndef SAVE_LINKS
if (CmpMSGID(Help[I],MCHelpSwOL))
continue;
#endif
#ifndef RAR_SMP
if (CmpMSGID(Help[I],MCHelpSwMT))
continue;
#endif
#endif
mprintf(St(Help[I]));
}
mprintf(L"\n");
ErrHandler.Exit(ExitCode);
#endif
}
+48
View File
@@ -0,0 +1,48 @@
inline byte RangeCoder::GetChar()
{
return UnpackRead->GetChar();
}
void RangeCoder::InitDecoder(Unpack *UnpackRead)
{
RangeCoder::UnpackRead=UnpackRead;
low=code=0;
range=0xffffffff;
for (uint i = 0; i < 4; i++)
code=(code << 8) | GetChar();
}
// (int) cast before "low" added only to suppress compiler warnings.
#define ARI_DEC_NORMALIZE(code,low,range,read) \
{ \
while ((low^(low+range))<TOP || range<BOT && ((range=-(int)low&(BOT-1)),1)) \
{ \
code=(code << 8) | read->GetChar(); \
range <<= 8; \
low <<= 8; \
} \
}
inline int RangeCoder::GetCurrentCount()
{
return (code-low)/(range /= SubRange.scale);
}
inline uint RangeCoder::GetCurrentShiftCount(uint SHIFT)
{
return (code-low)/(range >>= SHIFT);
}
inline void RangeCoder::Decode()
{
low += range*SubRange.LowCount;
range *= SubRange.HighCount-SubRange.LowCount;
}
+23
View File
@@ -0,0 +1,23 @@
/****************************************************************************
* Contents: 'Carryless rangecoder' by Dmitry Subbotin *
****************************************************************************/
class RangeCoder
{
public:
void InitDecoder(Unpack *UnpackRead);
inline int GetCurrentCount();
inline uint GetCurrentShiftCount(uint SHIFT);
inline void Decode();
inline void PutChar(unsigned int c);
inline byte GetChar();
uint low, code, range;
struct SUBRANGE
{
uint LowCount, HighCount, scale;
} SubRange;
Unpack *UnpackRead;
};
+61
View File
@@ -0,0 +1,61 @@
#ifndef _RAR_COMPRESS_
#define _RAR_COMPRESS_
// Combine pack and unpack constants to class to avoid polluting global
// namespace with numerous short names.
class PackDef
{
public:
// Maximum LZ match length we can encode even for short distances.
static const uint MAX_LZ_MATCH = 0x1001;
// We increment LZ match length for longer distances, because shortest
// matches are not allowed for them. Maximum length increment is 3
// for distances larger than 256KB (0x40000). Here we define the maximum
// incremented LZ match. Normally packer does not use it, but we must be
// ready to process it in corrupt archives.
static const uint MAX_INC_LZ_MATCH = MAX_LZ_MATCH + 3;
static const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3.
static const uint MAX3_INC_LZ_MATCH = MAX3_LZ_MATCH + 3;
static const uint LOW_DIST_REP_COUNT = 16;
static const uint NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */
static const uint DCB = 64; // Base distance codes up to 4 GB.
static const uint DCX = 80; // Extended distance codes up to 1 TB.
static const uint LDC = 16;
static const uint RC = 44;
static const uint HUFF_TABLE_SIZEB = NC + DCB + RC + LDC;
static const uint HUFF_TABLE_SIZEX = NC + DCX + RC + LDC;
static const uint BC = 20;
static const uint NC30 = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */
static const uint DC30 = 60;
static const uint LDC30 = 17;
static const uint RC30 = 28;
static const uint BC30 = 20;
static const uint HUFF_TABLE_SIZE30 = NC30 + DC30 + RC30 + LDC30;
static const uint NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */
static const uint DC20 = 48;
static const uint RC20 = 28;
static const uint BC20 = 19;
static const uint MC20 = 257;
// Largest alphabet size among all values listed above.
static const uint LARGEST_TABLE_SIZE = 306;
};
enum FilterType {
// These values must not be changed, because we use them directly
// in RAR5 compression and decompression code.
FILTER_DELTA=0, FILTER_E8, FILTER_E8E9, FILTER_ARM,
FILTER_AUDIO, FILTER_RGB, FILTER_ITANIUM, FILTER_TEXT,
// These values can be changed.
FILTER_LONGRANGE,FILTER_EXHAUSTIVE,FILTER_NONE
};
#endif
+416
View File
@@ -0,0 +1,416 @@
#include "rar.hpp"
#include "log.cpp"
static MESSAGE_TYPE MsgStream=MSG_STDOUT;
static RAR_CHARSET RedirectCharset=RCH_DEFAULT;
static bool ProhibitInput=false;
static bool ConsoleOutputPresent=false;
static bool StdoutRedirected=false,StderrRedirected=false,StdinRedirected=false;
#ifdef _WIN_ALL
static bool IsRedirected(DWORD nStdHandle)
{
HANDLE hStd=GetStdHandle(nStdHandle);
DWORD Mode;
return GetFileType(hStd)!=FILE_TYPE_CHAR || GetConsoleMode(hStd,&Mode)==0;
}
#endif
void InitConsole()
{
#ifdef _WIN_ALL
// We want messages like file names or progress percent to be printed
// immediately. Use only in Windows, in Unix they can cause wprintf %ls
// to fail with non-English strings.
setbuf(stdout,NULL);
setbuf(stderr,NULL);
// Detect if output is redirected and set output mode properly.
// We do not want to send Unicode output to files and especially to pipes
// like '|more', which cannot handle them correctly in Windows.
// In Unix console output is UTF-8 and it is handled correctly
// when redirecting, so no need to perform any adjustments.
StdoutRedirected=IsRedirected(STD_OUTPUT_HANDLE);
StderrRedirected=IsRedirected(STD_ERROR_HANDLE);
StdinRedirected=IsRedirected(STD_INPUT_HANDLE);
#ifdef _MSC_VER
if (!StdoutRedirected)
_setmode(_fileno(stdout), _O_U16TEXT);
if (!StderrRedirected)
_setmode(_fileno(stderr), _O_U16TEXT);
#endif
#elif defined(_UNIX)
StdoutRedirected=!isatty(fileno(stdout));
StderrRedirected=!isatty(fileno(stderr));
StdinRedirected=!isatty(fileno(stdin));
#endif
}
void SetConsoleMsgStream(MESSAGE_TYPE MsgStream)
{
::MsgStream=MsgStream;
}
void SetConsoleRedirectCharset(RAR_CHARSET RedirectCharset)
{
::RedirectCharset=RedirectCharset;
}
void ProhibitConsoleInput()
{
ProhibitInput=true;
}
#ifndef SILENT
static void cvt_wprintf(FILE *dest,const wchar *fmt,va_list arglist)
{
ConsoleOutputPresent=true;
// No need for PrintfPrepareFmt here, vwstrprintf calls it.
std::wstring s=vwstrprintf(fmt,arglist);
ReplaceEsc(s);
#ifdef _WIN_ALL
if (dest==stdout && StdoutRedirected || dest==stderr && StderrRedirected)
{
HANDLE hOut=GetStdHandle(dest==stdout ? STD_OUTPUT_HANDLE:STD_ERROR_HANDLE);
DWORD Written;
if (RedirectCharset==RCH_UNICODE)
WriteFile(hOut,s.data(),(DWORD)s.size()*sizeof(s[0]),&Written,NULL);
else
{
// Avoid Unicode for redirect in Windows, it does not work with pipes.
std::string MsgA;
if (RedirectCharset==RCH_UTF8)
WideToUtf(s,MsgA);
else
WideToChar(s,MsgA);
if (RedirectCharset==RCH_DEFAULT || RedirectCharset==RCH_OEM)
CharToOemA(&MsgA[0],&MsgA[0]); // Console tools like 'more' expect OEM encoding.
// We already converted \n to \r\n above, so we use WriteFile instead
// of C library to avoid unnecessary additional conversion.
WriteFile(hOut,MsgA.data(),(DWORD)MsgA.size(),&Written,NULL);
}
return;
}
// MSVC2008 vfwprintf writes every character to console separately
// and it is too slow. We use direct WriteConsole call instead.
HANDLE hOut=GetStdHandle(dest==stderr ? STD_ERROR_HANDLE:STD_OUTPUT_HANDLE);
DWORD Written;
WriteConsole(hOut,s.data(),(DWORD)s.size(),&Written,NULL);
#else
fputws(s.c_str(),dest);
// We do not use setbuf(NULL) in Unix (see comments in InitConsole).
fflush(dest);
#endif
}
void mprintf(const wchar *fmt,...)
{
if (MsgStream==MSG_NULL || MsgStream==MSG_ERRONLY)
return;
fflush(stderr); // Ensure proper message order.
va_list arglist;
va_start(arglist,fmt);
FILE *dest=MsgStream==MSG_STDERR ? stderr:stdout;
cvt_wprintf(dest,fmt,arglist);
va_end(arglist);
}
#endif
#ifndef SILENT
void eprintf(const wchar *fmt,...)
{
if (MsgStream==MSG_NULL)
return;
fflush(stdout); // Ensure proper message order.
va_list arglist;
va_start(arglist,fmt);
cvt_wprintf(stderr,fmt,arglist);
va_end(arglist);
}
#endif
#ifndef SILENT
static void QuitIfInputProhibited()
{
// We cannot handle user prompts if -si is used to read file or archive data
// from stdin.
if (ProhibitInput)
{
mprintf(St(MStdinNoInput));
ErrHandler.Exit(RARX_FATAL);
}
}
static void GetPasswordText(std::wstring &Str)
{
QuitIfInputProhibited();
if (StdinRedirected)
getwstr(Str); // Read from pipe or redirected file.
else
{
#ifdef _WIN_ALL
HANDLE hConIn=GetStdHandle(STD_INPUT_HANDLE);
DWORD ConInMode;
GetConsoleMode(hConIn,&ConInMode);
SetConsoleMode(hConIn,ENABLE_LINE_INPUT); // Remove ENABLE_ECHO_INPUT.
std::vector<wchar> Buf(MAXPASSWORD);
// We prefer ReadConsole to ReadFile, so we can read Unicode input.
DWORD Read=0;
ReadConsole(hConIn,Buf.data(),(DWORD)Buf.size()-1,&Read,NULL);
Buf[Read]=0;
Str=Buf.data();
cleandata(Buf.data(),Buf.size()*sizeof(Buf[0]));
SetConsoleMode(hConIn,ConInMode);
// 2023.03.12: Previously we checked for presence of "\n" in entered
// passwords, supposing that truncated strings do not include it.
// We did it to read the rest of excessively long string, so it is not
// read later as the second password for -p switch. But this "\n" check
// doesn't seem to work in Windows 10 anymore and "\r" is present even
// in truncated strings. Also we increased MAXPASSWORD, so it is larger
// than MAXPASSWORD_RAR. Thus we removed this check as not working
// and not that necessary. Low level FlushConsoleInputBuffer doesn't help
// for high level ReadConsole, which in line input mode seems to store
// the rest of string in its own internal buffer.
#else
std::vector<char> StrA(MAXPASSWORD*4); // "*4" for multibyte UTF-8 characters.
#ifdef __VMS
fgets(StrA.data(),StrA.size()-1,stdin);
#elif defined(__sun)
strncpyz(StrA.data(),getpassphrase(""),StrA.size());
#else
strncpyz(StrA.data(),getpass(""),StrA.size());
#endif
CharToWide(StrA.data(),Str);
cleandata(StrA.data(),StrA.size()*sizeof(StrA[0]));
#endif
}
RemoveLF(Str);
}
#endif
#ifndef SILENT
bool GetConsolePassword(UIPASSWORD_TYPE Type,const std::wstring &FileName,SecPassword *Password)
{
if (!StdinRedirected)
uiAlarm(UIALARM_QUESTION);
while (true)
{
// if (!StdinRedirected)
if (Type==UIPASSWORD_GLOBAL)
eprintf(L"\n%s: ",St(MAskPsw));
else
eprintf(St(MAskPswFor),FileName.c_str());
std::wstring PlainPsw;
GetPasswordText(PlainPsw);
if (PlainPsw.empty() && Type==UIPASSWORD_GLOBAL)
return false;
if (PlainPsw.size()>=MAXPASSWORD)
{
PlainPsw.erase(MAXPASSWORD-1);
uiMsg(UIERROR_TRUNCPSW,MAXPASSWORD-1);
}
if (!StdinRedirected && Type==UIPASSWORD_GLOBAL)
{
eprintf(St(MReAskPsw));
std::wstring CmpStr;
GetPasswordText(CmpStr);
if (CmpStr.empty() || PlainPsw!=CmpStr)
{
eprintf(St(MNotMatchPsw));
cleandata(&PlainPsw[0],PlainPsw.size()*sizeof(PlainPsw[0]));
cleandata(&CmpStr[0],CmpStr.size()*sizeof(CmpStr[0]));
continue;
}
cleandata(&CmpStr[0],CmpStr.size()*sizeof(CmpStr[0]));
}
Password->Set(PlainPsw.c_str());
cleandata(&PlainPsw[0],PlainPsw.size()*sizeof(PlainPsw[0]));
break;
}
return true;
}
#endif
#ifndef SILENT
void getwstr(std::wstring &str)
{
// Print buffered prompt title function before waiting for input.
fflush(stderr);
QuitIfInputProhibited();
str.clear();
const size_t MaxRead=MAXPATHSIZE; // Large enough to read a file name.
#if defined(_WIN_ALL)
// fgetws does not work well with non-English text in Windows,
// so we do not use it.
if (StdinRedirected) // ReadConsole does not work if redirected.
{
// fgets does not work well with pipes in Windows in our test.
// Let's use files.
std::vector<char> StrA(MaxRead*4); // Up to 4 UTF-8 characters per wchar_t.
File SrcFile;
SrcFile.SetHandleType(FILE_HANDLESTD);
SrcFile.SetLineInputMode(true);
int ReadSize=SrcFile.Read(&StrA[0],StrA.size()-1);
if (ReadSize<=0)
{
// Looks like stdin is a null device. We can enter to infinite loop
// calling Ask() or set an empty password, so let's better exit.
ErrHandler.ReadError(L"stdin");
}
StrA[ReadSize]=0;
// We expect ANSI encoding here, but "echo text|rar ..." to pipe to RAR,
// such as send passwords, we get OEM encoding by default, unless we
// use "chcp" in console. But we avoid OEM to ANSI conversion,
// because we also want to handle ANSI files redirection correctly,
// like "rar ... < ansifile.txt".
CharToWide(&StrA[0],str);
cleandata(&StrA[0],StrA.size()); // We can use this function to enter passwords.
}
else
{
std::vector<wchar> Buf(MaxRead); // Up to 4 UTF-8 characters per wchar_t.
DWORD SizeToRead=(DWORD)Buf.size()-1;
// ReadConsole fails in Windows 7 for requested input exceeding 30 KB.
// Not certain about Windows 8, so check for Windows 10 here.
if (WinNT()<=WNT_W10)
SizeToRead=Min(SizeToRead,0x4000);
DWORD ReadSize=0;
if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE),&Buf[0],SizeToRead,&ReadSize,NULL)==0)
ErrHandler.ReadError(L"stdin"); // Unknown user input, safer to abort.
Buf[ReadSize]=0;
str=Buf.data();
}
#else
std::vector<wchar> Buf(MaxRead); // Up to 4 UTF-8 characters per wchar_t.
if (fgetws(&Buf[0],Buf.size(),stdin)==NULL)
ErrHandler.ReadError(L"stdin"); // Avoid infinite Ask() loop.
str=Buf.data();
#endif
RemoveLF(str);
}
#endif
#ifndef SILENT
// We allow this function to return 0 in case of invalid input,
// because it might be convenient to press Enter to some not dangerous
// prompts like "insert disk with next volume". We should call this function
// again in case of 0 in dangerous prompt such as overwriting file.
int Ask(const wchar *AskStr)
{
uiAlarm(UIALARM_QUESTION);
const uint MaxItems=10;
wchar Item[MaxItems][40];
uint ItemKeyPos[MaxItems],NumItems=0;
for (const wchar *NextItem=AskStr;NextItem!=nullptr;NextItem=wcschr(NextItem+1,'_'))
{
wchar *CurItem=Item[NumItems];
wcsncpyz(CurItem,NextItem+1,ASIZE(Item[0]));
wchar *EndItem=wcschr(CurItem,'_');
if (EndItem!=nullptr)
*EndItem=0;
uint KeyPos=0,CurKey;
while ((CurKey=CurItem[KeyPos])!=0)
{
bool Found=false;
for (uint I=0;I<NumItems && !Found;I++)
if (toupperw(Item[I][ItemKeyPos[I]])==toupperw(CurKey))
Found=true;
if (!Found && CurKey!=' ')
break;
KeyPos++;
}
ItemKeyPos[NumItems]=KeyPos;
NumItems++;
}
for (uint I=0;I<NumItems;I++)
{
eprintf(I==0 ? (NumItems>3 ? L"\n":L" "):L", ");
uint KeyPos=ItemKeyPos[I];
for (uint J=0;J<KeyPos;J++)
eprintf(L"%c",Item[I][J]);
eprintf(L"[%c]%ls",Item[I][KeyPos],&Item[I][KeyPos+1]);
}
eprintf(L" ");
std::wstring Str;
getwstr(Str);
wchar Ch=toupperw(Str[0]);
for (uint I=0;I<NumItems;I++)
if (Ch==Item[I][ItemKeyPos[I]])
return I+1;
return 0;
}
#endif
static bool IsCommentUnsafe(const std::wstring &Data)
{
for (size_t I=0;I<Data.size();I++)
if (Data[I]==27 && Data[I+1]=='[')
for (size_t J=I+2;J<Data.size();J++)
{
// Return true for <ESC>[{key};"{string}"p used to redefine
// a keyboard key on some terminals.
if (Data[J]=='\"')
return true;
if (!IsDigit(Data[J]) && Data[J]!=';')
break;
}
return false;
}
void OutComment(const std::wstring &Comment)
{
if (IsCommentUnsafe(Comment))
return;
const size_t MaxOutSize=0x400;
for (size_t I=0;I<Comment.size();I+=MaxOutSize)
{
size_t CopySize=Min(MaxOutSize,Comment.size()-I);
mprintf(L"%s",Comment.substr(I,CopySize).c_str());
}
mprintf(L"\n");
}
bool IsConsoleOutputPresent()
{
return ConsoleOutputPresent;
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef _RAR_CONSIO_
#define _RAR_CONSIO_
void InitConsole();
void SetConsoleMsgStream(MESSAGE_TYPE MsgStream);
void SetConsoleRedirectCharset(RAR_CHARSET RedirectCharset);
void ProhibitConsoleInput();
void OutComment(const std::wstring &Comment);
bool IsConsoleOutputPresent();
#ifndef SILENT
bool GetConsolePassword(UIPASSWORD_TYPE Type,const std::wstring &FileName,SecPassword *Password);
#endif
#ifdef SILENT
inline void mprintf(const wchar *fmt,...) {}
inline void eprintf(const wchar *fmt,...) {}
inline void Alarm() {}
inline int Ask(const wchar *AskStr) {return 0;}
inline void getwstr(std::wstring &str) {}
#else
void mprintf(const wchar *fmt,...);
void eprintf(const wchar *fmt,...);
void Alarm();
int Ask(const wchar *AskStr);
void getwstr(std::wstring &str);
#endif
#endif
+280
View File
@@ -0,0 +1,280 @@
// This CRC function is based on Intel Slicing-by-8 algorithm.
//
// Original Intel Slicing-by-8 code is available here:
//
// http://sourceforge.net/projects/slicing-by-8/
//
// Original Intel Slicing-by-8 code is licensed as:
//
// Copyright (c) 2004-2006 Intel Corporation - All Rights Reserved
//
// This software program is licensed subject to the BSD License,
// available at http://www.opensource.org/licenses/bsd-license.html
#include "rar.hpp"
#ifndef SFX_MODULE
// User suggested to avoid BSD license in SFX module, so they do not need
// to include the license to SFX archive.
#define USE_SLICING
#endif
static uint crc_tables[16][256]; // Tables for Slicing-by-16.
#ifdef USE_NEON_CRC32
static bool CRC_Neon;
#endif
// Build the classic CRC32 lookup table.
// We also provide this function to legacy RAR and ZIP decryption code.
void InitCRC32(uint *CRCTab)
{
if (CRCTab[1]!=0)
return;
for (uint I=0;I<256;I++)
{
uint C=I;
for (uint J=0;J<8;J++)
C=(C & 1) ? (C>>1)^0xEDB88320 : (C>>1);
CRCTab[I]=C;
}
#ifdef USE_NEON_CRC32
#ifdef _APPLE
// getauxval isn't available in OS X
uint Value=0;
size_t Size=sizeof(Value);
int RetCode=sysctlbyname("hw.optional.armv8_crc32",&Value,&Size,NULL,0);
CRC_Neon=RetCode==0 && Value!=0;
#else
CRC_Neon=(getauxval(AT_HWCAP) & HWCAP_CRC32)!=0;
#endif
#endif
}
static void InitTables()
{
InitCRC32(crc_tables[0]);
#ifdef USE_SLICING
for (uint I=0;I<256;I++) // Build additional lookup tables.
{
uint C=crc_tables[0][I];
for (uint J=1;J<16;J++)
{
C=crc_tables[0][(byte)C]^(C>>8);
crc_tables[J][I]=C;
}
}
#endif
}
struct CallInitCRC {CallInitCRC() {InitTables();}} static CallInit32;
uint CRC32(uint StartCRC,const void *Addr,size_t Size)
{
byte *Data=(byte *)Addr;
#ifdef USE_NEON_CRC32
if (CRC_Neon)
{
for (;Size>=8;Size-=8,Data+=8)
#ifdef __clang__
StartCRC = __builtin_arm_crc32d(StartCRC, RawGet8(Data));
#else
StartCRC = __builtin_aarch64_crc32x(StartCRC, RawGet8(Data));
#endif
for (;Size>0;Size--,Data++) // Process left data.
#ifdef __clang__
StartCRC = __builtin_arm_crc32b(StartCRC, *Data);
#else
StartCRC = __builtin_aarch64_crc32b(StartCRC, *Data);
#endif
return StartCRC;
}
#endif
#ifdef USE_SLICING
// Align Data to 16 for better performance and to avoid ALLOW_MISALIGNED
// check below.
for (;Size>0 && ((size_t)Data & 15)!=0;Size--,Data++)
StartCRC=crc_tables[0][(byte)(StartCRC^Data[0])]^(StartCRC>>8);
// 2023.12.06: We switched to slicing-by-16, which seems to be faster than
// slicing-by-8 on modern CPUs. Slicing-by-32 would require 32 KB for tables
// and could be limited by L1 cache size on some CPUs.
for (;Size>=16;Size-=16,Data+=16)
{
#ifdef BIG_ENDIAN
StartCRC ^= RawGet4(Data);
uint D1 = RawGet4(Data+4);
uint D2 = RawGet4(Data+8);
uint D3 = RawGet4(Data+12);
#else
// We avoid RawGet4 here for performance reason, to access uint32
// directly even if ALLOW_MISALIGNED isn't defined. We can do it,
// because we aligned 'Data' above.
StartCRC ^= *(uint32 *) Data;
uint D1 = *(uint32 *) (Data+4);
uint D2 = *(uint32 *) (Data+8);
uint D3 = *(uint32 *) (Data+12);
#endif
StartCRC = crc_tables[15][(byte) StartCRC ] ^
crc_tables[14][(byte)(StartCRC >> 8) ] ^
crc_tables[13][(byte)(StartCRC >> 16)] ^
crc_tables[12][(byte)(StartCRC >> 24)] ^
crc_tables[11][(byte) D1 ] ^
crc_tables[10][(byte)(D1 >> 8) ] ^
crc_tables[ 9][(byte)(D1 >> 16)] ^
crc_tables[ 8][(byte)(D1 >> 24)] ^
crc_tables[ 7][(byte) D2 ] ^
crc_tables[ 6][(byte)(D2 >> 8)] ^
crc_tables[ 5][(byte)(D2 >> 16)] ^
crc_tables[ 4][(byte)(D2 >> 24)] ^
crc_tables[ 3][(byte) D3 ] ^
crc_tables[ 2][(byte)(D3 >> 8)] ^
crc_tables[ 1][(byte)(D3 >> 16)] ^
crc_tables[ 0][(byte)(D3 >> 24)];
}
#endif
for (;Size>0;Size--,Data++) // Process left data.
StartCRC=crc_tables[0][(byte)(StartCRC^Data[0])]^(StartCRC>>8);
return StartCRC;
}
#ifndef SFX_MODULE
// For RAR 1.4 archives in case somebody still has them.
ushort Checksum14(ushort StartCRC,const void *Addr,size_t Size)
{
byte *Data=(byte *)Addr;
for (size_t I=0;I<Size;I++)
{
StartCRC=(StartCRC+Data[I])&0xffff;
StartCRC=((StartCRC<<1)|(StartCRC>>15))&0xffff;
}
return StartCRC;
}
#endif
#if 0
static void TestCRC();
struct TestCRCStruct {TestCRCStruct() {TestCRC();exit(0);}} GlobalTesCRC;
void TestCRC()
{
// This function is invoked from global object and _SSE_Version is global
// and can be initialized after this function. So we explicitly initialize
// it here to enable SSE support in Blake2sp.
_SSE_Version=GetSSEVersion();
const uint FirstSize=300;
byte b[FirstSize];
if ((CRC32(0xffffffff,(byte*)"testtesttest",12)^0xffffffff)==0x44608e84)
mprintf(L"\nCRC32 test1 OK");
else
mprintf(L"\nCRC32 test1 FAILED");
if (CRC32(0,(byte*)"te\x80st",5)==0xB2E5C5AE)
mprintf(L"\nCRC32 test2 OK");
else
mprintf(L"\nCRC32 test2 FAILED");
for (uint I=0;I<14;I++) // Check for possible int sign extension.
b[I]=(byte)0x7f+I;
if ((CRC32(0xffffffff,b,14)^0xffffffff)==0x1DFA75DA)
mprintf(L"\nCRC32 test3 OK");
else
mprintf(L"\nCRC32 test3 FAILED");
for (uint I=0;I<FirstSize;I++)
b[I]=(byte)I;
uint r32=CRC32(0xffffffff,b,FirstSize);
for (uint I=FirstSize;I<1024;I++)
{
b[0]=(byte)I;
r32=CRC32(r32,b,1);
}
if ((r32^0xffffffff)==0xB70B4C26)
mprintf(L"\nCRC32 test4 OK");
else
mprintf(L"\nCRC32 test4 FAILED");
if ((CRC64(0xffffffffffffffff,(byte*)"testtesttest",12)^0xffffffffffffffff)==0x7B1C2D230EDEB436)
mprintf(L"\nCRC64 test1 OK");
else
mprintf(L"\nCRC64 test1 FAILED");
if (CRC64(0,(byte*)"te\x80st",5)==0xB5DBF9583A6EED4A)
mprintf(L"\nCRC64 test2 OK");
else
mprintf(L"\nCRC64 test2 FAILED");
for (uint I=0;I<14;I++) // Check for possible int sign extension.
b[I]=(byte)0x7f+I;
if ((CRC64(0xffffffffffffffff,b,14)^0xffffffffffffffff)==0xE019941C05B2820C)
mprintf(L"\nCRC64 test3 OK");
else
mprintf(L"\nCRC64 test3 FAILED");
for (uint I=0;I<FirstSize;I++)
b[I]=(byte)I;
uint64 r64=CRC64(0xffffffffffffffff,b,FirstSize);
for (uint I=FirstSize;I<1024;I++)
{
b[0]=(byte)I;
r64=CRC64(r64,b,1);
}
if ((r64^0xffffffffffffffff)==0xD51FB58DC789C400)
mprintf(L"\nCRC64 test4 OK");
else
mprintf(L"\nCRC64 test4 FAILED");
const size_t BufSize=0x100000;
byte *Buf=new byte[BufSize];
GetRnd(Buf,BufSize);
clock_t StartTime=clock();
r32=0xffffffff;
const uint64 BufCount=5000;
for (uint I=0;I<BufCount;I++)
r32=CRC32(r32,Buf,BufSize);
if (r32!=0) // Otherwise compiler optimizer removes CRC calculation.
mprintf(L"\nCRC32 speed: %llu MB/s",BufCount*CLOCKS_PER_SEC/(clock()-StartTime));
StartTime=clock();
DataHash Hash;
Hash.Init(HASH_CRC32,MaxPoolThreads);
const uint64 BufCountMT=20000;
for (uint I=0;I<BufCountMT;I++)
Hash.Update(Buf,BufSize);
HashValue Result;
Hash.Result(&Result);
mprintf(L"\nCRC32 MT speed: %llu MB/s",BufCountMT*CLOCKS_PER_SEC/(clock()-StartTime));
StartTime=clock();
Hash.Init(HASH_BLAKE2,MaxPoolThreads);
for (uint I=0;I<BufCount;I++)
Hash.Update(Buf,BufSize);
Hash.Result(&Result);
mprintf(L"\nBlake2sp speed: %llu MB/s",BufCount*CLOCKS_PER_SEC/(clock()-StartTime));
StartTime=clock();
r64=0xffffffffffffffff;
for (uint I=0;I<BufCount;I++)
r64=CRC64(r64,Buf,BufSize);
if (r64!=0) // Otherwise compiler optimizer removes CRC calculation.
mprintf(L"\nCRC64 speed: %llu MB/s",BufCount*CLOCKS_PER_SEC/(clock()-StartTime));
}
#endif
+15
View File
@@ -0,0 +1,15 @@
#ifndef _RAR_CRC_
#define _RAR_CRC_
// This function is only to intialize external CRC tables. We do not need to
// call it before calculating CRC32.
void InitCRC32(uint *CRCTab);
uint CRC32(uint StartCRC,const void *Addr,size_t Size);
#ifndef SFX_MODULE
ushort Checksum14(ushort StartCRC,const void *Addr,size_t Size);
#endif
#endif
+134
View File
@@ -0,0 +1,134 @@
#include "rar.hpp"
#ifndef SFX_MODULE
#include "crypt1.cpp"
#include "crypt2.cpp"
#endif
#include "crypt3.cpp"
#include "crypt5.cpp"
CryptData::CryptData()
{
Method=CRYPT_NONE;
KDF3CachePos=0;
KDF5CachePos=0;
memset(CRCTab,0,sizeof(CRCTab));
}
void CryptData::DecryptBlock(byte *Buf,size_t Size)
{
switch(Method)
{
#ifndef SFX_MODULE
case CRYPT_RAR13:
Decrypt13(Buf,Size);
break;
case CRYPT_RAR15:
Crypt15(Buf,Size);
break;
case CRYPT_RAR20:
for (size_t I=0;I<Size;I+=CRYPT_BLOCK_SIZE)
DecryptBlock20(Buf+I);
break;
#endif
case CRYPT_RAR30:
case CRYPT_RAR50:
rin.blockDecrypt(Buf,Size,Buf);
break;
}
}
bool CryptData::SetCryptKeys(bool Encrypt,CRYPT_METHOD Method,
SecPassword *Password,const byte *Salt,
const byte *InitV,uint Lg2Cnt,byte *HashKey,byte *PswCheck)
{
if (Method==CRYPT_NONE || !Password->IsSet())
return false;
CryptData::Method=Method;
wchar PwdW[MAXPASSWORD];
Password->Get(PwdW,ASIZE(PwdW));
if (wcslen(PwdW)>=MAXPASSWORD_RAR)
uiMsg(UIERROR_TRUNCPSW,MAXPASSWORD_RAR-1);
PwdW[Min(MAXPASSWORD_RAR,MAXPASSWORD)-1]=0; // For compatibility with existing archives.
char PwdA[MAXPASSWORD];
WideToChar(PwdW,PwdA,ASIZE(PwdA));
PwdA[Min(MAXPASSWORD_RAR,MAXPASSWORD)-1]=0; // For compatibility with existing archives.
bool Success=true;
switch(Method)
{
#ifndef SFX_MODULE
case CRYPT_RAR13:
SetKey13(PwdA);
break;
case CRYPT_RAR15:
SetKey15(PwdA);
break;
case CRYPT_RAR20:
SetKey20(PwdA);
break;
#endif
case CRYPT_RAR30:
SetKey30(Encrypt,Password,PwdW,Salt);
break;
case CRYPT_RAR50:
Success=SetKey50(Encrypt,Password,PwdW,Salt,InitV,Lg2Cnt,HashKey,PswCheck);
break;
}
cleandata(PwdA,sizeof(PwdA));
cleandata(PwdW,sizeof(PwdW));
return Success;
}
// Use the current system time to additionally randomize data.
static void TimeRandomize(byte *RndBuf,size_t BufSize)
{
static uint Count=0;
RarTime CurTime;
CurTime.SetCurrentTime();
uint64 Random=CurTime.GetWin()+clock();
for (size_t I=0;I<BufSize;I++)
{
byte RndByte = byte (Random >> ( (I & 7) * 8 ));
RndBuf[I]=byte( (RndByte ^ I) + Count++);
}
}
// Fill buffer with random data.
void GetRnd(byte *RndBuf,size_t BufSize)
{
bool Success=false;
#if defined(_WIN_ALL)
HCRYPTPROV hProvider = 0;
if (CryptAcquireContext(&hProvider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
{
Success=CryptGenRandom(hProvider, (DWORD)BufSize, RndBuf) != FALSE;
CryptReleaseContext(hProvider, 0);
}
#elif defined(_UNIX)
FILE *rndf = fopen("/dev/urandom", "r");
if (rndf!=NULL)
{
Success=fread(RndBuf, BufSize, 1, rndf) == BufSize;
fclose(rndf);
}
#endif
// We use this code only as the last resort if code above failed.
if (!Success)
TimeRandomize(RndBuf,BufSize);
}
+171
View File
@@ -0,0 +1,171 @@
#ifndef _RAR_CRYPT_
#define _RAR_CRYPT_
enum CRYPT_METHOD {
CRYPT_NONE,CRYPT_RAR13,CRYPT_RAR15,CRYPT_RAR20,CRYPT_RAR30,CRYPT_RAR50,
CRYPT_UNKNOWN
};
#define SIZE_SALT50 16
#define SIZE_SALT30 8
#define SIZE_INITV 16
#define SIZE_PSWCHECK 8
#define SIZE_PSWCHECK_CSUM 4
#define CRYPT_BLOCK_SIZE 16
#define CRYPT_BLOCK_MASK (CRYPT_BLOCK_SIZE-1) // 0xf
#define CRYPT5_KDF_LG2_COUNT 15 // LOG2 of PDKDF2 iteration count.
#define CRYPT5_KDF_LG2_COUNT_MAX 24 // LOG2 of maximum accepted iteration count.
#define CRYPT_VERSION 0 // Supported encryption version.
class CryptData
{
struct KDF5CacheItem
{
SecPassword Pwd;
byte Salt[SIZE_SALT50];
byte Key[32];
uint Lg2Count; // Log2 of PBKDF2 repetition count.
byte PswCheckValue[SHA256_DIGEST_SIZE];
byte HashKeyValue[SHA256_DIGEST_SIZE];
KDF5CacheItem() {Clean();}
~KDF5CacheItem() {Clean();}
void Clean()
{
cleandata(Salt,sizeof(Salt));
cleandata(Key,sizeof(Key));
cleandata(&Lg2Count,sizeof(Lg2Count));
cleandata(PswCheckValue,sizeof(PswCheckValue));
cleandata(HashKeyValue,sizeof(HashKeyValue));
}
};
struct KDF3CacheItem
{
SecPassword Pwd;
byte Salt[SIZE_SALT30];
byte Key[16];
byte Init[16];
bool SaltPresent;
KDF3CacheItem() {Clean();}
~KDF3CacheItem() {Clean();}
void Clean()
{
cleandata(Salt,sizeof(Salt));
cleandata(Key,sizeof(Key));
cleandata(Init,sizeof(Init));
cleandata(&SaltPresent,sizeof(SaltPresent));
}
};
private:
void SetKey13(const char *Password);
void Decrypt13(byte *Data,size_t Count);
void SetKey15(const char *Password);
void Crypt15(byte *Data,size_t Count);
void SetKey20(const char *Password);
void Swap20(byte *Ch1,byte *Ch2);
void UpdKeys20(byte *Buf);
void EncryptBlock20(byte *Buf);
void DecryptBlock20(byte *Buf);
void SetKey30(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt);
bool SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey,byte *PswCheck);
KDF3CacheItem KDF3Cache[4];
uint KDF3CachePos;
KDF5CacheItem KDF5Cache[4];
uint KDF5CachePos;
CRYPT_METHOD Method;
Rijndael rin;
uint CRCTab[256]; // For RAR 1.5 and RAR 2.0 encryption.
byte SubstTable20[256];
uint Key20[4];
byte Key13[3];
ushort Key15[4];
public:
CryptData();
bool SetCryptKeys(bool Encrypt,CRYPT_METHOD Method,SecPassword *Password,
const byte *Salt,const byte *InitV,uint Lg2Cnt,
byte *HashKey,byte *PswCheck);
void SetCmt13Encryption();
void EncryptBlock(byte *Buf,size_t Size);
void DecryptBlock(byte *Buf,size_t Size);
static void SetSalt(byte *Salt,size_t SaltSize);
};
class CheckPassword
{
public:
enum CONFIDENCE {CONFIDENCE_HIGH,CONFIDENCE_MEDIUM,CONFIDENCE_LOW};
virtual CONFIDENCE GetConfidence()=0;
virtual bool Check(SecPassword *Password)=0;
};
class RarCheckPassword:public CheckPassword
{
private:
CryptData *Crypt;
uint Lg2Count;
byte Salt[SIZE_SALT50];
byte InitV[SIZE_INITV];
byte PswCheck[SIZE_PSWCHECK];
public:
RarCheckPassword()
{
Crypt=NULL;
}
~RarCheckPassword()
{
delete Crypt;
}
void Set(byte *Salt,byte *InitV,uint Lg2Count,byte *PswCheck)
{
if (Crypt==NULL)
Crypt=new CryptData;
memcpy(this->Salt,Salt,sizeof(this->Salt));
memcpy(this->InitV,InitV,sizeof(this->InitV));
this->Lg2Count=Lg2Count;
memcpy(this->PswCheck,PswCheck,sizeof(this->PswCheck));
}
bool IsSet() {return Crypt!=NULL;}
// RAR5 provides the higly reliable 64 bit password verification value.
CONFIDENCE GetConfidence() {return CONFIDENCE_HIGH;}
bool Check(SecPassword *Password)
{
byte PswCheck[SIZE_PSWCHECK];
Crypt->SetCryptKeys(false,CRYPT_RAR50,Password,Salt,InitV,Lg2Count,NULL,PswCheck);
return memcmp(PswCheck,this->PswCheck,sizeof(this->PswCheck))==0;
}
};
void GetRnd(byte *RndBuf,size_t BufSize);
void hmac_sha256(const byte *Key,size_t KeyLength,const byte *Data,
size_t DataLength,byte *ResDigest);
void pbkdf2(const byte *pass, size_t pass_len, const byte *salt,
size_t salt_len,byte *key, byte *Value1, byte *Value2,
uint rounds);
void ConvertHashToMAC(HashValue *Value,byte *Key);
#endif
+66
View File
@@ -0,0 +1,66 @@
void CryptData::SetKey13(const char *Password)
{
Key13[0]=Key13[1]=Key13[2]=0;
for (size_t I=0;Password[I]!=0;I++)
{
byte P=Password[I];
Key13[0]+=P;
Key13[1]^=P;
Key13[2]+=P;
Key13[2]=(byte)rotls(Key13[2],1,8);
}
}
void CryptData::SetKey15(const char *Password)
{
InitCRC32(CRCTab);
uint PswCRC=CRC32(0xffffffff,Password,strlen(Password));
Key15[0]=PswCRC&0xffff;
Key15[1]=(PswCRC>>16)&0xffff;
Key15[2]=Key15[3]=0;
for (size_t I=0;Password[I]!=0;I++)
{
byte P=Password[I];
Key15[2]^=P^CRCTab[P];
Key15[3]+=ushort(P+(CRCTab[P]>>16));
}
}
void CryptData::SetCmt13Encryption()
{
Method=CRYPT_RAR13;
Key13[0]=0;
Key13[1]=7;
Key13[2]=77;
}
void CryptData::Decrypt13(byte *Data,size_t Count)
{
while (Count--)
{
Key13[1]+=Key13[2];
Key13[0]+=Key13[1];
*Data-=Key13[0];
Data++;
}
}
void CryptData::Crypt15(byte *Data,size_t Count)
{
while (Count--)
{
Key15[0]+=0x1234;
Key15[1]^=CRCTab[(Key15[0] & 0x1fe)>>1];
Key15[2]-=ushort(CRCTab[(Key15[0] & 0x1fe)>>1]>>16);
Key15[0]^=Key15[2];
Key15[3]=rotrs(Key15[3]&0xffff,1,16)^Key15[1];
Key15[3]=rotrs(Key15[3]&0xffff,1,16);
Key15[0]^=Key15[3];
*Data^=(byte)(Key15[0]>>8);
Data++;
}
}
+133
View File
@@ -0,0 +1,133 @@
#define NROUNDS 32
#define substLong(t) ( (uint)SubstTable20[(uint)t&255] | \
((uint)SubstTable20[(int)(t>> 8)&255]<< 8) | \
((uint)SubstTable20[(int)(t>>16)&255]<<16) | \
((uint)SubstTable20[(int)(t>>24)&255]<<24) )
static byte InitSubstTable20[256]={
215, 19,149, 35, 73,197,192,205,249, 28, 16,119, 48,221, 2, 42,
232, 1,177,233, 14, 88,219, 25,223,195,244, 90, 87,239,153,137,
255,199,147, 70, 92, 66,246, 13,216, 40, 62, 29,217,230, 86, 6,
71, 24,171,196,101,113,218,123, 93, 91,163,178,202, 67, 44,235,
107,250, 75,234, 49,167,125,211, 83,114,157,144, 32,193,143, 36,
158,124,247,187, 89,214,141, 47,121,228, 61,130,213,194,174,251,
97,110, 54,229,115, 57,152, 94,105,243,212, 55,209,245, 63, 11,
164,200, 31,156, 81,176,227, 21, 76, 99,139,188,127, 17,248, 51,
207,120,189,210, 8,226, 41, 72,183,203,135,165,166, 60, 98, 7,
122, 38,155,170, 69,172,252,238, 39,134, 59,128,236, 27,240, 80,
131, 3, 85,206,145, 79,154,142,159,220,201,133, 74, 64, 20,129,
224,185,138,103,173,182, 43, 34,254, 82,198,151,231,180, 58, 10,
118, 26,102, 12, 50,132, 22,191,136,111,162,179, 45, 4,148,108,
161, 56, 78,126,242,222, 15,175,146, 23, 33,241,181,190, 77,225,
0, 46,169,186, 68, 95,237, 65, 53,208,253,168, 9, 18,100, 52,
116,184,160, 96,109, 37, 30,106,140,104,150, 5,204,117,112, 84
};
void CryptData::SetKey20(const char *Password)
{
InitCRC32(CRCTab);
char Psw[MAXPASSWORD];
strncpyz(Psw,Password,ASIZE(Psw)); // We'll need to modify it below.
size_t PswLength=strlen(Psw);
Key20[0]=0xD3A3B879L;
Key20[1]=0x3F6D12F7L;
Key20[2]=0x7515A235L;
Key20[3]=0xA4E7F123L;
memcpy(SubstTable20,InitSubstTable20,sizeof(SubstTable20));
for (uint J=0;J<256;J++)
for (size_t I=0;I<PswLength;I+=2)
{
uint N1=(byte)CRCTab [ (byte(Password[I]) - J) &0xff];
uint N2=(byte)CRCTab [ (byte(Password[I+1]) + J) &0xff];
for (int K=1;N1!=N2;N1=(N1+1)&0xff,K++)
Swap20(&SubstTable20[N1],&SubstTable20[(N1+I+K)&0xff]);
}
// Incomplete last block of password must be zero padded.
if ((PswLength & CRYPT_BLOCK_MASK)!=0)
for (size_t I=PswLength;I<=(PswLength|CRYPT_BLOCK_MASK);I++)
Psw[I]=0;
for (size_t I=0;I<PswLength;I+=CRYPT_BLOCK_SIZE)
EncryptBlock20((byte *)Psw+I);
}
void CryptData::EncryptBlock20(byte *Buf)
{
uint A,B,C,D,T,TA,TB;
A=RawGet4(Buf+0)^Key20[0];
B=RawGet4(Buf+4)^Key20[1];
C=RawGet4(Buf+8)^Key20[2];
D=RawGet4(Buf+12)^Key20[3];
for(int I=0;I<NROUNDS;I++)
{
T=((C+rotls(D,11,32))^Key20[I&3]);
TA=A^substLong(T);
T=((D^rotls(C,17,32))+Key20[I&3]);
TB=B^substLong(T);
A=C;
B=D;
C=TA;
D=TB;
}
RawPut4(C^Key20[0],Buf+0);
RawPut4(D^Key20[1],Buf+4);
RawPut4(A^Key20[2],Buf+8);
RawPut4(B^Key20[3],Buf+12);
UpdKeys20(Buf);
}
void CryptData::DecryptBlock20(byte *Buf)
{
byte InBuf[16];
uint A,B,C,D,T,TA,TB;
A=RawGet4(Buf+0)^Key20[0];
B=RawGet4(Buf+4)^Key20[1];
C=RawGet4(Buf+8)^Key20[2];
D=RawGet4(Buf+12)^Key20[3];
memcpy(InBuf,Buf,sizeof(InBuf));
for(int I=NROUNDS-1;I>=0;I--)
{
T=((C+rotls(D,11,32))^Key20[I&3]);
TA=A^substLong(T);
T=((D^rotls(C,17,32))+Key20[I&3]);
TB=B^substLong(T);
A=C;
B=D;
C=TA;
D=TB;
}
RawPut4(C^Key20[0],Buf+0);
RawPut4(D^Key20[1],Buf+4);
RawPut4(A^Key20[2],Buf+8);
RawPut4(B^Key20[3],Buf+12);
UpdKeys20(InBuf);
}
void CryptData::UpdKeys20(byte *Buf)
{
for (int I=0;I<16;I+=4)
{
Key20[0]^=CRCTab[Buf[I]];
Key20[1]^=CRCTab[Buf[I+1]];
Key20[2]^=CRCTab[Buf[I+2]];
Key20[3]^=CRCTab[Buf[I+3]];
}
}
void CryptData::Swap20(byte *Ch1,byte *Ch2)
{
byte Ch=*Ch1;
*Ch1=*Ch2;
*Ch2=Ch;
}
+69
View File
@@ -0,0 +1,69 @@
void CryptData::SetKey30(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt)
{
byte AESKey[16],AESInit[16];
bool Cached=false;
for (uint I=0;I<ASIZE(KDF3Cache);I++)
if (KDF3Cache[I].Pwd==*Password &&
(Salt==NULL && !KDF3Cache[I].SaltPresent || Salt!=NULL &&
KDF3Cache[I].SaltPresent && memcmp(KDF3Cache[I].Salt,Salt,SIZE_SALT30)==0))
{
memcpy(AESKey,KDF3Cache[I].Key,sizeof(AESKey));
SecHideData(AESKey,sizeof(AESKey),false,false);
memcpy(AESInit,KDF3Cache[I].Init,sizeof(AESInit));
Cached=true;
break;
}
if (!Cached)
{
byte RawPsw[2*MAXPASSWORD+SIZE_SALT30];
size_t PswLength=wcslen(PwdW);
size_t RawLength=2*PswLength;
WideToRaw(PwdW,PswLength,RawPsw,RawLength);
if (Salt!=NULL)
{
memcpy(RawPsw+RawLength,Salt,SIZE_SALT30);
RawLength+=SIZE_SALT30;
}
sha1_context c;
sha1_init(&c);
const uint HashRounds=0x40000;
for (uint I=0;I<HashRounds;I++)
{
sha1_process_rar29( &c, RawPsw, RawLength );
byte PswNum[3];
PswNum[0]=(byte)I;
PswNum[1]=(byte)(I>>8);
PswNum[2]=(byte)(I>>16);
sha1_process(&c, PswNum, 3);
if (I%(HashRounds/16)==0)
{
sha1_context tempc=c;
uint32 digest[5];
sha1_done( &tempc, digest );
AESInit[I/(HashRounds/16)]=(byte)digest[4];
}
}
uint32 digest[5];
sha1_done( &c, digest );
for (uint I=0;I<4;I++)
for (uint J=0;J<4;J++)
AESKey[I*4+J]=(byte)(digest[I]>>(J*8));
KDF3Cache[KDF3CachePos].Pwd=*Password;
if ((KDF3Cache[KDF3CachePos].SaltPresent=(Salt!=NULL))==true)
memcpy(KDF3Cache[KDF3CachePos].Salt,Salt,SIZE_SALT30);
memcpy(KDF3Cache[KDF3CachePos].Key,AESKey,sizeof(AESKey));
SecHideData(KDF3Cache[KDF3CachePos].Key,sizeof(KDF3Cache[KDF3CachePos].Key),true,false);
memcpy(KDF3Cache[KDF3CachePos].Init,AESInit,sizeof(AESInit));
KDF3CachePos=(KDF3CachePos+1)%ASIZE(KDF3Cache);
cleandata(RawPsw,sizeof(RawPsw));
}
rin.Init(Encrypt, AESKey, 128, AESInit);
cleandata(AESKey,sizeof(AESKey));
cleandata(AESInit,sizeof(AESInit));
}
+235
View File
@@ -0,0 +1,235 @@
static void hmac_sha256(const byte *Key,size_t KeyLength,const byte *Data,
size_t DataLength,byte *ResDigest,
sha256_context *ICtxOpt,bool *SetIOpt,
sha256_context *RCtxOpt,bool *SetROpt)
{
const size_t Sha256BlockSize=64; // As defined in RFC 4868.
byte KeyHash[SHA256_DIGEST_SIZE];
if (KeyLength > Sha256BlockSize) // Convert longer keys to key hash.
{
sha256_context KCtx;
sha256_init(&KCtx);
sha256_process(&KCtx, Key, KeyLength);
sha256_done(&KCtx, KeyHash);
Key = KeyHash;
KeyLength = SHA256_DIGEST_SIZE;
}
byte KeyBuf[Sha256BlockSize]; // Store the padded key here.
sha256_context ICtx;
if (ICtxOpt!=NULL && *SetIOpt)
ICtx=*ICtxOpt; // Use already calculated the first block context.
else
{
// This calculation is the same for all iterations with same password.
// So for PBKDF2 we can calculate it only for first block and then reuse
// to improve performance.
for (size_t I = 0; I < KeyLength; I++) // Use 0x36 padding for inner digest.
KeyBuf[I] = Key[I] ^ 0x36;
for (size_t I = KeyLength; I < Sha256BlockSize; I++)
KeyBuf[I] = 0x36;
sha256_init(&ICtx);
sha256_process(&ICtx, KeyBuf, Sha256BlockSize); // Hash padded key.
}
if (ICtxOpt!=NULL && !*SetIOpt) // Store constant context for further reuse.
{
*ICtxOpt=ICtx;
*SetIOpt=true;
}
sha256_process(&ICtx, Data, DataLength); // Hash data.
byte IDig[SHA256_DIGEST_SIZE]; // Internal digest for padded key and data.
sha256_done(&ICtx, IDig);
sha256_context RCtx;
if (RCtxOpt!=NULL && *SetROpt)
RCtx=*RCtxOpt; // Use already calculated first block context.
else
{
// This calculation is the same for all iterations with same password.
// So for PBKDF2 we can calculate it only for first block and then reuse
// to improve performance.
for (size_t I = 0; I < KeyLength; I++) // Use 0x5c for outer key padding.
KeyBuf[I] = Key[I] ^ 0x5c;
for (size_t I = KeyLength; I < Sha256BlockSize; I++)
KeyBuf[I] = 0x5c;
sha256_init(&RCtx);
sha256_process(&RCtx, KeyBuf, Sha256BlockSize); // Hash padded key.
}
if (RCtxOpt!=NULL && !*SetROpt) // Store constant context for further reuse.
{
*RCtxOpt=RCtx;
*SetROpt=true;
}
sha256_process(&RCtx, IDig, SHA256_DIGEST_SIZE); // Hash internal digest.
sha256_done(&RCtx, ResDigest);
}
// PBKDF2 for 32 byte key length. We generate the key for specified number
// of iteration count also as two supplementary values (key for checksums
// and password verification) for iterations+16 and iterations+32.
void pbkdf2(const byte *Pwd, size_t PwdLength,
const byte *Salt, size_t SaltLength,
byte *Key, byte *V1, byte *V2, uint Count)
{
const size_t MaxSalt=64;
byte SaltData[MaxSalt+4];
memcpy(SaltData, Salt, Min(SaltLength,MaxSalt));
SaltData[SaltLength + 0] = 0; // Block index appened to salt.
SaltData[SaltLength + 1] = 0; //
SaltData[SaltLength + 2] = 0; // Since we do not request the key width
SaltData[SaltLength + 3] = 1; // exceeding HMAC width, it is always 1.
// First iteration: HMAC of password, salt and block index (1).
byte U1[SHA256_DIGEST_SIZE];
hmac_sha256(Pwd, PwdLength, SaltData, SaltLength + 4, U1, NULL, NULL, NULL, NULL);
byte Fn[SHA256_DIGEST_SIZE]; // Current function value.
memcpy(Fn, U1, sizeof(Fn)); // Function at first iteration.
uint CurCount[] = { Count-1, 16, 16 };
byte *CurValue[] = { Key , V1, V2 };
sha256_context ICtxOpt,RCtxOpt;
bool SetIOpt=false,SetROpt=false;
byte U2[SHA256_DIGEST_SIZE];
for (uint I = 0; I < 3; I++) // For output key and 2 supplementary values.
{
for (uint J = 0; J < CurCount[I]; J++)
{
// U2 = PRF (P, U1).
hmac_sha256(Pwd, PwdLength, U1, sizeof(U1), U2, &ICtxOpt, &SetIOpt, &RCtxOpt, &SetROpt);
memcpy(U1, U2, sizeof(U1));
for (uint K = 0; K < sizeof(Fn); K++) // Function ^= U.
Fn[K] ^= U1[K];
}
memcpy(CurValue[I], Fn, SHA256_DIGEST_SIZE);
}
cleandata(SaltData, sizeof(SaltData));
cleandata(Fn, sizeof(Fn));
cleandata(U1, sizeof(U1));
cleandata(U2, sizeof(U2));
}
bool CryptData::SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW,
const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey,
byte *PswCheck)
{
if (Lg2Cnt>CRYPT5_KDF_LG2_COUNT_MAX)
return false;
byte Key[32],PswCheckValue[SHA256_DIGEST_SIZE],HashKeyValue[SHA256_DIGEST_SIZE];
bool Found=false;
for (uint I=0;I<ASIZE(KDF5Cache);I++)
{
KDF5CacheItem *Item=KDF5Cache+I;
if (Item->Pwd==*Password && Item->Lg2Count==Lg2Cnt &&
memcmp(Item->Salt,Salt,SIZE_SALT50)==0)
{
memcpy(Key,Item->Key,sizeof(Key));
SecHideData(Key,sizeof(Key),false,false);
memcpy(PswCheckValue,Item->PswCheckValue,sizeof(PswCheckValue));
memcpy(HashKeyValue,Item->HashKeyValue,sizeof(HashKeyValue));
Found=true;
break;
}
}
if (!Found)
{
char PwdUtf[MAXPASSWORD*4];
WideToUtf(PwdW,PwdUtf,ASIZE(PwdUtf));
pbkdf2((byte *)PwdUtf,strlen(PwdUtf),Salt,SIZE_SALT50,Key,HashKeyValue,PswCheckValue,(1<<Lg2Cnt));
cleandata(PwdUtf,sizeof(PwdUtf));
KDF5CacheItem *Item=KDF5Cache+(KDF5CachePos++ % ASIZE(KDF5Cache));
Item->Lg2Count=Lg2Cnt;
Item->Pwd=*Password;
memcpy(Item->Salt,Salt,SIZE_SALT50);
memcpy(Item->Key,Key,sizeof(Item->Key));
memcpy(Item->PswCheckValue,PswCheckValue,sizeof(PswCheckValue));
memcpy(Item->HashKeyValue,HashKeyValue,sizeof(HashKeyValue));
SecHideData(Item->Key,sizeof(Item->Key),true,false);
}
if (HashKey!=NULL)
memcpy(HashKey,HashKeyValue,SHA256_DIGEST_SIZE);
if (PswCheck!=NULL)
{
memset(PswCheck,0,SIZE_PSWCHECK);
for (uint I=0;I<SHA256_DIGEST_SIZE;I++)
PswCheck[I%SIZE_PSWCHECK]^=PswCheckValue[I];
cleandata(PswCheckValue,sizeof(PswCheckValue));
}
// NULL initialization vector is possible if we only need the password
// check value for archive encryption header.
if (InitV!=NULL)
rin.Init(Encrypt, Key, 256, InitV);
cleandata(Key,sizeof(Key));
return true;
}
void ConvertHashToMAC(HashValue *Value,byte *Key)
{
if (Value->Type==HASH_CRC32)
{
byte RawCRC[4];
RawPut4(Value->CRC32,RawCRC);
byte Digest[SHA256_DIGEST_SIZE];
hmac_sha256(Key,SHA256_DIGEST_SIZE,RawCRC,sizeof(RawCRC),Digest,NULL,NULL,NULL,NULL);
Value->CRC32=0;
for (uint I=0;I<ASIZE(Digest);I++)
Value->CRC32^=Digest[I] << ((I & 3) * 8);
Value->CRC32&=0xffffffff; // In case the variable size is larger than 32-bit.
}
if (Value->Type==HASH_BLAKE2)
{
byte Digest[BLAKE2_DIGEST_SIZE];
hmac_sha256(Key,BLAKE2_DIGEST_SIZE,Value->Digest,sizeof(Value->Digest),Digest,NULL,NULL,NULL,NULL);
memcpy(Value->Digest,Digest,sizeof(Value->Digest));
}
}
#if 0
static void TestPBKDF2();
struct TestKDF {TestKDF() {TestPBKDF2();exit(0);}} GlobalTestKDF;
void TestPBKDF2() // Test PBKDF2 HMAC-SHA256
{
byte Key[32],V1[32],V2[32];
pbkdf2((byte *)"password", 8, (byte *)"salt", 4, Key, V1, V2, 1);
byte Res1[32]={0x12, 0x0f, 0xb6, 0xcf, 0xfc, 0xf8, 0xb3, 0x2c, 0x43, 0xe7, 0x22, 0x52, 0x56, 0xc4, 0xf8, 0x37, 0xa8, 0x65, 0x48, 0xc9, 0x2c, 0xcc, 0x35, 0x48, 0x08, 0x05, 0x98, 0x7c, 0xb7, 0x0b, 0xe1, 0x7b };
mprintf(L"\nPBKDF2 test1: %s", memcmp(Key,Res1,32)==0 ? L"OK":L"Failed");
pbkdf2((byte *)"password", 8, (byte *)"salt", 4, Key, V1, V2, 4096);
byte Res2[32]={0xc5, 0xe4, 0x78, 0xd5, 0x92, 0x88, 0xc8, 0x41, 0xaa, 0x53, 0x0d, 0xb6, 0x84, 0x5c, 0x4c, 0x8d, 0x96, 0x28, 0x93, 0xa0, 0x01, 0xce, 0x4e, 0x11, 0xa4, 0x96, 0x38, 0x73, 0xaa, 0x98, 0x13, 0x4a };
mprintf(L"\nPBKDF2 test2: %s", memcmp(Key,Res2,32)==0 ? L"OK":L"Failed");
pbkdf2((byte *)"just some long string pretending to be a password", 49, (byte *)"salt, salt, salt, a lot of salt", 31, Key, V1, V2, 65536);
byte Res3[32]={0x08, 0x0f, 0xa3, 0x1d, 0x42, 0x2d, 0xb0, 0x47, 0x83, 0x9b, 0xce, 0x3a, 0x3b, 0xce, 0x49, 0x51, 0xe2, 0x62, 0xb9, 0xff, 0x76, 0x2f, 0x57, 0xe9, 0xc4, 0x71, 0x96, 0xce, 0x4b, 0x6b, 0x6e, 0xbf};
mprintf(L"\nPBKDF2 test3: %s", memcmp(Key,Res3,32)==0 ? L"OK":L"Failed");
}
#endif
+513
View File
@@ -0,0 +1,513 @@
#include "rar.hpp"
static int RarErrorToDll(RAR_EXIT ErrCode);
struct DataSet
{
CommandData Cmd;
Archive Arc;
CmdExtract Extract;
int OpenMode;
int HeaderSize;
DataSet():Arc(&Cmd),Extract(&Cmd) {};
};
HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *r)
{
RAROpenArchiveDataEx rx{};
rx.ArcName=r->ArcName;
rx.OpenMode=r->OpenMode;
rx.CmtBuf=r->CmtBuf;
rx.CmtBufSize=r->CmtBufSize;
HANDLE hArc=RAROpenArchiveEx(&rx);
r->OpenResult=rx.OpenResult;
r->CmtSize=rx.CmtSize;
r->CmtState=rx.CmtState;
return hArc;
}
HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *r)
{
DataSet *Data=nullptr;
try
{
ErrHandler.Clean();
r->OpenResult=0;
Data=new DataSet;
Data->Cmd.DllError=0;
Data->OpenMode=r->OpenMode;
Data->Cmd.FileArgs.AddString(L"*");
Data->Cmd.KeepBroken=(r->OpFlags&ROADOF_KEEPBROKEN)!=0;
std::string AnsiArcName;
if (r->ArcName!=nullptr)
{
AnsiArcName=r->ArcName;
#ifdef _WIN_ALL
if (!AreFileApisANSI())
OemToExt(r->ArcName,AnsiArcName);
#endif
}
std::wstring ArcName;
if (r->ArcNameW!=nullptr && *r->ArcNameW!=0)
ArcName=r->ArcNameW;
else
CharToWide(AnsiArcName,ArcName);
Data->Cmd.AddArcName(ArcName);
Data->Cmd.Overwrite=OVERWRITE_ALL;
Data->Cmd.VersionControl=1;
Data->Cmd.Callback=r->Callback;
Data->Cmd.UserData=r->UserData;
// Open shared mode is added by request of dll users, who need to
// browse and unpack archives while downloading.
Data->Cmd.OpenShared = true;
if (!Data->Arc.Open(ArcName,FMF_OPENSHARED))
{
r->OpenResult=ERAR_EOPEN;
delete Data;
return nullptr;
}
if (!Data->Arc.IsArchive(true))
{
if (Data->Cmd.DllError!=0)
r->OpenResult=Data->Cmd.DllError;
else
{
RAR_EXIT ErrCode=ErrHandler.GetErrorCode();
if (ErrCode!=RARX_SUCCESS && ErrCode!=RARX_WARNING)
r->OpenResult=RarErrorToDll(ErrCode);
else
r->OpenResult=ERAR_BAD_ARCHIVE;
}
delete Data;
return nullptr;
}
r->Flags=0;
if (Data->Arc.Volume)
r->Flags|=ROADF_VOLUME;
if (Data->Arc.MainComment)
r->Flags|=ROADF_COMMENT;
if (Data->Arc.Locked)
r->Flags|=ROADF_LOCK;
if (Data->Arc.Solid)
r->Flags|=ROADF_SOLID;
if (Data->Arc.NewNumbering)
r->Flags|=ROADF_NEWNUMBERING;
if (Data->Arc.Signed)
r->Flags|=ROADF_SIGNED;
if (Data->Arc.Protected)
r->Flags|=ROADF_RECOVERY;
if (Data->Arc.Encrypted)
r->Flags|=ROADF_ENCHEADERS;
if (Data->Arc.FirstVolume)
r->Flags|=ROADF_FIRSTVOLUME;
std::wstring CmtDataW;
if (r->CmtBufSize!=0 && Data->Arc.GetComment(CmtDataW))
{
if (r->CmtBufW!=nullptr)
{
// CmtDataW.push_back(0);
size_t Size=wcslen(CmtDataW.data())+1;
r->CmtState=Size>r->CmtBufSize ? ERAR_SMALL_BUF:1;
r->CmtSize=(uint)Min(Size,r->CmtBufSize);
memcpy(r->CmtBufW,CmtDataW.data(),(r->CmtSize-1)*sizeof(*r->CmtBufW));
r->CmtBufW[r->CmtSize-1]=0;
}
else
if (r->CmtBuf!=NULL)
{
std::vector<char> CmtData(CmtDataW.size()*4+1);
WideToChar(&CmtDataW[0],&CmtData[0],CmtData.size()-1);
size_t Size=strlen(CmtData.data())+1;
r->CmtState=Size>r->CmtBufSize ? ERAR_SMALL_BUF:1;
r->CmtSize=(uint)Min(Size,r->CmtBufSize);
memcpy(r->CmtBuf,CmtData.data(),r->CmtSize-1);
r->CmtBuf[r->CmtSize-1]=0;
}
}
else
r->CmtState=r->CmtSize=0;
#ifdef PROPAGATE_MOTW
if (r->MarkOfTheWeb!=nullptr)
{
Data->Cmd.MotwAllFields=r->MarkOfTheWeb[0]=='1';
const wchar *Sep=wcschr(r->MarkOfTheWeb,'=');
if (r->MarkOfTheWeb[0]=='-')
Data->Cmd.MotwList.Reset();
else
Data->Cmd.GetBriefMaskList(Sep==nullptr ? L"*":Sep+1,Data->Cmd.MotwList);
}
#endif
Data->Extract.ExtractArchiveInit(Data->Arc);
return (HANDLE)Data;
}
catch (RAR_EXIT ErrCode)
{
if (Data!=NULL && Data->Cmd.DllError!=0)
r->OpenResult=Data->Cmd.DllError;
else
r->OpenResult=RarErrorToDll(ErrCode);
if (Data != NULL)
delete Data;
return NULL;
}
catch (std::bad_alloc&) // Catch 'new' exception.
{
r->OpenResult=ERAR_NO_MEMORY;
if (Data != NULL)
delete Data;
}
return NULL; // To make compilers happy.
}
int PASCAL RARCloseArchive(HANDLE hArcData)
{
DataSet *Data=(DataSet *)hArcData;
try
{
bool Success=Data==NULL ? false:Data->Arc.Close();
delete Data;
return Success ? ERAR_SUCCESS : ERAR_ECLOSE;
}
catch (RAR_EXIT ErrCode)
{
return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrCode);
}
}
int PASCAL RARReadHeader(HANDLE hArcData,struct RARHeaderData *D)
{
struct RARHeaderDataEx X{};
int Code=RARReadHeaderEx(hArcData,&X);
strncpyz(D->ArcName,X.ArcName,ASIZE(D->ArcName));
strncpyz(D->FileName,X.FileName,ASIZE(D->FileName));
D->Flags=X.Flags;
D->PackSize=X.PackSize;
D->UnpSize=X.UnpSize;
D->HostOS=X.HostOS;
D->FileCRC=X.FileCRC;
D->FileTime=X.FileTime;
D->UnpVer=X.UnpVer;
D->Method=X.Method;
D->FileAttr=X.FileAttr;
D->CmtSize=0;
D->CmtState=0;
return Code;
}
int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *D)
{
DataSet *Data=(DataSet *)hArcData;
try
{
if ((Data->HeaderSize=(int)Data->Arc.SearchBlock(HEAD_FILE))<=0)
{
if (Data->Arc.Volume && Data->Arc.GetHeaderType()==HEAD_ENDARC &&
Data->Arc.EndArcHead.NextVolume)
if (MergeArchive(Data->Arc,NULL,false,'L'))
{
Data->Arc.Seek(Data->Arc.CurBlockPos,SEEK_SET);
return RARReadHeaderEx(hArcData,D);
}
else
return ERAR_EOPEN;
if (Data->Arc.BrokenHeader)
return ERAR_BAD_DATA;
// Might be necessary if RARSetPassword is still called instead of
// open callback for RAR5 archives and if password is invalid.
if (Data->Arc.FailedHeaderDecryption)
return ERAR_BAD_PASSWORD;
return ERAR_END_ARCHIVE;
}
FileHeader *hd=&Data->Arc.FileHead;
if (Data->OpenMode==RAR_OM_LIST && hd->SplitBefore)
{
int Code=RARProcessFile(hArcData,RAR_SKIP,NULL,NULL);
if (Code==0)
return RARReadHeaderEx(hArcData,D);
else
return Code;
}
wcsncpyz(D->ArcNameW,Data->Arc.FileName.c_str(),ASIZE(D->ArcNameW));
WideToChar(D->ArcNameW,D->ArcName,ASIZE(D->ArcName));
if (D->ArcNameEx!=nullptr)
wcsncpyz(D->ArcNameEx,Data->Arc.FileName.c_str(),D->ArcNameExSize);
wcsncpyz(D->FileNameW,hd->FileName.c_str(),ASIZE(D->FileNameW));
WideToChar(D->FileNameW,D->FileName,ASIZE(D->FileName));
#ifdef _WIN_ALL
CharToOemA(D->FileName,D->FileName);
#endif
if (D->FileNameEx!=nullptr)
wcsncpyz(D->FileNameEx,hd->FileName.c_str(),D->FileNameExSize);
D->Flags=0;
if (hd->SplitBefore)
D->Flags|=RHDF_SPLITBEFORE;
if (hd->SplitAfter)
D->Flags|=RHDF_SPLITAFTER;
if (hd->Encrypted)
D->Flags|=RHDF_ENCRYPTED;
if (hd->Solid)
D->Flags|=RHDF_SOLID;
if (hd->Dir)
D->Flags|=RHDF_DIRECTORY;
D->PackSize=uint(hd->PackSize & 0xffffffff);
D->PackSizeHigh=uint(hd->PackSize>>32);
D->UnpSize=uint(hd->UnpSize & 0xffffffff);
D->UnpSizeHigh=uint(hd->UnpSize>>32);
D->HostOS=hd->HSType==HSYS_WINDOWS ? HOST_WIN32:HOST_UNIX;
D->UnpVer=Data->Arc.FileHead.UnpVer;
D->FileCRC=hd->FileHash.CRC32;
D->FileTime=hd->mtime.GetDos();
uint64 MRaw=hd->mtime.GetWin();
D->MtimeLow=(uint)MRaw;
D->MtimeHigh=(uint)(MRaw>>32);
uint64 CRaw=hd->ctime.GetWin();
D->CtimeLow=(uint)CRaw;
D->CtimeHigh=(uint)(CRaw>>32);
uint64 ARaw=hd->atime.GetWin();
D->AtimeLow=(uint)ARaw;
D->AtimeHigh=(uint)(ARaw>>32);
D->Method=hd->Method+0x30;
D->FileAttr=hd->FileAttr;
D->CmtSize=0;
D->CmtState=0;
D->DictSize=uint(hd->WinSize/1024);
switch (hd->FileHash.Type)
{
case HASH_RAR14:
case HASH_CRC32:
D->HashType=RAR_HASH_CRC32;
break;
case HASH_BLAKE2:
D->HashType=RAR_HASH_BLAKE2;
memcpy(D->Hash,hd->FileHash.Digest,BLAKE2_DIGEST_SIZE);
break;
default:
D->HashType=RAR_HASH_NONE;
break;
}
D->RedirType=hd->RedirType;
// RedirNameSize sanity check is useful in case some developer
// did not initialize Reserved area with 0 as required in docs.
// We have taken 'Redir*' fields from Reserved area. We may remove
// this RedirNameSize check sometimes later.
if (hd->RedirType!=FSREDIR_NONE && D->RedirName!=NULL &&
D->RedirNameSize>0 && D->RedirNameSize<100000)
wcsncpyz(D->RedirName,hd->RedirName.c_str(),D->RedirNameSize);
D->DirTarget=hd->DirTarget;
}
catch (RAR_EXIT ErrCode)
{
return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrCode);
}
return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrHandler.GetErrorCode());
}
int PASCAL ProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName,wchar *DestPathW,wchar *DestNameW)
{
DataSet *Data=(DataSet *)hArcData;
try
{
Data->Cmd.DllError=0;
if (Data->OpenMode==RAR_OM_LIST || Data->OpenMode==RAR_OM_LIST_INCSPLIT ||
Operation==RAR_SKIP && !Data->Arc.Solid)
{
if (Data->Arc.Volume && Data->Arc.GetHeaderType()==HEAD_FILE &&
Data->Arc.FileHead.SplitAfter)
if (MergeArchive(Data->Arc,NULL,false,'L'))
{
Data->Arc.Seek(Data->Arc.CurBlockPos,SEEK_SET);
return ERAR_SUCCESS;
}
else
return ERAR_EOPEN;
Data->Arc.SeekToNext();
}
else
{
Data->Cmd.DllOpMode=Operation;
Data->Cmd.ExtrPath.clear();
Data->Cmd.DllDestName.clear();
if (DestPath!=NULL)
{
std::string ExtrPathA=DestPath;
#ifdef _WIN_ALL
// We must not apply OemToCharBuffA directly to DestPath,
// because we do not know DestPath length and OemToCharBuffA
// does not stop at 0.
OemToExt(ExtrPathA,ExtrPathA);
#endif
CharToWide(ExtrPathA,Data->Cmd.ExtrPath);
AddEndSlash(Data->Cmd.ExtrPath);
}
if (DestName!=NULL)
{
std::string DestNameA=DestName;
#ifdef _WIN_ALL
// We must not apply OemToCharBuffA directly to DestName,
// because we do not know DestName length and OemToCharBuffA
// does not stop at 0.
OemToExt(DestNameA,DestNameA);
#endif
CharToWide(DestNameA,Data->Cmd.DllDestName);
}
if (DestPathW!=NULL)
{
Data->Cmd.ExtrPath=DestPathW;
AddEndSlash(Data->Cmd.ExtrPath);
}
if (DestNameW!=NULL)
Data->Cmd.DllDestName=DestNameW;
Data->Cmd.Command=Operation==RAR_EXTRACT ? L"X":L"T";
Data->Cmd.Test=Operation!=RAR_EXTRACT;
bool Repeat=false;
Data->Extract.ExtractCurrentFile(Data->Arc,Data->HeaderSize,Repeat);
// Now we process extra file information if any.
// It is important to do it in the same ProcessFile(), because caller
// app can rely on this behavior, for example, to overwrite
// the extracted Mark of the Web with propagated from archive
// immediately after ProcessFile() call.
//
// Archive can be closed if we process volumes, next volume is missing
// and current one is already removed or deleted. So we need to check
// if archive is still open to avoid calling file operations on
// the invalid file handle. Some of our file operations like Seek()
// process such invalid handle correctly, some not.
while (Data->Arc.IsOpened() && Data->Arc.ReadHeader()!=0 &&
Data->Arc.GetHeaderType()==HEAD_SERVICE)
{
Data->Extract.ExtractCurrentFile(Data->Arc,Data->HeaderSize,Repeat);
Data->Arc.SeekToNext();
}
Data->Arc.Seek(Data->Arc.CurBlockPos,SEEK_SET);
}
}
catch (std::bad_alloc&)
{
return ERAR_NO_MEMORY;
}
catch (RAR_EXIT ErrCode)
{
return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrCode);
}
return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrHandler.GetErrorCode());
}
int PASCAL RARProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName)
{
return ProcessFile(hArcData,Operation,DestPath,DestName,NULL,NULL);
}
int PASCAL RARProcessFileW(HANDLE hArcData,int Operation,wchar *DestPath,wchar *DestName)
{
return ProcessFile(hArcData,Operation,NULL,NULL,DestPath,DestName);
}
void PASCAL RARSetChangeVolProc(HANDLE hArcData,CHANGEVOLPROC ChangeVolProc)
{
DataSet *Data=(DataSet *)hArcData;
Data->Cmd.ChangeVolProc=ChangeVolProc;
}
void PASCAL RARSetCallback(HANDLE hArcData,UNRARCALLBACK Callback,LPARAM UserData)
{
DataSet *Data=(DataSet *)hArcData;
Data->Cmd.Callback=Callback;
Data->Cmd.UserData=UserData;
}
void PASCAL RARSetProcessDataProc(HANDLE hArcData,PROCESSDATAPROC ProcessDataProc)
{
DataSet *Data=(DataSet *)hArcData;
Data->Cmd.ProcessDataProc=ProcessDataProc;
}
void PASCAL RARSetPassword(HANDLE hArcData,char *Password)
{
#ifndef RAR_NOCRYPT
DataSet *Data=(DataSet *)hArcData;
wchar PasswordW[MAXPASSWORD];
CharToWide(Password,PasswordW,ASIZE(PasswordW));
Data->Cmd.Password.Set(PasswordW);
cleandata(PasswordW,sizeof(PasswordW));
#endif
}
int PASCAL RARGetDllVersion()
{
return RAR_DLL_VERSION;
}
static int RarErrorToDll(RAR_EXIT ErrCode)
{
switch(ErrCode)
{
case RARX_FATAL:
case RARX_READ:
return ERAR_EREAD;
case RARX_CRC:
return ERAR_BAD_DATA;
case RARX_WRITE:
return ERAR_EWRITE;
case RARX_OPEN:
return ERAR_EOPEN;
case RARX_CREATE:
return ERAR_ECREATE;
case RARX_MEMORY:
return ERAR_NO_MEMORY;
case RARX_BADPWD:
return ERAR_BAD_PASSWORD;
case RARX_SUCCESS:
return ERAR_SUCCESS; // 0.
case RARX_BADARC:
return ERAR_BAD_ARCHIVE;
default:
return ERAR_UNKNOWN;
}
}
+13
View File
@@ -0,0 +1,13 @@
EXPORTS
RAROpenArchive
RAROpenArchiveEx
RARCloseArchive
RARReadHeader
RARReadHeaderEx
RARProcessFile
RARProcessFileW
RARSetCallback
RARSetChangeVolProc
RARSetProcessDataProc
RARSetPassword
RARGetDllVersion
+195
View File
@@ -0,0 +1,195 @@
#ifndef _UNRAR_DLL_
#define _UNRAR_DLL_
#pragma pack(push, 1)
#define ERAR_SUCCESS 0
#define ERAR_END_ARCHIVE 10
#define ERAR_NO_MEMORY 11
#define ERAR_BAD_DATA 12
#define ERAR_BAD_ARCHIVE 13
#define ERAR_UNKNOWN_FORMAT 14
#define ERAR_EOPEN 15
#define ERAR_ECREATE 16
#define ERAR_ECLOSE 17
#define ERAR_EREAD 18
#define ERAR_EWRITE 19
#define ERAR_SMALL_BUF 20
#define ERAR_UNKNOWN 21
#define ERAR_MISSING_PASSWORD 22
#define ERAR_EREFERENCE 23
#define ERAR_BAD_PASSWORD 24
#define ERAR_LARGE_DICT 25
#define RAR_OM_LIST 0
#define RAR_OM_EXTRACT 1
#define RAR_OM_LIST_INCSPLIT 2
#define RAR_SKIP 0
#define RAR_TEST 1
#define RAR_EXTRACT 2
#define RAR_VOL_ASK 0
#define RAR_VOL_NOTIFY 1
#define RAR_DLL_VERSION 9
#define RAR_HASH_NONE 0
#define RAR_HASH_CRC32 1
#define RAR_HASH_BLAKE2 2
#ifdef _UNIX
#define CALLBACK
#define PASCAL
#define LONG long
#define HANDLE void *
#define LPARAM long
#define UINT unsigned int
#endif
#define RHDF_SPLITBEFORE 0x01
#define RHDF_SPLITAFTER 0x02
#define RHDF_ENCRYPTED 0x04
#define RHDF_SOLID 0x10
#define RHDF_DIRECTORY 0x20
struct RARHeaderData
{
char ArcName[260];
char FileName[260];
unsigned int Flags;
unsigned int PackSize;
unsigned int UnpSize;
unsigned int HostOS;
unsigned int FileCRC;
unsigned int FileTime;
unsigned int UnpVer;
unsigned int Method;
unsigned int FileAttr;
char *CmtBuf;
unsigned int CmtBufSize;
unsigned int CmtSize;
unsigned int CmtState;
};
struct RARHeaderDataEx
{
char ArcName[1024];
wchar_t ArcNameW[1024];
char FileName[1024];
wchar_t FileNameW[1024];
unsigned int Flags;
unsigned int PackSize;
unsigned int PackSizeHigh;
unsigned int UnpSize;
unsigned int UnpSizeHigh;
unsigned int HostOS;
unsigned int FileCRC;
unsigned int FileTime;
unsigned int UnpVer;
unsigned int Method;
unsigned int FileAttr;
char *CmtBuf;
unsigned int CmtBufSize;
unsigned int CmtSize;
unsigned int CmtState;
unsigned int DictSize;
unsigned int HashType;
char Hash[32];
unsigned int RedirType;
wchar_t *RedirName;
unsigned int RedirNameSize;
unsigned int DirTarget;
unsigned int MtimeLow;
unsigned int MtimeHigh;
unsigned int CtimeLow;
unsigned int CtimeHigh;
unsigned int AtimeLow;
unsigned int AtimeHigh;
wchar_t *ArcNameEx;
unsigned int ArcNameExSize;
wchar_t *FileNameEx;
unsigned int FileNameExSize;
unsigned int Reserved[982];
};
struct RAROpenArchiveData
{
char *ArcName;
unsigned int OpenMode;
unsigned int OpenResult;
char *CmtBuf;
unsigned int CmtBufSize;
unsigned int CmtSize;
unsigned int CmtState;
};
typedef int (CALLBACK *UNRARCALLBACK)(UINT msg,LPARAM UserData,LPARAM P1,LPARAM P2);
#define ROADF_VOLUME 0x0001
#define ROADF_COMMENT 0x0002
#define ROADF_LOCK 0x0004
#define ROADF_SOLID 0x0008
#define ROADF_NEWNUMBERING 0x0010
#define ROADF_SIGNED 0x0020
#define ROADF_RECOVERY 0x0040
#define ROADF_ENCHEADERS 0x0080
#define ROADF_FIRSTVOLUME 0x0100
#define ROADOF_KEEPBROKEN 0x0001
struct RAROpenArchiveDataEx
{
char *ArcName;
wchar_t *ArcNameW;
unsigned int OpenMode;
unsigned int OpenResult;
char *CmtBuf;
unsigned int CmtBufSize;
unsigned int CmtSize;
unsigned int CmtState;
unsigned int Flags;
UNRARCALLBACK Callback;
LPARAM UserData;
unsigned int OpFlags;
wchar_t *CmtBufW;
wchar_t *MarkOfTheWeb;
unsigned int Reserved[23];
};
enum UNRARCALLBACK_MESSAGES {
UCM_CHANGEVOLUME,UCM_PROCESSDATA,UCM_NEEDPASSWORD,UCM_CHANGEVOLUMEW,
UCM_NEEDPASSWORDW,UCM_LARGEDICT
};
typedef int (PASCAL *CHANGEVOLPROC)(char *ArcName,int Mode);
typedef int (PASCAL *PROCESSDATAPROC)(unsigned char *Addr,int Size);
#ifdef __cplusplus
extern "C" {
#endif
HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *ArchiveData);
HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *ArchiveData);
int PASCAL RARCloseArchive(HANDLE hArcData);
int PASCAL RARReadHeader(HANDLE hArcData,struct RARHeaderData *HeaderData);
int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *HeaderData);
int PASCAL RARProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName);
int PASCAL RARProcessFileW(HANDLE hArcData,int Operation,wchar_t *DestPath,wchar_t *DestName);
void PASCAL RARSetCallback(HANDLE hArcData,UNRARCALLBACK Callback,LPARAM UserData);
void PASCAL RARSetChangeVolProc(HANDLE hArcData,CHANGEVOLPROC ChangeVolProc);
void PASCAL RARSetProcessDataProc(HANDLE hArcData,PROCESSDATAPROC ProcessDataProc);
void PASCAL RARSetPassword(HANDLE hArcData,char *Password);
int PASCAL RARGetDllVersion();
#ifdef __cplusplus
}
#endif
#pragma pack(pop)
#endif
+28
View File
@@ -0,0 +1,28 @@
#include <windows.h>
#include <commctrl.h>
VS_VERSION_INFO VERSIONINFO
FILEVERSION 7, 20, 1, 1764
PRODUCTVERSION 7, 20, 1, 1764
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
{
BLOCK "StringFileInfo"
{
BLOCK "040904E4"
{
VALUE "CompanyName", "Alexander Roshal\0"
VALUE "ProductName", "RAR decompression library\0"
VALUE "FileDescription", "RAR decompression library\0"
VALUE "FileVersion", "7.20.1\0"
VALUE "ProductVersion", "7.20.1\0"
VALUE "LegalCopyright", "Copyright © Alexander Roshal 1993-2025\0"
VALUE "OriginalFilename", "Unrar.dll\0"
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x0409, 0x04E4
}
}
+13
View File
@@ -0,0 +1,13 @@
EXPORTS
RAROpenArchive
RAROpenArchiveEx
RARCloseArchive
RARReadHeader
RARReadHeaderEx
RARProcessFile
RARProcessFileW
RARSetCallback
RARSetChangeVolProc
RARSetProcessDataProc
; RARSetPassword
RARGetDllVersion
+77
View File
@@ -0,0 +1,77 @@
#include "rar.hpp"
EncodeFileName::EncodeFileName()
{
Flags=0;
FlagBits=0;
FlagsPos=0;
DestSize=0;
}
void EncodeFileName::Decode(const char *Name,size_t NameSize,
const byte *EncName,size_t EncSize,
std::wstring &NameW)
{
size_t EncPos=0,DecPos=0;
byte HighByte=EncPos<EncSize ? EncName[EncPos++] : 0;
while (EncPos<EncSize)
{
if (FlagBits==0)
{
Flags=EncName[EncPos++];
FlagBits=8;
}
switch(Flags>>6)
{
case 0:
if (EncPos>=EncSize)
break;
// We need DecPos also for ASCII "Name", so resize() instead of push_back().
NameW.resize(DecPos+1);
NameW[DecPos++]=EncName[EncPos++];
break;
case 1:
if (EncPos>=EncSize)
break;
NameW.resize(DecPos+1);
NameW[DecPos++]=EncName[EncPos++]+(HighByte<<8);
break;
case 2:
if (EncPos+1>=EncSize)
break;
NameW.resize(DecPos+1);
NameW[DecPos++]=EncName[EncPos]+(EncName[EncPos+1]<<8);
EncPos+=2;
break;
case 3:
{
if (EncPos>=EncSize)
break;
int Length=EncName[EncPos++];
if ((Length & 0x80)!=0)
{
if (EncPos>=EncSize)
break;
byte Correction=EncName[EncPos++];
for (Length=(Length&0x7f)+2;Length>0 && DecPos<NameSize;Length--,DecPos++)
{
NameW.resize(DecPos+1);
NameW[DecPos]=((Name[DecPos]+Correction)&0xff)+(HighByte<<8);
}
}
else
for (Length+=2;Length>0 && DecPos<NameSize;Length--,DecPos++)
{
NameW.resize(DecPos+1);
NameW[DecPos]=Name[DecPos];
}
}
break;
}
Flags<<=2;
FlagBits-=2;
}
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef _RAR_ENCNAME_
#define _RAR_ENCNAME_
class EncodeFileName
{
private:
void AddFlags(byte Value,std::vector<byte> &EncName);
byte Flags;
uint FlagBits;
size_t FlagsPos;
size_t DestSize;
public:
EncodeFileName();
void Encode(const std::string &Name,const std::wstring &NameW,std::vector<byte> &EncName);
void Decode(const char *Name,size_t NameSize,const byte *EncName,size_t EncSize,std::wstring &NameW);
};
#endif
+433
View File
@@ -0,0 +1,433 @@
#include "rar.hpp"
ErrorHandler::ErrorHandler()
{
Clean();
}
void ErrorHandler::Clean()
{
ExitCode=RARX_SUCCESS;
ErrCount=0;
EnableBreak=true;
Silent=false;
UserBreak=false;
MainExit=false;
DisableShutdown=false;
ReadErrIgnoreAll=false;
}
void ErrorHandler::MemoryError()
{
MemoryErrorMsg();
Exit(RARX_MEMORY);
}
void ErrorHandler::OpenError(const std::wstring &FileName)
{
#ifndef SILENT
OpenErrorMsg(FileName);
Exit(RARX_OPEN);
#endif
}
void ErrorHandler::CloseError(const std::wstring &FileName)
{
if (!UserBreak)
{
uiMsg(UIERROR_FILECLOSE,FileName);
SysErrMsg();
}
// We must not call Exit and throw an exception here, because this function
// is called from File object destructor and can be invoked when stack
// unwinding while handling another exception. Throwing a new exception
// when stack unwinding is prohibited and terminates a program.
// If necessary, we can check std::uncaught_exception() before throw.
SetErrorCode(RARX_FATAL);
}
void ErrorHandler::ReadError(const std::wstring &FileName)
{
#ifndef SILENT
ReadErrorMsg(FileName);
#endif
#if !defined(SILENT) || defined(RARDLL)
Exit(RARX_READ);
#endif
}
void ErrorHandler::AskRepeatRead(const std::wstring &FileName,bool &Ignore,bool &Retry,bool &Quit)
{
SetErrorCode(RARX_READ);
#if !defined(SILENT) && !defined(SFX_MODULE)
if (!Silent)
{
uiMsg(UIERROR_FILEREAD,L"",FileName);
SysErrMsg();
if (ReadErrIgnoreAll)
Ignore=true;
else
{
bool All=false;
uiAskRepeatRead(FileName,Ignore,All,Retry,Quit);
if (All)
ReadErrIgnoreAll=Ignore=true;
if (Quit) // Disable shutdown if user select Quit in read error prompt.
DisableShutdown=true;
}
return;
}
#endif
Ignore=true; // Saving the file part for -y or -inul or "Ignore all" choice.
}
void ErrorHandler::WriteError(const std::wstring &ArcName,const std::wstring &FileName)
{
#ifndef SILENT
WriteErrorMsg(ArcName,FileName);
#endif
#if !defined(SILENT) || defined(RARDLL)
Exit(RARX_WRITE);
#endif
}
#ifdef _WIN_ALL
void ErrorHandler::WriteErrorFAT(const std::wstring &FileName)
{
SysErrMsg();
uiMsg(UIERROR_NTFSREQUIRED,FileName);
#if !defined(SILENT) && !defined(SFX_MODULE) || defined(RARDLL)
Exit(RARX_WRITE);
#endif
}
#endif
bool ErrorHandler::AskRepeatWrite(const std::wstring &FileName,bool DiskFull)
{
#ifndef SILENT
if (!Silent)
{
// We do not display "repeat write" prompt in Android, so we do not
// need the matching system error message.
SysErrMsg();
bool Repeat=uiAskRepeatWrite(FileName,DiskFull);
if (!Repeat) // Disable shutdown if user pressed Cancel in error dialog.
DisableShutdown=true;
return Repeat;
}
#endif
return false;
}
void ErrorHandler::SeekError(const std::wstring &FileName)
{
if (!UserBreak)
{
uiMsg(UIERROR_FILESEEK,FileName);
SysErrMsg();
}
#if !defined(SILENT) || defined(RARDLL)
Exit(RARX_FATAL);
#endif
}
void ErrorHandler::GeneralErrMsg(const wchar *fmt,...)
{
#ifndef RARDLL
va_list arglist;
va_start(arglist,fmt);
std::wstring Msg=vwstrprintf(fmt,arglist);
uiMsg(UIERROR_GENERALERRMSG,Msg);
SysErrMsg();
va_end(arglist);
#endif
}
void ErrorHandler::MemoryErrorMsg()
{
uiMsg(UIERROR_MEMORY);
SetErrorCode(RARX_MEMORY);
}
void ErrorHandler::OpenErrorMsg(const std::wstring &FileName)
{
OpenErrorMsg(L"",FileName);
}
void ErrorHandler::OpenErrorMsg(const std::wstring &ArcName,const std::wstring &FileName)
{
uiMsg(UIERROR_FILEOPEN,ArcName,FileName);
SysErrMsg();
SetErrorCode(RARX_OPEN);
// Keep GUI responsive if many files cannot be opened when archiving.
// Call after SysErrMsg to avoid modifying the error code and SysErrMsg text.
Wait();
}
void ErrorHandler::CreateErrorMsg(const std::wstring &FileName)
{
CreateErrorMsg(L"",FileName);
}
void ErrorHandler::CreateErrorMsg(const std::wstring &ArcName,const std::wstring &FileName)
{
uiMsg(UIERROR_FILECREATE,ArcName,FileName);
SysErrMsg();
SetErrorCode(RARX_CREATE);
}
void ErrorHandler::ReadErrorMsg(const std::wstring &FileName)
{
ReadErrorMsg(L"",FileName);
}
void ErrorHandler::ReadErrorMsg(const std::wstring &ArcName,const std::wstring &FileName)
{
uiMsg(UIERROR_FILEREAD,ArcName,FileName);
SysErrMsg();
SetErrorCode(RARX_READ);
}
void ErrorHandler::WriteErrorMsg(const std::wstring &ArcName,const std::wstring &FileName)
{
uiMsg(UIERROR_FILEWRITE,ArcName,FileName);
SysErrMsg();
SetErrorCode(RARX_WRITE);
}
void ErrorHandler::ArcBrokenMsg(const std::wstring &ArcName)
{
uiMsg(UIERROR_ARCBROKEN,ArcName);
SetErrorCode(RARX_CRC);
}
void ErrorHandler::ChecksumFailedMsg(const std::wstring &ArcName,const std::wstring &FileName)
{
uiMsg(UIERROR_CHECKSUM,ArcName,FileName);
SetErrorCode(RARX_CRC);
}
void ErrorHandler::UnknownMethodMsg(const std::wstring &ArcName,const std::wstring &FileName)
{
uiMsg(UIERROR_UNKNOWNMETHOD,ArcName,FileName);
ErrHandler.SetErrorCode(RARX_FATAL);
}
void ErrorHandler::Exit(RAR_EXIT ExitCode)
{
uiAlarm(UIALARM_ERROR);
Throw(ExitCode);
}
void ErrorHandler::SetErrorCode(RAR_EXIT Code)
{
switch(Code)
{
case RARX_WARNING:
case RARX_USERBREAK:
if (ExitCode==RARX_SUCCESS)
ExitCode=Code;
break;
case RARX_CRC:
// 2025.10.25: RARX_OPEN is set if next volume is missing.
if (ExitCode!=RARX_BADPWD && ExitCode!=RARX_OPEN)
ExitCode=Code;
break;
case RARX_FATAL:
if (ExitCode==RARX_SUCCESS || ExitCode==RARX_WARNING)
ExitCode=RARX_FATAL;
break;
default:
ExitCode=Code;
break;
}
ErrCount++;
}
#ifdef _WIN_ALL
BOOL __stdcall ProcessSignal(DWORD SigType)
#else
#if defined(__sun)
extern "C"
#endif
void _stdfunction ProcessSignal(int SigType)
#endif
{
#ifdef _WIN_ALL
// When a console application is run as a service, this allows the service
// to continue running after the user logs off.
if (SigType==CTRL_LOGOFF_EVENT)
return TRUE;
#endif
ErrHandler.UserBreak=true;
ErrHandler.SetDisableShutdown();
mprintf(St(MBreak));
#ifdef _WIN_ALL
// Let the main thread to handle 'throw' and destroy file objects.
for (uint I=0;!ErrHandler.MainExit && I<50;I++)
Sleep(100);
#if defined(USE_RC) && !defined(SFX_MODULE) && !defined(RARDLL)
ExtRes.UnloadDLL();
#endif
exit(RARX_USERBREAK);
#endif
#ifdef _UNIX
static uint BreakCount=0;
// User continues to press Ctrl+C, exit immediately without cleanup.
if (++BreakCount>1)
exit(RARX_USERBREAK);
// Otherwise return from signal handler and let Wait() function to close
// files and quit. We cannot use the same approach as in Windows,
// because Unix signal handler can block execution of our main code.
#endif
#if defined(_WIN_ALL) && !defined(_MSC_VER)
// Never reached, just to avoid a compiler warning
return TRUE;
#endif
}
void ErrorHandler::SetSignalHandlers(bool Enable)
{
EnableBreak=Enable;
#ifdef _WIN_ALL
SetConsoleCtrlHandler(Enable ? ProcessSignal:NULL,TRUE);
#else
signal(SIGINT,Enable ? ProcessSignal:SIG_IGN);
signal(SIGTERM,Enable ? ProcessSignal:SIG_IGN);
#endif
}
void ErrorHandler::Throw(RAR_EXIT Code)
{
if (Code==RARX_USERBREAK && !EnableBreak)
return;
#if !defined(SILENT)
if (Code!=RARX_SUCCESS)
if (Code==RARX_USERERROR) // Do not write "aborted" when just displaying the online help.
mprintf(L"\n"); // For consistency with other errors, which print the final "\n".
else
mprintf(L"\n%s\n",St(MProgAborted));
#endif
SetErrorCode(Code);
throw Code;
}
bool ErrorHandler::GetSysErrMsg(std::wstring &Msg)
{
#ifndef SILENT
#ifdef _WIN_ALL
int ErrType=GetLastError();
if (ErrType!=0)
{
wchar *Buf=nullptr;
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|
FORMAT_MESSAGE_IGNORE_INSERTS|FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL,ErrType,MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
(LPTSTR)&Buf,0,NULL)!=0)
{
Msg=Buf;
LocalFree(Buf);
return true;
}
}
#endif
#ifdef _UNIX
if (errno!=0)
{
char *err=strerror(errno);
if (err!=NULL)
{
CharToWide(err,Msg);
return true;
}
}
#endif
#endif
return false;
}
void ErrorHandler::SysErrMsg()
{
#ifndef SILENT
std::wstring Msg;
if (!GetSysErrMsg(Msg))
return;
#ifdef _WIN_ALL
// Print string with \r\n as several strings to multiple lines.
size_t Pos=0;
while (Pos!=std::wstring::npos)
{
while (Msg[Pos]=='\r' || Msg[Pos]=='\n')
Pos++;
if (Pos==Msg.size())
break;
size_t EndPos=Msg.find_first_of(L"\r\n",Pos);
std::wstring CurMsg=Msg.substr(Pos,EndPos==std::wstring::npos ? EndPos:EndPos-Pos);
uiMsg(UIERROR_SYSERRMSG,CurMsg);
Pos=EndPos;
}
#endif
#ifdef _UNIX
uiMsg(UIERROR_SYSERRMSG,Msg);
#endif
#endif
}
int ErrorHandler::GetSystemErrorCode()
{
#ifdef _WIN_ALL
return GetLastError();
#else
return errno;
#endif
}
void ErrorHandler::SetSystemErrorCode(int Code)
{
#ifdef _WIN_ALL
SetLastError(Code);
#else
errno=Code;
#endif
}
+76
View File
@@ -0,0 +1,76 @@
#ifndef _RAR_ERRHANDLER_
#define _RAR_ERRHANDLER_
enum RAR_EXIT // RAR exit code.
{
RARX_SUCCESS = 0,
RARX_WARNING = 1,
RARX_FATAL = 2,
RARX_CRC = 3,
RARX_LOCK = 4,
RARX_WRITE = 5,
RARX_OPEN = 6,
RARX_USERERROR = 7,
RARX_MEMORY = 8,
RARX_CREATE = 9,
RARX_NOFILES = 10,
RARX_BADPWD = 11,
RARX_READ = 12,
RARX_BADARC = 13,
RARX_USERBREAK = 255
};
class ErrorHandler
{
private:
RAR_EXIT ExitCode;
uint ErrCount;
bool EnableBreak;
bool Silent;
bool DisableShutdown; // Shutdown is not suitable after last error.
bool ReadErrIgnoreAll;
public:
ErrorHandler();
void Clean();
void MemoryError();
void OpenError(const std::wstring &FileName);
void CloseError(const std::wstring &FileName);
void ReadError(const std::wstring &FileName);
void AskRepeatRead(const std::wstring &FileName,bool &Ignore,bool &Retry,bool &Quit);
void WriteError(const std::wstring &ArcName,const std::wstring &FileName);
void WriteErrorFAT(const std::wstring &FileName);
bool AskRepeatWrite(const std::wstring &FileName,bool DiskFull);
void SeekError(const std::wstring &FileName);
void GeneralErrMsg(const wchar *fmt,...);
void MemoryErrorMsg();
void OpenErrorMsg(const std::wstring &FileName);
void OpenErrorMsg(const std::wstring &ArcName,const std::wstring &FileName);
void CreateErrorMsg(const std::wstring &FileName);
void CreateErrorMsg(const std::wstring &ArcName,const std::wstring &FileName);
void ReadErrorMsg(const std::wstring &FileName);
void ReadErrorMsg(const std::wstring &ArcName,const std::wstring &FileName);
void WriteErrorMsg(const std::wstring &ArcName,const std::wstring &FileName);
void ArcBrokenMsg(const std::wstring &ArcName);
void ChecksumFailedMsg(const std::wstring &ArcName,const std::wstring &FileName);
void UnknownMethodMsg(const std::wstring &ArcName,const std::wstring &FileName);
void Exit(RAR_EXIT ExitCode);
void SetErrorCode(RAR_EXIT Code);
RAR_EXIT GetErrorCode() {return ExitCode;}
uint GetErrorCount() {return ErrCount;}
void SetSignalHandlers(bool Enable);
void Throw(RAR_EXIT Code);
void SetSilent(bool Mode) {Silent=Mode;}
bool GetSysErrMsg(std::wstring &Msg);
void SysErrMsg();
int GetSystemErrorCode();
void SetSystemErrorCode(int Code);
void SetDisableShutdown() {DisableShutdown=true;}
bool IsShutdownEnabled() {return !DisableShutdown;}
bool UserBreak; // Ctrl+Break is pressed.
bool MainExit; // main() is completed.
};
#endif
+184
View File
@@ -0,0 +1,184 @@
#include "rar.hpp"
#include "hardlinks.cpp"
#include "win32stm.cpp"
#ifdef _WIN_ALL
#include "win32acl.cpp"
#include "win32lnk.cpp"
#endif
#ifdef _UNIX
#include "uowners.cpp"
#ifdef SAVE_LINKS
#include "ulinks.cpp"
#endif
#endif
// RAR2 service header extra records.
#ifndef SFX_MODULE
void SetExtraInfo20(CommandData *Cmd,Archive &Arc,const std::wstring &Name)
{
#ifdef _WIN_ALL
if (Cmd->Test)
return;
switch(Arc.SubBlockHead.SubType)
{
case NTACL_HEAD:
if (Cmd->ProcessOwners)
ExtractACL20(Arc,Name);
break;
case STREAM_HEAD:
ExtractStreams20(Arc,Name);
break;
}
#endif
}
#endif
// RAR3 and RAR5 service header extra records.
void SetExtraInfo(CommandData *Cmd,Archive &Arc,const std::wstring &Name)
{
#ifdef _UNIX
if (!Cmd->Test && Cmd->ProcessOwners && Arc.Format==RARFMT15 &&
Arc.SubHead.CmpName(SUBHEAD_TYPE_UOWNER))
ExtractUnixOwner30(Arc,Name.c_str());
#endif
#ifdef _WIN_ALL
if (!Cmd->Test && Cmd->ProcessOwners && Arc.SubHead.CmpName(SUBHEAD_TYPE_ACL))
ExtractACL(Arc,Name);
if (Arc.SubHead.CmpName(SUBHEAD_TYPE_STREAM))
ExtractStreams(Arc,Name,Cmd->Test);
#endif
}
// Extra data stored directly in file header.
void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,const std::wstring &Name)
{
#ifdef _UNIX
if (Cmd->ProcessOwners && Arc.Format==RARFMT50 && Arc.FileHead.UnixOwnerSet)
SetUnixOwner(Arc,Name);
#endif
}
// Calculate the number of path components except \. and \..
static int CalcAllowedDepth(const std::wstring &Name)
{
int AllowedDepth=0;
for (size_t I=0;I<Name.size();I++)
if (IsPathDiv(Name[I]))
{
bool Dot=Name[I+1]=='.' && (IsPathDiv(Name[I+2]) || Name[I+2]==0);
bool Dot2=Name[I+1]=='.' && Name[I+2]=='.' && (IsPathDiv(Name[I+3]) || Name[I+3]==0);
if (!Dot && !Dot2)
AllowedDepth++;
else
if (Dot2)
AllowedDepth--;
}
return AllowedDepth < 0 ? 0 : AllowedDepth;
}
// Check if all existing path components are directories and not links.
static bool LinkInPath(std::wstring Path)
{
if (Path.empty()) // So we can safely use Path.size()-1 below.
return false;
for (size_t I=Path.size()-1;I>0;I--)
if (IsPathDiv(Path[I]))
{
Path.erase(I);
FindData FD;
if (FindFile::FastFind(Path,&FD,true) && (FD.IsLink || !FD.IsDir))
return true;
}
return false;
}
bool IsRelativeSymlinkSafe(CommandData *Cmd,const std::wstring &SrcName,std::wstring PrepSrcName,const std::wstring &TargetName)
{
// Catch root dir based /path/file paths also as stuff like \\?\.
// Do not check PrepSrcName here, it can be root based if destination path
// is a root based.
if (IsFullRootPath(SrcName) || IsFullRootPath(TargetName))
return false;
// Number of ".." in link target.
int UpLevels=0;
for (uint Pos=0;Pos<TargetName.size();Pos++)
{
bool Dot2=TargetName[Pos]=='.' && TargetName[Pos+1]=='.' &&
(IsPathDiv(TargetName[Pos+2]) || TargetName[Pos+2]==0) &&
(Pos==0 || IsPathDiv(TargetName[Pos-1]));
if (Dot2)
UpLevels++;
}
// If link target includes "..", it must not have another links in its
// source path, because they can bypass our safety check. For example,
// suppose we extracted "lnk1" -> "." first and "lnk1/lnk2" -> ".." next
// or "dir/lnk1" -> ".." first, "dir/lnk1/lnk2" -> ".." next and
// file "dir/lnk1/lnk2/poc.txt" last.
// Do not confuse with link chains in target, this is in link source path.
// It is important for Windows too, though this check can be omitted
// if LinksToDirs is invoked in Windows as well.
if (UpLevels>0 && LinkInPath(PrepSrcName))
return false;
// We could check just prepared src name, but for extra safety
// we check both original (as from archive header) and prepared
// (after applying the destination path and -ep switches) names.
int AllowedDepth=CalcAllowedDepth(SrcName); // Original name depth.
// Remove the destination path from prepared name if any. We should not
// count the destination path depth, because the link target must point
// inside of this path, not outside of it.
size_t ExtrPathLength=Cmd->ExtrPath.size();
if (ExtrPathLength>0 && PrepSrcName.compare(0,ExtrPathLength,Cmd->ExtrPath)==0)
{
while (IsPathDiv(PrepSrcName[ExtrPathLength]))
ExtrPathLength++;
PrepSrcName.erase(0,ExtrPathLength);
}
int PrepAllowedDepth=CalcAllowedDepth(PrepSrcName);
return AllowedDepth>=UpLevels && PrepAllowedDepth>=UpLevels;
}
bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const std::wstring &LinkName,bool &UpLink)
{
// Returning true in Uplink indicates that link target might include ".."
// and enables additional checks. It is ok to falsely return true here,
// as it implies only the minor performance penalty. But we shall always
// return true for links with ".." in target for security reason.
UpLink=true; // Assume the target might include potentially unsafe "..".
#if defined(SAVE_LINKS) && defined(_UNIX) || defined(_WIN_ALL)
if (Arc.Format==RARFMT50) // For RAR5 archives we can check RedirName for both Unix and Windows.
UpLink=Arc.FileHead.RedirName.find(L"..")!=std::wstring::npos;
#endif
#if defined(SAVE_LINKS) && defined(_UNIX)
// For RAR 3.x archives we process links even in test mode to skip link data.
if (Arc.Format==RARFMT15)
return ExtractUnixLink30(Cmd,DataIO,Arc,LinkName.c_str(),UpLink);
if (Arc.Format==RARFMT50)
return ExtractUnixLink50(Cmd,LinkName.c_str(),&Arc.FileHead);
#elif defined(_WIN_ALL)
// RAR 5.0 archives store link information in file header, so there is
// no need to additionally test it if we do not create a file.
if (Arc.Format==RARFMT50)
return CreateReparsePoint(Cmd,LinkName.c_str(),&Arc.FileHead);
#endif
return false;
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef _RAR_EXTINFO_
#define _RAR_EXTINFO_
bool IsRelativeSymlinkSafe(CommandData *Cmd,const std::wstring &SrcName,std::wstring PrepSrcName,const std::wstring &TargetName);
bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const std::wstring &LinkName,bool &UpLink);
#ifdef _UNIX
void SetUnixOwner(Archive &Arc,const std::wstring &FileName);
#endif
bool ExtractHardlink(CommandData *Cmd,const std::wstring &NameNew,const std::wstring &NameExisting);
std::wstring GetStreamNameNTFS(Archive &Arc);
void SetExtraInfo20(CommandData *Cmd,Archive &Arc,const std::wstring &Name);
void SetExtraInfo(CommandData *Cmd,Archive &Arc,const std::wstring &Name);
void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,const std::wstring &Name);
#endif
+1768
View File
File diff suppressed because it is too large. Load diff
+96
View File
@@ -0,0 +1,96 @@
#ifndef _RAR_EXTRACT_
#define _RAR_EXTRACT_
enum EXTRACT_ARC_CODE {EXTRACT_ARC_NEXT,EXTRACT_ARC_REPEAT};
class CmdExtract
{
private:
struct ExtractRef
{
std::wstring RefName;
std::wstring TmpName;
uint64 RefCount;
};
std::vector<ExtractRef> RefList;
struct AnalyzeData
{
std::wstring StartName;
uint64 StartPos;
std::wstring EndName;
uint64 EndPos;
} Analyze;
bool ArcAnalyzed;
void FreeAnalyzeData();
EXTRACT_ARC_CODE ExtractArchive();
bool ExtractFileCopy(File &New,const std::wstring &ArcName,const std::wstring &RedirName,const std::wstring &NameNew,const std::wstring &NameExisting,int64 UnpSize);
void ExtrPrepareName(Archive &Arc,const std::wstring &ArcFileName,std::wstring &DestName);
#ifdef RARDLL
bool ExtrDllGetPassword();
#else
bool ExtrGetPassword(Archive &Arc,const std::wstring &ArcFileName,RarCheckPassword *CheckPwd);
#endif
#if defined(_WIN_ALL) && !defined(SFX_MODULE)
void ConvertDosPassword(Archive &Arc,SecPassword &DestPwd);
#endif
void ExtrCreateDir(Archive &Arc,const std::wstring &ArcFileName);
bool ExtrCreateFile(Archive &Arc,File &CurFile,bool WriteOnly);
bool CheckUnpVer(Archive &Arc,const std::wstring &ArcFileName);
#ifndef SFX_MODULE
void AnalyzeArchive(const std::wstring &ArcName,bool Volume,bool NewNumbering);
void GetFirstVolIfFullSet(const std::wstring &SrcName,bool NewNumbering,std::wstring &DestName);
#endif
bool CheckWinLimit(Archive &Arc,std::wstring &ArcFileName);
RarTime StartTime; // Time when extraction started.
CommandData *Cmd;
ComprDataIO DataIO;
Unpack *Unp;
unsigned long TotalFileCount;
unsigned long FileCount;
unsigned long MatchedArgs;
bool FirstFile;
bool AllMatchesExact;
bool ReconstructDone;
bool UseExactVolName;
// If any non-zero solid file was successfully unpacked before current.
// If true and if current encrypted file is broken, obviously
// the password is correct and we can report broken CRC without
// any wrong password hints.
bool AnySolidDataUnpackedWell;
std::wstring ArcName;
bool GlobalPassword;
bool PrevProcessed; // If previous file was successfully extracted or tested.
std::wstring DestFileName;
bool SuppressNoFilesMessage;
// In Windows it is set to true if at least one symlink with ".."
// in target was extracted.
bool ConvertSymlinkPaths;
// Last path checked for symlinks. We use it to improve the performance,
// so we do not check recently checked folders again.
std::wstring LastCheckedSymlink;
#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT)
bool Fat32,NotFat32;
#endif
public:
CmdExtract(CommandData *Cmd);
~CmdExtract();
void DoExtract();
void ExtractArchiveInit(Archive &Arc);
bool ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat);
static void UnstoreFile(ComprDataIO &DataIO,int64 DestUnpSize);
};
#endif
+150
View File
@@ -0,0 +1,150 @@
#include "rar.hpp"
// If NewFile==NULL, we delete created file after user confirmation.
// It is useful if we need to overwrite an existing folder or file,
// but need user confirmation for that.
bool FileCreate(CommandData *Cmd,File *NewFile,std::wstring &Name,
bool *UserReject,int64 FileSize,RarTime *FileTime,bool WriteOnly)
{
if (UserReject!=NULL)
*UserReject=false;
#ifdef _WIN_ALL
bool ShortNameChanged=false;
#endif
while (FileExist(Name))
{
#if defined(_WIN_ALL)
if (!ShortNameChanged)
{
// Avoid the infinite loop if UpdateExistingShortName returns
// the same name.
ShortNameChanged=true;
// Maybe our long name matches the short name of existing file.
// Let's check if we can change the short name.
if (UpdateExistingShortName(Name))
continue;
}
// Allow short name check again. It is necessary, because rename and
// autorename below can change the name, so we need to check it again.
ShortNameChanged=false;
#endif
UIASKREP_RESULT Choice=uiAskReplaceEx(Cmd,Name,FileSize,FileTime,(NewFile==NULL ? UIASKREP_F_NORENAME:0));
if (Choice==UIASKREP_R_REPLACE)
break;
if (Choice==UIASKREP_R_SKIP)
{
if (UserReject!=NULL)
*UserReject=true;
return false;
}
if (Choice==UIASKREP_R_CANCEL)
ErrHandler.Exit(RARX_USERBREAK);
}
// Try to truncate the existing file first instead of delete,
// so we preserve existing file permissions, such as NTFS permissions,
// also as "Compressed" attribute and hard links. In GUI version we avoid
// deleting an existing file for non-.rar archive formats as well.
uint FileMode=WriteOnly ? FMF_WRITE|FMF_SHAREREAD:FMF_UPDATE|FMF_SHAREREAD;
if (NewFile!=NULL && NewFile->Create(Name,FileMode))
return true;
CreatePath(Name,true,Cmd->DisableNames);
return NewFile!=NULL ? NewFile->Create(Name,FileMode):DelFile(Name);
}
#if defined(_WIN_ALL)
// If we find a file, which short name is equal to 'Name', we try to change
// its short name, while preserving the long name. It helps when unpacking
// an archived file, which long name is equal to short name of already
// existing file. Otherwise we would overwrite the already existing file,
// even though its long name does not match the name of unpacking file.
bool UpdateExistingShortName(const std::wstring &Name)
{
DWORD Res=GetLongPathName(Name.c_str(),NULL,0);
if (Res==0)
return false;
std::vector<wchar> LongPathBuf(Res);
Res=GetLongPathName(Name.c_str(),LongPathBuf.data(),(DWORD)LongPathBuf.size());
if (Res==0 || Res>=LongPathBuf.size())
return false;
Res=GetShortPathName(Name.c_str(),NULL,0);
if (Res==0)
return false;
std::vector<wchar> ShortPathBuf(Res);
Res=GetShortPathName(Name.c_str(),ShortPathBuf.data(),(DWORD)ShortPathBuf.size());
if (Res==0 || Res>=ShortPathBuf.size())
return false;
std::wstring LongPathName=LongPathBuf.data();
std::wstring ShortPathName=ShortPathBuf.data();
std::wstring LongName=PointToName(LongPathName);
std::wstring ShortName=PointToName(ShortPathName);
// We continue only if file has a short name, which does not match its
// long name, and this short name is equal to name of file which we need
// to create.
if (ShortName.empty() || wcsicomp(LongName,ShortName)==0 ||
wcsicomp(PointToName(Name),ShortName)!=0)
return false;
// Generate the temporary new name for existing file.
std::wstring NewName;
for (uint I=0;I<10000 && NewName.empty();I+=123)
{
// Here we copy the path part of file to create. We'll make the temporary
// file in the same folder.
NewName=Name;
// Here we set the random name part.
SetName(NewName,std::wstring(L"rtmp") + std::to_wstring(I));
// If such file is already exist, try next random name.
if (FileExist(NewName))
NewName.clear();
}
// If we could not generate the name not used by any other file, we return.
if (NewName.empty())
return false;
// FastFind returns the name without path, but we need the fully qualified
// name for renaming, so we use the path from file to create and long name
// from existing file.
std::wstring FullName=Name;
SetName(FullName,LongName);
// Rename the existing file to randomly generated name. Normally it changes
// the short name too.
if (!MoveFile(FullName.c_str(),NewName.c_str()))
return false;
// Now we need to create the temporary empty file with same name as
// short name of our already existing file. We do it to occupy its previous
// short name and not allow to use it again when renaming the file back to
// its original long name.
File KeepShortFile;
bool Created=false;
if (!FileExist(Name))
Created=KeepShortFile.Create(Name,FMF_WRITE|FMF_SHAREREAD);
// Now we rename the existing file from temporary name to original long name.
// Since its previous short name is occupied by another file, it should
// get another short name.
MoveFile(NewName.c_str(),FullName.c_str());
if (Created)
{
// Delete the temporary zero length file occupying the short name,
KeepShortFile.Close();
KeepShortFile.Delete();
}
// We successfully changed the short name. We do not use the simpler
// SetFileShortName Windows API call, because it requires SE_RESTORE_NAME
// privilege.
return true;
}
#endif
+12
View File
@@ -0,0 +1,12 @@
#ifndef _RAR_FILECREATE_
#define _RAR_FILECREATE_
bool FileCreate(CommandData *Cmd,File *NewFile,std::wstring &Name,
bool *UserReject,int64 FileSize=INT64NDF,
RarTime *FileTime=NULL,bool WriteOnly=false);
#if defined(_WIN_ALL)
bool UpdateExistingShortName(const std::wstring &Name);
#endif
#endif
+855
View File
@@ -0,0 +1,855 @@
#include "rar.hpp"
File::File()
{
hFile=FILE_BAD_HANDLE;
NewFile=false;
LastWrite=false;
HandleType=FILE_HANDLENORMAL;
LineInput=false;
SkipClose=false;
ErrorType=FILE_SUCCESS;
OpenShared=false;
AllowDelete=true;
AllowExceptions=true;
PreserveAtime=false;
#ifdef _WIN_ALL
// CreateMode=FMF_UNDEFINED;
#endif
ReadErrorMode=FREM_ASK;
TruncatedAfterReadError=false;
CurFilePos=0;
}
File::~File()
{
if (hFile!=FILE_BAD_HANDLE && !SkipClose)
if (NewFile)
Delete();
else
Close();
}
void File::operator = (File &SrcFile)
{
hFile=SrcFile.hFile;
NewFile=SrcFile.NewFile;
LastWrite=SrcFile.LastWrite;
HandleType=SrcFile.HandleType;
TruncatedAfterReadError=SrcFile.TruncatedAfterReadError;
FileName=SrcFile.FileName;
SrcFile.SkipClose=true;
}
bool File::Open(const std::wstring &Name,uint Mode)
{
ErrorType=FILE_SUCCESS;
FileHandle hNewFile;
bool OpenShared=File::OpenShared || (Mode & FMF_OPENSHARED)!=0;
bool UpdateMode=(Mode & FMF_UPDATE)!=0;
bool WriteMode=(Mode & FMF_WRITE)!=0;
#ifdef _WIN_ALL
uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ;
if (UpdateMode)
Access|=GENERIC_WRITE;
uint ShareMode=(Mode & FMF_OPENEXCLUSIVE) ? 0 : FILE_SHARE_READ;
if (OpenShared)
ShareMode|=FILE_SHARE_WRITE;
uint Flags=FILE_FLAG_SEQUENTIAL_SCAN;
FindData FD;
if (PreserveAtime)
Access|=FILE_WRITE_ATTRIBUTES; // Needed to preserve atime.
hNewFile=CreateFile(Name.c_str(),Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL);
DWORD LastError;
if (hNewFile==FILE_BAD_HANDLE)
{
LastError=GetLastError();
std::wstring LongName;
if (GetWinLongPath(Name,LongName))
{
hNewFile=CreateFile(LongName.c_str(),Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL);
// For archive names longer than 260 characters first CreateFile
// (without \\?\) fails and sets LastError to 3 (access denied).
// We need the correct "file not found" error code to decide
// if we create a new archive or quit with "cannot create" error.
// So we need to check the error code after \\?\ CreateFile again,
// otherwise we'll fail to create new archives with long names.
// But we cannot simply assign the new code to LastError,
// because it would break "..\arcname.rar" relative names processing.
// First CreateFile returns the correct "file not found" code for such
// names, but "\\?\" CreateFile returns ERROR_INVALID_NAME treating
// dots as a directory name. So we check only for "file not found"
// error here and for other errors use the first CreateFile result.
if (GetLastError()==ERROR_FILE_NOT_FOUND)
LastError=ERROR_FILE_NOT_FOUND;
}
}
if (hNewFile==FILE_BAD_HANDLE && LastError==ERROR_FILE_NOT_FOUND)
ErrorType=FILE_NOTFOUND;
if (PreserveAtime && hNewFile!=FILE_BAD_HANDLE)
{
FILETIME ft={0xffffffff,0xffffffff}; // This value prevents atime modification.
SetFileTime(hNewFile,NULL,&ft,NULL);
}
#else
int flags=UpdateMode ? O_RDWR:(WriteMode ? O_WRONLY:O_RDONLY);
// 2025.06.09: We can't just set O_DIRECT for Unix like we set
// FILE_FLAG_SEQUENTIAL_SCAN for Windows to minimize disk caching.
// O_DIRECT might impose alignment requirements for data size, data address
// and file offset. Also it might not be supported by some file systems
// and fail with an error.
#ifdef O_BINARY
flags|=O_BINARY;
#if defined(_AIX) && defined(_LARGE_FILE_API)
flags|=O_LARGEFILE;
#endif
#endif
// NDK r20 has O_NOATIME, but fails to create files with it in Android 7+.
#if defined(O_NOATIME)
if (PreserveAtime)
flags|=O_NOATIME;
#endif
std::string NameA;
WideToChar(Name,NameA);
int handle=open(NameA.c_str(),flags);
#ifdef LOCK_EX
#ifdef _OSF_SOURCE
extern "C" int flock(int, int);
#endif
if (!OpenShared && UpdateMode && handle>=0 && flock(handle,LOCK_EX|LOCK_NB)==-1)
{
close(handle);
return false;
}
#endif
if (handle==-1)
hNewFile=FILE_BAD_HANDLE;
else
{
#ifdef FILE_USE_OPEN
hNewFile=handle;
#else
hNewFile=fdopen(handle,UpdateMode ? UPDATEBINARY:READBINARY);
#endif
}
if (hNewFile==FILE_BAD_HANDLE && errno==ENOENT)
ErrorType=FILE_NOTFOUND;
#endif
NewFile=false;
HandleType=FILE_HANDLENORMAL;
SkipClose=false;
bool Success=hNewFile!=FILE_BAD_HANDLE;
if (Success)
{
hFile=hNewFile;
FileName=Name;
TruncatedAfterReadError=false;
}
return Success;
}
#if !defined(SFX_MODULE)
void File::TOpen(const std::wstring &Name)
{
if (!WOpen(Name))
ErrHandler.Exit(RARX_OPEN);
}
#endif
bool File::WOpen(const std::wstring &Name)
{
if (Open(Name))
return true;
ErrHandler.OpenErrorMsg(Name);
return false;
}
bool File::Create(const std::wstring &Name,uint Mode)
{
// 2025.09.03: Likely outdated info, see https://www.illumos.org/issues/2000
// OpenIndiana based NAS and CIFS shares fail to set the file time if file
// was created in read+write mode and some data was written and not flushed
// before SetFileTime call. So we should use the write only mode if we plan
// SetFileTime call and do not need to read from file.
bool WriteMode=(Mode & FMF_WRITE)!=0;
bool ShareRead=(Mode & FMF_SHAREREAD)!=0 || File::OpenShared;
#ifdef _WIN_ALL
// CreateMode=Mode;
uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ|GENERIC_WRITE;
DWORD ShareMode=ShareRead ? FILE_SHARE_READ:0;
// Windows automatically removes dots and spaces in the end of file name,
// So we detect such names and process them with \\?\ prefix.
wchar LastChar=GetLastChar(Name);
bool Special=LastChar=='.' || LastChar==' ';
if (Special && (Mode & FMF_STANDARDNAMES)==0)
hFile=FILE_BAD_HANDLE;
else
hFile=CreateFile(Name.c_str(),Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL);
if (hFile==FILE_BAD_HANDLE)
{
std::wstring LongName;
if (GetWinLongPath(Name,LongName))
hFile=CreateFile(LongName.c_str(),Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL);
}
#else
#ifdef FILE_USE_OPEN
std::string NameA;
WideToChar(Name,NameA);
hFile=open(NameA.c_str(),(O_CREAT|O_TRUNC) | (WriteMode ? O_WRONLY : O_RDWR),0666);
#else
hFile=fopen(NameA.c_str(),WriteMode ? WRITEBINARY:CREATEBINARY);
#endif
#endif
NewFile=true;
HandleType=FILE_HANDLENORMAL;
SkipClose=false;
FileName=Name;
return hFile!=FILE_BAD_HANDLE;
}
#if !defined(SFX_MODULE)
void File::TCreate(const std::wstring &Name,uint Mode)
{
if (!WCreate(Name,Mode))
ErrHandler.Exit(RARX_FATAL);
}
#endif
bool File::WCreate(const std::wstring &Name,uint Mode)
{
if (Create(Name,Mode))
return true;
ErrHandler.CreateErrorMsg(Name);
return false;
}
bool File::Close()
{
bool Success=true;
if (hFile!=FILE_BAD_HANDLE)
{
if (!SkipClose)
{
#ifdef _WIN_ALL
// We use the standard system handle for stdout in Windows
// and it must not be closed here.
if (HandleType==FILE_HANDLENORMAL)
Success=CloseHandle(hFile)!=FALSE;
#else
#ifdef FILE_USE_OPEN
Success=close(hFile)!=-1;
#else
Success=fclose(hFile)!=EOF;
#endif
#endif
}
hFile=FILE_BAD_HANDLE;
}
HandleType=FILE_HANDLENORMAL;
if (!Success && AllowExceptions)
ErrHandler.CloseError(FileName);
return Success;
}
bool File::Delete()
{
if (HandleType!=FILE_HANDLENORMAL)
return false;
if (hFile!=FILE_BAD_HANDLE)
Close();
if (!AllowDelete)
return false;
return DelFile(FileName);
}
bool File::Rename(const std::wstring &NewName)
{
// No need to rename if names are already same.
bool Success=(NewName==FileName);
if (!Success)
Success=RenameFile(FileName,NewName);
if (Success)
FileName=NewName;
return Success;
}
bool File::Write(const void *Data,size_t Size)
{
if (Size==0)
return true;
if (HandleType==FILE_HANDLESTD)
{
#ifdef _WIN_ALL
hFile=GetStdHandle(STD_OUTPUT_HANDLE);
#else
// Cannot use the standard stdout here, because it already has wide orientation.
if (hFile==FILE_BAD_HANDLE)
{
#ifdef FILE_USE_OPEN
hFile=dup(STDOUT_FILENO); // Open new stdout stream.
#else
hFile=fdopen(dup(STDOUT_FILENO),"w"); // Open new stdout stream.
#endif
}
#endif
}
bool Success;
while (1)
{
Success=false;
#ifdef _WIN_ALL
DWORD Written=0;
if (HandleType!=FILE_HANDLENORMAL)
{
// writing to stdout can fail in old Windows if data block is too large
const size_t MaxSize=0x4000;
for (size_t I=0;I<Size;I+=MaxSize)
{
Success=WriteFile(hFile,(byte *)Data+I,(DWORD)Min(Size-I,MaxSize),&Written,NULL)!=FALSE;
if (!Success)
break;
}
}
else
Success=WriteFile(hFile,Data,(DWORD)Size,&Written,NULL)!=FALSE;
#else
#ifdef FILE_USE_OPEN
ssize_t Written=write(hFile,Data,Size);
Success=Written==Size;
#else
int Written=fwrite(Data,1,Size,hFile);
Success=Written==Size && !ferror(hFile);
#endif
#endif
if (!Success && AllowExceptions && HandleType==FILE_HANDLENORMAL)
{
#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(RARDLL)
int ErrCode=GetLastError();
int64 FilePos=Tell();
uint64 FreeSize=GetFreeDisk(FileName);
SetLastError(ErrCode);
if (FreeSize>Size && FilePos-Size<=0xffffffff && FilePos+Size>0xffffffff)
ErrHandler.WriteErrorFAT(FileName);
#endif
if (ErrHandler.AskRepeatWrite(FileName,false))
{
#if !defined(_WIN_ALL) && !defined(FILE_USE_OPEN)
clearerr(hFile);
#endif
if (Written<Size && Written>0)
Seek(Tell()-Written,SEEK_SET);
continue;
}
ErrHandler.WriteError(L"",FileName);
}
break;
}
LastWrite=true;
return Success; // It can return false only if AllowExceptions is disabled.
}
int File::Read(void *Data,size_t Size)
{
if (TruncatedAfterReadError)
return 0;
int64 FilePos=0; // Initialized only to suppress some compilers warning.
if (ReadErrorMode==FREM_IGNORE)
FilePos=Tell();
int TotalRead=0;
while (true)
{
int ReadSize=DirectRead(Data,Size);
if (ReadSize==-1)
{
ErrorType=FILE_READERROR;
if (AllowExceptions)
if (ReadErrorMode==FREM_IGNORE)
{
ReadSize=0;
for (size_t I=0;I<Size;I+=512)
{
Seek(FilePos+I,SEEK_SET);
size_t SizeToRead=Min(Size-I,512);
int ReadCode=DirectRead(Data,SizeToRead);
ReadSize+=(ReadCode==-1) ? 512:ReadCode;
if (ReadSize!=-1)
TotalRead+=ReadSize;
}
}
else
{
bool Ignore=false,Retry=false,Quit=false;
if (ReadErrorMode==FREM_ASK && HandleType==FILE_HANDLENORMAL && IsOpened())
{
ErrHandler.AskRepeatRead(FileName,Ignore,Retry,Quit);
if (Retry)
continue;
}
if (Ignore || ReadErrorMode==FREM_TRUNCATE)
{
TruncatedAfterReadError=true;
return 0;
}
ErrHandler.ReadError(FileName);
}
}
TotalRead+=ReadSize; // If ReadSize is -1, TotalRead is also set to -1 here.
if (HandleType==FILE_HANDLESTD && !LineInput && ReadSize>0 && (uint)ReadSize<Size)
{
// Unlike regular files, for pipe we can read only as much as was
// written at the other end of pipe. We had seen data coming in small
// ~80 byte chunks when piping from 'type arc.rar'. Extraction code
// would fail if we read an incomplete archive header from stdin.
// So here we ensure that requested size is completely read.
// But we return the available data immediately in "line input" mode,
// when processing user's input in console prompts. Otherwise apps
// piping user responses to multiple Ask() prompts can hang if no more
// data is available yet and pipe isn't closed.
Data=(byte*)Data+ReadSize;
Size-=ReadSize;
continue;
}
break;
}
if (TotalRead>0) // Can be -1 for error and AllowExceptions disabled.
CurFilePos+=TotalRead;
return TotalRead; // It can return -1 only if AllowExceptions is disabled.
}
// Returns -1 in case of error.
int File::DirectRead(void *Data,size_t Size)
{
#ifdef _WIN_ALL
const size_t MaxDeviceRead=20000;
const size_t MaxLockedRead=32768;
#endif
if (HandleType==FILE_HANDLESTD)
{
#ifdef _WIN_ALL
// if (Size>MaxDeviceRead)
// Size=MaxDeviceRead;
hFile=GetStdHandle(STD_INPUT_HANDLE);
#else
#ifdef FILE_USE_OPEN
hFile=STDIN_FILENO;
#else
hFile=stdin;
#endif
#endif
}
#ifdef _WIN_ALL
// For pipes like 'type file.txt | rar -si arcname' ReadFile may return
// data in small ~4KB blocks. It may slightly reduce the compression ratio.
DWORD Read;
if (!ReadFile(hFile,Data,(DWORD)Size,&Read,NULL))
{
if (IsDevice() && Size>MaxDeviceRead)
return DirectRead(Data,MaxDeviceRead);
if (HandleType==FILE_HANDLESTD && GetLastError()==ERROR_BROKEN_PIPE)
return 0;
// We had a bug report about failure to archive 1C database lock file
// 1Cv8tmp.1CL, which is a zero length file with a region above 200 KB
// permanently locked. If our first read request uses too large buffer
// and if we are in -dh mode, so we were able to open the file,
// we'll fail with "Read error". So now we use try a smaller buffer size
// in case of lock error.
if (HandleType==FILE_HANDLENORMAL && Size>MaxLockedRead &&
GetLastError()==ERROR_LOCK_VIOLATION)
return DirectRead(Data,MaxLockedRead);
return -1;
}
return Read;
#else
#ifdef FILE_USE_OPEN
ssize_t ReadSize=read(hFile,Data,Size);
if (ReadSize==-1)
return -1;
return (int)ReadSize;
#else
if (LastWrite)
{
fflush(hFile);
LastWrite=false;
}
clearerr(hFile);
size_t ReadSize=fread(Data,1,Size,hFile);
if (ferror(hFile))
return -1;
return (int)ReadSize;
#endif
#endif
}
void File::Seek(int64 Offset,int Method)
{
if (!RawSeek(Offset,Method) && AllowExceptions)
ErrHandler.SeekError(FileName);
}
bool File::RawSeek(int64 Offset,int Method)
{
if (hFile==FILE_BAD_HANDLE)
return true;
if (!IsSeekable()) // To extract archives from stdin with -si.
{
// We tried to dynamically allocate 32 KB buffer here, but it improved
// speed in Windows 10 by mere ~1.5%.
byte Buf[4096];
if (Method==SEEK_CUR || Method==SEEK_SET && Offset>=CurFilePos)
{
uint64 SkipSize=Method==SEEK_CUR ? Offset:Offset-CurFilePos;
while (SkipSize>0) // Reading to emulate seek forward.
{
int ReadSize=Read(Buf,(size_t)Min(SkipSize,ASIZE(Buf)));
if (ReadSize<=0)
return false;
SkipSize-=ReadSize;
CurFilePos+=ReadSize;
}
return true;
}
// May need it in FileLength() in Archive::UnexpEndArcMsg() when unpacking
// RAR 4.x archives without the end of archive block created with -en.
if (Method==SEEK_END)
{
int ReadSize;
while ((ReadSize=Read(Buf,ASIZE(Buf)))>0)
CurFilePos+=ReadSize;
return true;
}
return false; // Backward seek on unseekable file.
}
if (Offset<0 && Method!=SEEK_SET)
{
Offset=(Method==SEEK_CUR ? Tell():FileLength())+Offset;
Method=SEEK_SET;
}
#ifdef _WIN_ALL
LONG HighDist=(LONG)(Offset>>32);
if (SetFilePointer(hFile,(LONG)Offset,&HighDist,Method)==0xffffffff &&
GetLastError()!=NO_ERROR)
return false;
#else
LastWrite=false;
#ifdef FILE_USE_OPEN
if (lseek(hFile,(off_t)Offset,Method)==-1)
return false;
#elif defined(_LARGEFILE_SOURCE) && !defined(_OSF_SOURCE) && !defined(__VMS)
if (fseeko(hFile,Offset,Method)!=0)
return false;
#else
if (fseek(hFile,(long)Offset,Method)!=0)
return false;
#endif
#endif
return true;
}
int64 File::Tell()
{
if (hFile==FILE_BAD_HANDLE)
if (AllowExceptions)
ErrHandler.SeekError(FileName);
else
return -1;
if (!IsSeekable())
return CurFilePos;
#ifdef _WIN_ALL
LONG HighDist=0;
uint LowDist=SetFilePointer(hFile,0,&HighDist,FILE_CURRENT);
if (LowDist==0xffffffff && GetLastError()!=NO_ERROR)
if (AllowExceptions)
ErrHandler.SeekError(FileName);
else
return -1;
return INT32TO64(HighDist,LowDist);
#else
#ifdef FILE_USE_OPEN
return lseek(hFile,0,SEEK_CUR);
#elif defined(_LARGEFILE_SOURCE) && !defined(_OSF_SOURCE)
return ftello(hFile);
#else
return ftell(hFile);
#endif
#endif
}
void File::Prealloc(int64 Size)
{
#ifdef _WIN_ALL
if (RawSeek(Size,SEEK_SET))
{
Truncate();
Seek(0,SEEK_SET);
}
#endif
#if defined(_UNIX) && defined(USE_FALLOCATE)
// fallocate is rather new call. Only latest kernels support it.
// So we are not using it by default yet.
int fd = GetFD();
if (fd >= 0)
fallocate(fd, 0, 0, Size);
#endif
}
byte File::GetByte()
{
byte Byte=0;
Read(&Byte,1);
return Byte;
}
void File::PutByte(byte Byte)
{
Write(&Byte,1);
}
bool File::Truncate()
{
#ifdef _WIN_ALL
return SetEndOfFile(hFile)!=FALSE;
#else
return ftruncate(GetFD(),(off_t)Tell())==0;
#endif
}
void File::Flush()
{
#ifdef _WIN_ALL
FlushFileBuffers(hFile);
#else
#ifndef FILE_USE_OPEN
fflush(hFile);
#endif
fsync(GetFD());
#endif
}
void File::SetOpenFileTime(RarTime *ftm,RarTime *ftc,RarTime *fta)
{
#ifdef _WIN_ALL
// Workaround for OpenIndiana NAS time bug. If we cannot create a file
// in write only mode, we need to flush the write buffer before calling
// SetFileTime or file time will not be changed.
// 2025.09.03: Removed this code as likely redundant now,
// see https://www.illumos.org/issues/2000
// if (CreateMode!=FMF_UNDEFINED && (CreateMode & FMF_WRITE)==0)
// FlushFileBuffers(hFile);
bool sm=ftm!=NULL && ftm->IsSet();
bool sc=ftc!=NULL && ftc->IsSet();
bool sa=fta!=NULL && fta->IsSet();
FILETIME fm,fc,fa;
if (sm)
ftm->GetWinFT(&fm);
if (sc)
ftc->GetWinFT(&fc);
if (sa)
fta->GetWinFT(&fa);
SetFileTime(hFile,sc ? &fc:NULL,sa ? &fa:NULL,sm ? &fm:NULL);
#endif
}
void File::SetCloseFileTime(RarTime *ftm,RarTime *fta)
{
// Android APP_PLATFORM := android-14 does not support futimens and futimes.
// Newer platforms support futimens, but fail on Android 4.2.
// We have to use utime for Android.
// Also we noticed futimens fail to set timestamps on NTFS partition
// mounted to virtual Linux x86 machine, but utimensat worked correctly.
// So we set timestamps for already closed files in Unix.
#ifdef _UNIX
SetCloseFileTimeByName(FileName,ftm,fta);
#endif
}
void File::SetCloseFileTimeByName(const std::wstring &Name,RarTime *ftm,RarTime *fta)
{
#ifdef _UNIX
bool setm=ftm!=NULL && ftm->IsSet();
bool seta=fta!=NULL && fta->IsSet();
if (setm || seta)
{
std::string NameA;
WideToChar(Name,NameA);
#ifdef UNIX_TIME_NS
timespec times[2];
times[0].tv_sec=seta ? fta->GetUnix() : 0;
times[0].tv_nsec=seta ? long(fta->GetUnixNS()%1000000000) : UTIME_NOW;
times[1].tv_sec=setm ? ftm->GetUnix() : 0;
times[1].tv_nsec=setm ? long(ftm->GetUnixNS()%1000000000) : UTIME_NOW;
utimensat(AT_FDCWD,NameA.c_str(),times,0);
#else
utimbuf ut;
if (setm)
ut.modtime=ftm->GetUnix();
else
ut.modtime=fta->GetUnix(); // Need to set something, cannot left it 0.
if (seta)
ut.actime=fta->GetUnix();
else
ut.actime=ut.modtime; // Need to set something, cannot left it 0.
utime(NameA.c_str(),&ut);
#endif
}
#endif
}
#ifdef _UNIX
void File::StatToRarTime(struct stat &st,RarTime *ftm,RarTime *ftc,RarTime *fta)
{
#ifdef UNIX_TIME_NS
#if defined(_APPLE)
if (ftm!=NULL) ftm->SetUnixNS(st.st_mtimespec.tv_sec*(uint64)1000000000+st.st_mtimespec.tv_nsec);
if (ftc!=NULL) ftc->SetUnixNS(st.st_ctimespec.tv_sec*(uint64)1000000000+st.st_ctimespec.tv_nsec);
if (fta!=NULL) fta->SetUnixNS(st.st_atimespec.tv_sec*(uint64)1000000000+st.st_atimespec.tv_nsec);
#else
if (ftm!=NULL) ftm->SetUnixNS(st.st_mtim.tv_sec*(uint64)1000000000+st.st_mtim.tv_nsec);
if (ftc!=NULL) ftc->SetUnixNS(st.st_ctim.tv_sec*(uint64)1000000000+st.st_ctim.tv_nsec);
if (fta!=NULL) fta->SetUnixNS(st.st_atim.tv_sec*(uint64)1000000000+st.st_atim.tv_nsec);
#endif
#else
if (ftm!=NULL) ftm->SetUnix(st.st_mtime);
if (ftc!=NULL) ftc->SetUnix(st.st_ctime);
if (fta!=NULL) fta->SetUnix(st.st_atime);
#endif
}
#endif
void File::GetOpenFileTime(RarTime *ftm,RarTime *ftc,RarTime *fta)
{
#ifdef _WIN_ALL
FILETIME ctime,atime,mtime;
GetFileTime(hFile,&ctime,&atime,&mtime);
if (ftm!=NULL) ftm->SetWinFT(&mtime);
if (ftc!=NULL) ftc->SetWinFT(&ctime);
if (fta!=NULL) fta->SetWinFT(&atime);
#elif defined(_UNIX)
struct stat st;
fstat(GetFD(),&st);
StatToRarTime(st,ftm,ftc,fta);
#endif
}
int64 File::FileLength()
{
int64 SavePos=Tell();
Seek(0,SEEK_END);
int64 Length=Tell();
Seek(SavePos,SEEK_SET);
return Length;
}
bool File::IsDevice()
{
if (hFile==FILE_BAD_HANDLE)
return false;
#ifdef _WIN_ALL
uint Type=GetFileType(hFile);
return Type==FILE_TYPE_CHAR || Type==FILE_TYPE_PIPE;
#else
return isatty(GetFD());
#endif
}
#ifndef SFX_MODULE
int64 File::Copy(File &Dest,int64 Length)
{
bool CopyAll=(Length==INT64NDF);
// Adjust the buffer size to data size. So we do not waste too much time
// to vector initialization when copying many small data blocks like
// when updating an archive with many small files.
size_t BufSize=File::CopyBufferSize();
if (!CopyAll && Length<(int64)BufSize)
BufSize=(size_t)Length;
std::vector<byte> Buffer(BufSize);
int64 CopySize=0;
while (CopyAll || Length>0)
{
Wait();
size_t SizeToRead=(!CopyAll && Length<(int64)Buffer.size()) ? (size_t)Length:Buffer.size();
byte *Buf=Buffer.data();
int ReadSize=Read(Buf,SizeToRead);
if (ReadSize==0)
break;
size_t WriteSize=ReadSize;
#ifdef _WIN_ALL
// For FAT32 USB flash drives in Windows if first write is 4 KB or more,
// write caching is disabled and "write through" is enabled, resulting
// in bad performance, especially for many small files. It happens when
// we create SFX archive on USB drive, because SFX module is written first.
// So we split the first write to small 1 KB followed by rest of data.
if (CopySize==0 && WriteSize>=4096)
{
const size_t FirstWrite=1024;
Dest.Write(Buf,FirstWrite);
Buf+=FirstWrite;
WriteSize-=FirstWrite;
}
#endif
Dest.Write(Buf,WriteSize);
CopySize+=ReadSize;
if (!CopyAll)
Length-=ReadSize;
}
return CopySize;
}
#endif
+156
View File
@@ -0,0 +1,156 @@
#ifndef _RAR_FILE_
#define _RAR_FILE_
#define FILE_USE_OPEN
#ifdef _WIN_ALL
typedef HANDLE FileHandle;
#define FILE_BAD_HANDLE INVALID_HANDLE_VALUE
#elif defined(FILE_USE_OPEN)
typedef off_t FileHandle;
#define FILE_BAD_HANDLE -1
#else
typedef FILE* FileHandle;
#define FILE_BAD_HANDLE NULL
#endif
enum FILE_HANDLETYPE {FILE_HANDLENORMAL,FILE_HANDLESTD};
enum FILE_ERRORTYPE {FILE_SUCCESS,FILE_NOTFOUND,FILE_READERROR};
enum FILE_MODE_FLAGS {
// Request read only access to file. Default for Open.
FMF_READ=0,
// Request both read and write access to file. Default for Create.
FMF_UPDATE=1,
// Request write only access to file.
FMF_WRITE=2,
// Open files which are already opened for write by other programs.
FMF_OPENSHARED=4,
// Open files only if no other program is opened it even in shared mode.
FMF_OPENEXCLUSIVE=8,
// Provide read access to created file for other programs.
FMF_SHAREREAD=16,
// Use standard NTFS names without trailing dots and spaces.
FMF_STANDARDNAMES=32,
// Mode flags are not defined yet.
// FMF_UNDEFINED=256
};
enum FILE_READ_ERROR_MODE {
FREM_ASK, // Propose to use the already read part, retry or abort.
FREM_TRUNCATE, // Use the already read part without additional prompt.
FREM_IGNORE // Try to skip unreadable block and read further.
};
class File
{
private:
FileHandle hFile;
bool LastWrite;
FILE_HANDLETYPE HandleType;
// If we read the user input in console prompts from stdin, we shall
// process the available line immediately, not waiting for rest of data.
// Otherwise apps piping user responses to multiple Ask() prompts can
// hang if no more data is available yet and pipe isn't closed.
// If we read RAR archive or other file data from stdin, we shall collect
// the entire requested block as long as pipe isn't closed, so we get
// complete archive headers, not split between different reads.
bool LineInput;
bool SkipClose;
FILE_READ_ERROR_MODE ReadErrorMode;
bool NewFile;
bool AllowDelete;
bool AllowExceptions;
#ifdef _WIN_ALL
// uint CreateMode;
#endif
bool PreserveAtime;
bool TruncatedAfterReadError;
int64 CurFilePos; // Used for forward seeks in stdin files.
protected:
bool OpenShared; // Set by 'Archive' class.
public:
std::wstring FileName;
FILE_ERRORTYPE ErrorType;
public:
File();
virtual ~File();
void operator = (File &SrcFile);
// Several functions below are 'virtual', because they are redefined
// by Archive for QOpen and by MultiFile for split files in WinRAR.
virtual bool Open(const std::wstring &Name,uint Mode=FMF_READ);
void TOpen(const std::wstring &Name);
bool WOpen(const std::wstring &Name);
bool Create(const std::wstring &Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD);
void TCreate(const std::wstring &Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD);
bool WCreate(const std::wstring &Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD);
virtual bool Close(); // 'virtual' for MultiFile class.
bool Delete();
bool Rename(const std::wstring &NewName);
bool Write(const void *Data,size_t Size);
virtual int Read(void *Data,size_t Size);
int DirectRead(void *Data,size_t Size);
virtual void Seek(int64 Offset,int Method);
bool RawSeek(int64 Offset,int Method);
virtual int64 Tell();
void Prealloc(int64 Size);
byte GetByte();
void PutByte(byte Byte);
bool Truncate();
void Flush();
void SetOpenFileTime(RarTime *ftm,RarTime *ftc=NULL,RarTime *fta=NULL);
void SetCloseFileTime(RarTime *ftm,RarTime *fta=NULL);
static void SetCloseFileTimeByName(const std::wstring &Name,RarTime *ftm,RarTime *fta);
#ifdef _UNIX
static void StatToRarTime(struct stat &st,RarTime *ftm,RarTime *ftc,RarTime *fta);
#endif
void GetOpenFileTime(RarTime *ftm,RarTime *ftc=NULL,RarTime *fta=NULL);
virtual bool IsOpened() {return hFile!=FILE_BAD_HANDLE;} // 'virtual' for MultiFile class.
virtual int64 FileLength(); // 'virtual' for MultiFile class.
void SetHandleType(FILE_HANDLETYPE Type) {HandleType=Type;}
void SetLineInputMode(bool Mode) {LineInput=Mode;}
FILE_HANDLETYPE GetHandleType() {return HandleType;}
bool IsSeekable() {return HandleType!=FILE_HANDLESTD;}
bool IsDevice();
static bool RemoveCreated();
FileHandle GetHandle() {return hFile;}
void SetHandle(FileHandle Handle) {Close();hFile=Handle;}
void SetReadErrorMode(FILE_READ_ERROR_MODE Mode) {ReadErrorMode=Mode;}
int64 Copy(File &Dest,int64 Length=INT64NDF);
void SetAllowDelete(bool Allow) {AllowDelete=Allow;}
void SetExceptions(bool Allow) {AllowExceptions=Allow;}
void SetPreserveAtime(bool Preserve) {PreserveAtime=Preserve;}
bool IsTruncatedAfterReadError() {return TruncatedAfterReadError;}
#ifdef _UNIX
int GetFD()
{
#ifdef FILE_USE_OPEN
return hFile;
#else
return fileno(hFile);
#endif
}
#endif
static size_t CopyBufferSize()
{
// Values in 0x100000 - 0x400000 range are ok, but multithreaded CRC32
// seems to benefit from 0x400000, especially on ARM CPUs.
return 0x400000;
}
};
#endif
+630
View File
@@ -0,0 +1,630 @@
#include "rar.hpp"
MKDIR_CODE MakeDir(const std::wstring &Name,bool SetAttr,uint Attr)
{
#ifdef _WIN_ALL
// Windows automatically removes dots and spaces in the end of directory
// name. So we detect such names and process them with \\?\ prefix.
wchar LastChar=GetLastChar(Name);
bool Special=LastChar=='.' || LastChar==' ';
BOOL RetCode=Special ? FALSE : CreateDirectory(Name.c_str(),NULL);
if (RetCode==0 && !FileExist(Name))
{
std::wstring LongName;
if (GetWinLongPath(Name,LongName))
RetCode=CreateDirectory(LongName.c_str(),NULL);
}
if (RetCode!=0) // Non-zero return code means success for CreateDirectory.
{
if (SetAttr)
SetFileAttr(Name,Attr);
return MKDIR_SUCCESS;
}
int ErrCode=GetLastError();
if (ErrCode==ERROR_FILE_NOT_FOUND || ErrCode==ERROR_PATH_NOT_FOUND)
return MKDIR_BADPATH;
return MKDIR_ERROR;
#elif defined(_UNIX)
std::string NameA;
WideToChar(Name,NameA);
mode_t uattr=SetAttr ? (mode_t)Attr:0777;
int ErrCode=mkdir(NameA.c_str(),uattr);
if (ErrCode==-1)
return errno==ENOENT ? MKDIR_BADPATH:MKDIR_ERROR;
return MKDIR_SUCCESS;
#else
return MKDIR_ERROR;
#endif
}
// Simplified version of MakeDir().
bool CreateDir(const std::wstring &Name)
{
return MakeDir(Name,false,0)==MKDIR_SUCCESS;
}
bool CreatePath(const std::wstring &Path,bool SkipLastName,bool Silent)
{
if (Path.empty())
return false;
#ifdef _WIN_ALL
uint DirAttr=0;
#else
uint DirAttr=0777;
#endif
bool Success=true;
for (size_t I=0;I<Path.size();I++)
{
// Process all kinds of path separators, so user can enter Unix style
// path in Windows or Windows in Unix. I>0 check avoids attempting
// creating an empty directory for paths starting from path separator.
if (IsPathDiv(Path[I]) && I>0)
{
#ifdef _WIN_ALL
// We must not attempt to create "D:" directory, because first
// CreateDirectory will fail, so we'll use \\?\D:, which forces Wine
// to create "D:" directory.
if (I==2 && Path[1]==':')
continue;
#endif
std::wstring DirName=Path.substr(0,I);
Success=MakeDir(DirName,true,DirAttr)==MKDIR_SUCCESS;
if (Success && !Silent)
{
mprintf(St(MCreatDir),DirName.c_str());
mprintf(L" %s",St(MOk));
}
}
}
if (!SkipLastName && !IsPathDiv(GetLastChar(Path)))
Success=MakeDir(Path,true,DirAttr)==MKDIR_SUCCESS;
return Success;
}
void SetDirTime(const std::wstring &Name,RarTime *ftm,RarTime *ftc,RarTime *fta)
{
#if defined(_WIN_ALL)
bool sm=ftm!=NULL && ftm->IsSet();
bool sc=ftc!=NULL && ftc->IsSet();
bool sa=fta!=NULL && fta->IsSet();
uint DirAttr=GetFileAttr(Name);
bool ResetAttr=(DirAttr!=0xffffffff && (DirAttr & FILE_ATTRIBUTE_READONLY)!=0);
if (ResetAttr)
SetFileAttr(Name,0);
HANDLE hFile=CreateFile(Name.c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);
if (hFile==INVALID_HANDLE_VALUE)
{
std::wstring LongName;
if (GetWinLongPath(Name,LongName))
hFile=CreateFile(LongName.c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);
}
if (hFile==INVALID_HANDLE_VALUE)
return;
FILETIME fm,fc,fa;
if (sm)
ftm->GetWinFT(&fm);
if (sc)
ftc->GetWinFT(&fc);
if (sa)
fta->GetWinFT(&fa);
SetFileTime(hFile,sc ? &fc:NULL,sa ? &fa:NULL,sm ? &fm:NULL);
CloseHandle(hFile);
if (ResetAttr)
SetFileAttr(Name,DirAttr);
#endif
#ifdef _UNIX
File::SetCloseFileTimeByName(Name,ftm,fta);
#endif
}
bool IsRemovable(const std::wstring &Name)
{
#if defined(_WIN_ALL)
std::wstring Root;
GetPathRoot(Name,Root);
int Type=GetDriveType(Root.empty() ? nullptr : Root.c_str());
return Type==DRIVE_REMOVABLE || Type==DRIVE_CDROM;
#else
return false;
#endif
}
#ifndef SFX_MODULE
int64 GetFreeDisk(const std::wstring &Name)
{
#ifdef _WIN_ALL
std::wstring Root;
GetPathWithSep(Name,Root);
ULARGE_INTEGER uiTotalSize,uiTotalFree,uiUserFree;
uiUserFree.u.LowPart=uiUserFree.u.HighPart=0;
if (GetDiskFreeSpaceEx(Root.empty() ? NULL:Root.c_str(),&uiUserFree,&uiTotalSize,&uiTotalFree) &&
uiUserFree.u.HighPart<=uiTotalFree.u.HighPart)
return INT32TO64(uiUserFree.u.HighPart,uiUserFree.u.LowPart);
return 0;
#elif defined(_UNIX)
std::wstring Root;
GetPathWithSep(Name,Root);
std::string RootA;
WideToChar(Root,RootA);
struct statvfs sfs;
if (statvfs(RootA.empty() ? ".":RootA.c_str(),&sfs)!=0)
return 0;
int64 FreeSize=sfs.f_bsize;
FreeSize=FreeSize*sfs.f_bavail;
return FreeSize;
#else
return 0;
#endif
}
#endif
#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT)
// Return 'true' for FAT and FAT32, so we can adjust the maximum supported
// file size to 4 GB for these file systems.
bool IsFAT(const std::wstring &Name)
{
std::wstring Root;
GetPathRoot(Name,Root);
wchar FileSystem[MAX_PATH+1];
// Root can be empty, when we create volumes with -v in the current folder.
if (GetVolumeInformation(Root.empty() ? NULL:Root.c_str(),NULL,0,NULL,NULL,NULL,FileSystem,ASIZE(FileSystem)))
return wcscmp(FileSystem,L"FAT")==0 || wcscmp(FileSystem,L"FAT32")==0;
return false;
}
#endif
bool FileExist(const std::wstring &Name)
{
#ifdef _WIN_ALL
return GetFileAttr(Name)!=0xffffffff;
#elif defined(ENABLE_ACCESS)
std::string NameA;
WideToChar(Name,NameA);
return access(NameA.c_str(),0)==0;
#else
FindData FD;
return FindFile::FastFind(Name,&FD);
#endif
}
bool WildFileExist(const std::wstring &Name)
{
if (IsWildcard(Name))
{
FindFile Find;
Find.SetMask(Name);
FindData fd;
return Find.Next(&fd);
}
return FileExist(Name);
}
bool IsDir(uint Attr)
{
#ifdef _WIN_ALL
return Attr!=0xffffffff && (Attr & FILE_ATTRIBUTE_DIRECTORY)!=0;
#endif
#if defined(_UNIX)
return (Attr & 0xF000)==0x4000;
#endif
}
bool IsUnreadable(uint Attr)
{
#if defined(_UNIX) && defined(S_ISFIFO) && defined(S_ISSOCK) && defined(S_ISCHR)
return S_ISFIFO(Attr) || S_ISSOCK(Attr) || S_ISCHR(Attr);
#else
return false;
#endif
}
bool IsLink(uint Attr)
{
#ifdef _UNIX
return (Attr & 0xF000)==0xA000;
#elif defined(_WIN_ALL)
return (Attr & FILE_ATTRIBUTE_REPARSE_POINT)!=0;
#else
return false;
#endif
}
bool IsDeleteAllowed(uint FileAttr)
{
#ifdef _WIN_ALL
return (FileAttr & (FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN))==0;
#else
return (FileAttr & (S_IRUSR|S_IWUSR))==(S_IRUSR|S_IWUSR);
#endif
}
void PrepareToDelete(const std::wstring &Name)
{
#ifdef _WIN_ALL
SetFileAttr(Name,0);
#endif
#ifdef _UNIX
std::string NameA;
WideToChar(Name,NameA);
chmod(NameA.c_str(),S_IRUSR|S_IWUSR|S_IXUSR);
#endif
}
uint GetFileAttr(const std::wstring &Name)
{
#ifdef _WIN_ALL
DWORD Attr=GetFileAttributes(Name.c_str());
if (Attr==0xffffffff)
{
std::wstring LongName;
if (GetWinLongPath(Name,LongName))
Attr=GetFileAttributes(LongName.c_str());
}
return Attr;
#else
std::string NameA;
WideToChar(Name,NameA);
struct stat st;
if (stat(NameA.c_str(),&st)!=0)
return 0;
return st.st_mode;
#endif
}
bool SetFileAttr(const std::wstring &Name,uint Attr)
{
#ifdef _WIN_ALL
bool Success=SetFileAttributes(Name.c_str(),Attr)!=0;
if (!Success)
{
std::wstring LongName;
if (GetWinLongPath(Name,LongName))
Success=SetFileAttributes(LongName.c_str(),Attr)!=0;
}
return Success;
#elif defined(_UNIX)
std::string NameA;
WideToChar(Name,NameA);
return chmod(NameA.c_str(),(mode_t)Attr)==0;
#else
return false;
#endif
}
// Ext is the extension with the leading dot, like L".bat", or nullptr to use
// the default extension.
bool MkTemp(std::wstring &Name,const wchar *Ext)
{
RarTime CurTime;
CurTime.SetCurrentTime();
// We cannot use CurTime.GetWin() as is, because its lowest bits can
// have low informational value, like being a zero or few fixed numbers.
uint Random=(uint)(CurTime.GetWin()/100000);
// Using PID we guarantee that different RAR copies use different temp names
// even if started in exactly the same time.
uint PID=0;
#ifdef _WIN_ALL
PID=(uint)GetCurrentProcessId();
#elif defined(_UNIX)
PID=(uint)getpid();
#endif
for (uint Attempt=0;;Attempt++)
{
uint RandomExt=Random%50000+Attempt;
if (Attempt==1000)
return false;
// User asked to specify the single extension for all temporary files,
// so it can be added to server ransomware protection exceptions.
// He wrote, this protection blocks temporary files when adding
// a file to RAR archive with drag and drop. So unless a calling code
// requires a specific extension, like .bat file when uninstalling,
// we set the uniform extension here.
if (Ext==nullptr)
Ext=L".rartemp";
std::wstring NewName=Name + std::to_wstring(PID) + L"." + std::to_wstring(RandomExt) + Ext;
if (!FileExist(NewName))
{
Name=NewName;
break;
}
}
return true;
}
#if !defined(SFX_MODULE)
void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size,uint Flags)
{
int64 SavePos=SrcFile->Tell();
#ifndef SILENT
int64 FileLength=Size==INT64NDF ? SrcFile->FileLength() : Size;
#endif
if ((Flags & (CALCFSUM_SHOWTEXT|CALCFSUM_SHOWPERCENT))!=0)
uiMsg(UIEVENT_FILESUMSTART);
if ((Flags & CALCFSUM_CURPOS)==0)
SrcFile->Seek(0,SEEK_SET);
const size_t BufSize=0x100000;
std::vector<byte> Data(BufSize);
DataHash HashCRC,HashBlake2;
HashCRC.Init(HASH_CRC32,Threads);
HashBlake2.Init(HASH_BLAKE2,Threads);
int64 BlockCount=0;
int64 TotalRead=0;
while (true)
{
size_t SizeToRead;
if (Size==INT64NDF) // If we process the entire file.
SizeToRead=BufSize; // Then always attempt to read the entire buffer.
else
SizeToRead=(size_t)Min((int64)BufSize,Size);
int ReadSize=SrcFile->Read(Data.data(),SizeToRead);
if (ReadSize==0)
break;
TotalRead+=ReadSize;
if ((++BlockCount & 0xf)==0)
{
#ifndef SILENT
if ((Flags & CALCFSUM_SHOWPROGRESS)!=0)
{
// Update only the current file progress in WinRAR, set the total to 0
// to keep it as is. It looks better for WinRAR.
uiExtractProgress(TotalRead,FileLength,0,0);
}
else
{
if ((Flags & CALCFSUM_SHOWPERCENT)!=0)
uiMsg(UIEVENT_FILESUMPROGRESS,ToPercent(TotalRead,FileLength));
}
#endif
Wait();
}
if (CRC32!=NULL)
HashCRC.Update(Data.data(),ReadSize);
if (Blake2!=NULL)
HashBlake2.Update(Data.data(),ReadSize);
if (Size!=INT64NDF)
Size-=ReadSize;
}
SrcFile->Seek(SavePos,SEEK_SET);
if ((Flags & CALCFSUM_SHOWPERCENT)!=0)
uiMsg(UIEVENT_FILESUMEND);
if (CRC32!=NULL)
*CRC32=HashCRC.GetCRC32();
if (Blake2!=NULL)
{
HashValue Result;
HashBlake2.Result(&Result);
memcpy(Blake2,Result.Digest,sizeof(Result.Digest));
}
}
#endif
bool RenameFile(const std::wstring &SrcName,const std::wstring &DestName)
{
#ifdef _WIN_ALL
bool Success=MoveFile(SrcName.c_str(),DestName.c_str())!=0;
if (!Success)
{
std::wstring LongName1,LongName2;
if (GetWinLongPath(SrcName,LongName1) && GetWinLongPath(DestName,LongName2))
Success=MoveFile(LongName1.c_str(),LongName2.c_str())!=0;
}
return Success;
#else
std::string SrcNameA,DestNameA;
WideToChar(SrcName,SrcNameA);
WideToChar(DestName,DestNameA);
bool Success=rename(SrcNameA.c_str(),DestNameA.c_str())==0;
return Success;
#endif
}
bool DelFile(const std::wstring &Name)
{
#ifdef _WIN_ALL
bool Success=DeleteFile(Name.c_str())!=0;
if (!Success)
{
std::wstring LongName;
if (GetWinLongPath(Name,LongName))
Success=DeleteFile(LongName.c_str())!=0;
}
return Success;
#else
std::string NameA;
WideToChar(Name,NameA);
bool Success=remove(NameA.c_str())==0;
return Success;
#endif
}
bool DelDir(const std::wstring &Name)
{
#ifdef _WIN_ALL
bool Success=RemoveDirectory(Name.c_str())!=0;
if (!Success)
{
std::wstring LongName;
if (GetWinLongPath(Name,LongName))
Success=RemoveDirectory(LongName.c_str())!=0;
}
return Success;
#else
std::string NameA;
WideToChar(Name,NameA);
bool Success=rmdir(NameA.c_str())==0;
return Success;
#endif
}
#if defined(_WIN_ALL) && !defined(SFX_MODULE)
bool SetFileCompression(const std::wstring &Name,bool State)
{
HANDLE hFile=CreateFile(Name.c_str(),FILE_READ_DATA|FILE_WRITE_DATA,
FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_SEQUENTIAL_SCAN,NULL);
if (hFile==INVALID_HANDLE_VALUE)
{
std::wstring LongName;
if (GetWinLongPath(Name,LongName))
hFile=CreateFile(LongName.c_str(),FILE_READ_DATA|FILE_WRITE_DATA,
FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_SEQUENTIAL_SCAN,NULL);
if (hFile==INVALID_HANDLE_VALUE)
return false;
}
bool Success=SetFileCompression(hFile,State);
CloseHandle(hFile);
return Success;
}
bool SetFileCompression(HANDLE hFile,bool State)
{
SHORT NewState=State ? COMPRESSION_FORMAT_DEFAULT:COMPRESSION_FORMAT_NONE;
DWORD Result;
int RetCode=DeviceIoControl(hFile,FSCTL_SET_COMPRESSION,&NewState,
sizeof(NewState),NULL,0,&Result,NULL);
return RetCode!=0;
}
void ResetFileCache(const std::wstring &Name)
{
// To reset file cache in Windows it is enough to open it with
// FILE_FLAG_NO_BUFFERING and then close it.
HANDLE hSrc=CreateFile(Name.c_str(),GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,OPEN_EXISTING,FILE_FLAG_NO_BUFFERING,NULL);
if (hSrc!=INVALID_HANDLE_VALUE)
CloseHandle(hSrc);
}
#endif
// Delete symbolic links in file path, if any, and replace them by directories.
// Prevents extracting files outside of destination folder with symlink chains.
bool LinksToDirs(const std::wstring &SrcName,const std::wstring &SkipPart,std::wstring &LastChecked)
{
// Unlike Unix, Windows doesn't expand lnk1 in symlink targets like
// "lnk1/../dir", but converts the path to "dir". In Unix we need to call
// this function to prevent placing unpacked files outside of destination
// folder if previously we unpacked "dir/lnk1" -> "..",
// "dir/lnk2" -> "lnk1/.." and "dir/lnk2/anypath/poc.txt".
// We may still need this function to prevent abusing symlink chains
// in link source path if we remove detection of such chains
// in IsRelativeSymlinkSafe. This function seems to make other symlink
// related safety checks redundant, but for now we prefer to keep them too.
//
// 2022.12.01: the performance impact is minimized after adding the check
// against the previous path and enabling this verification only after
// extracting a symlink with ".." in target. So we enabled it for Windows
// as well for extra safety.
//#ifdef _UNIX
std::wstring Path=SrcName;
size_t SkipLength=SkipPart.size();
if (SkipLength>0 && !starts_with(Path,SkipPart))
SkipLength=0; // Parameter validation, not really needed now.
// Do not check parts already checked in previous path to improve performance.
for (size_t I=0;I<Path.size() && I<LastChecked.size() && Path[I]==LastChecked[I];I++)
if (IsPathDiv(Path[I]) && I>SkipLength)
SkipLength=I;
// Avoid converting symlinks in destination path part specified by user.
while (SkipLength<Path.size() && IsPathDiv(Path[SkipLength]))
SkipLength++;
if (Path.size()>0)
for (size_t I=Path.size()-1;I>SkipLength;I--)
if (IsPathDiv(Path[I]))
{
Path.erase(I);
FindData FD;
if (FindFile::FastFind(Path,&FD,true) && FD.IsLink)
{
#ifdef _WIN_ALL
// Normally Windows symlinks to directory look like a directory
// and are deleted with DelDir(). It is possible to create
// a file-like symlink pointing at directory, which can be deleted
// only with && DelFile, but such symlink isn't really functional.
// Here we prefer to fail deleting such symlink and skip extracting
// a file.
if (!DelDir(Path))
#else
if (!DelFile(Path))
#endif
{
ErrHandler.CreateErrorMsg(SrcName); // Extraction command will skip this file or directory.
return false; // Couldn't delete the symlink to replace it with directory.
}
}
}
LastChecked=SrcName;
//#endif
return true;
}
+57
View File
@@ -0,0 +1,57 @@
#ifndef _RAR_FILEFN_
#define _RAR_FILEFN_
enum MKDIR_CODE {MKDIR_SUCCESS,MKDIR_ERROR,MKDIR_BADPATH};
MKDIR_CODE MakeDir(const std::wstring &Name,bool SetAttr,uint Attr);
bool CreateDir(const std::wstring &Name);
bool CreatePath(const std::wstring &Path,bool SkipLastName,bool Silent);
void SetDirTime(const std::wstring &Name,RarTime *ftm,RarTime *ftc,RarTime *fta);
bool IsRemovable(const std::wstring &Name);
#ifndef SFX_MODULE
int64 GetFreeDisk(const std::wstring &Name);
#endif
#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT)
bool IsFAT(const std::wstring &Root);
#endif
bool FileExist(const std::wstring &Name);
bool WildFileExist(const std::wstring &Name);
bool IsDir(uint Attr);
bool IsUnreadable(uint Attr);
bool IsLink(uint Attr);
void SetSFXMode(const std::wstring &FileName);
void EraseDiskContents(const std::wstring &FileName);
bool IsDeleteAllowed(uint FileAttr);
void PrepareToDelete(const std::wstring &Name);
uint GetFileAttr(const std::wstring &Name);
bool SetFileAttr(const std::wstring &Name,uint Attr);
bool MkTemp(std::wstring &Name,const wchar *Ext);
enum CALCFSUM_FLAGS {CALCFSUM_SHOWTEXT=1,CALCFSUM_SHOWPERCENT=2,CALCFSUM_SHOWPROGRESS=4,CALCFSUM_CURPOS=8};
void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size=INT64NDF,uint Flags=0);
bool RenameFile(const std::wstring &SrcName,const std::wstring &DestName);
bool DelFile(const std::wstring &Name);
bool DelDir(const std::wstring &Name);
#if defined(_WIN_ALL) && !defined(SFX_MODULE)
bool SetFileCompression(const std::wstring &Name,bool State);
bool SetFileCompression(HANDLE hFile,bool State);
void ResetFileCache(const std::wstring &Name);
#endif
// Keep it here and not in extinfo.cpp, because it is invoked from Zip.SFX too.
bool LinksToDirs(const std::wstring &SrcName,const std::wstring &SkipPart,std::wstring &LastChecked);
#endif
+163
View File
@@ -0,0 +1,163 @@
#include "rar.hpp"
bool ReadTextFile(
const std::wstring &Name,
StringList *List,
bool Config,
bool AbortOnError,
RAR_CHARSET SrcCharset,
bool Unquote,
bool SkipComments,
bool ExpandEnvStr)
{
std::wstring FileName;
if (Config)
GetConfigName(Name,FileName,true,false);
else
FileName=Name;
File SrcFile;
if (!FileName.empty())
{
bool OpenCode=AbortOnError ? SrcFile.WOpen(FileName):SrcFile.Open(FileName,0);
if (!OpenCode)
{
if (AbortOnError)
ErrHandler.Exit(RARX_OPEN);
return false;
}
}
else
SrcFile.SetHandleType(FILE_HANDLESTD);
size_t DataSize=0,ReadSize;
const int ReadBlock=4096;
std::vector<byte> Data(ReadBlock);
while ((ReadSize=SrcFile.Read(&Data[DataSize],ReadBlock))!=0)
{
DataSize+=ReadSize;
Data.resize(DataSize+ReadBlock); // Always have ReadBlock available for next data.
}
// Set to really read size, so we can zero terminate it correctly.
Data.resize(DataSize);
int LittleEndian=DataSize>=2 && Data[0]==255 && Data[1]==254 ? 1:0;
int BigEndian=DataSize>=2 && Data[0]==254 && Data[1]==255 ? 1:0;
bool Utf8=DataSize>=3 && Data[0]==0xef && Data[1]==0xbb && Data[2]==0xbf;
if (SrcCharset==RCH_DEFAULT)
SrcCharset=DetectTextEncoding(Data.data(),DataSize);
std::vector<wchar> DataW(ReadBlock);
if (SrcCharset==RCH_DEFAULT || SrcCharset==RCH_OEM || SrcCharset==RCH_ANSI)
{
Data.push_back(0); // Zero terminate.
#if defined(_WIN_ALL)
if (SrcCharset==RCH_OEM)
OemToCharA((char *)Data.data(),(char *)Data.data());
#endif
DataW.resize(Data.size());
CharToWide((char *)Data.data(),DataW.data(),DataW.size());
}
if (SrcCharset==RCH_UNICODE)
{
size_t Start=2; // Skip byte order mark.
if (!LittleEndian && !BigEndian) // No byte order mask.
{
Start=0;
LittleEndian=1;
}
DataW.resize(Data.size()/2+1);
size_t End=Data.size() & ~1; // We need even bytes number for UTF-16.
for (size_t I=Start;I<End;I+=2)
DataW[(I-Start)/2]=Data[I+BigEndian]+Data[I+LittleEndian]*256;
DataW[(End-Start)/2]=0;
}
if (SrcCharset==RCH_UTF8)
{
Data.push_back(0); // Zero terminate data.
DataW.resize(Data.size());
UtfToWide((const char *)(Data.data()+(Utf8 ? 3:0)),DataW.data(),DataW.size());
}
wchar *CurStr=DataW.data();
while (*CurStr!=0)
{
wchar *NextStr=CurStr,*CmtPtr=NULL;
while (*NextStr!='\r' && *NextStr!='\n' && *NextStr!=0)
{
if (SkipComments && NextStr[0]=='/' && NextStr[1]=='/')
{
*NextStr=0;
CmtPtr=NextStr;
}
NextStr++;
}
bool Done=*NextStr==0;
*NextStr=0;
for (wchar *SpacePtr=(CmtPtr!=NULL ? CmtPtr:NextStr)-1;SpacePtr>=CurStr;SpacePtr--)
{
if (*SpacePtr!=' ' && *SpacePtr!='\t')
break;
*SpacePtr=0;
}
if (Unquote && *CurStr=='\"')
{
size_t Length=wcslen(CurStr);
if (CurStr[Length-1]=='\"')
{
CurStr[Length-1]=0;
CurStr++;
}
}
bool Expanded=false;
#if defined(_WIN_ALL)
if (ExpandEnvStr && *CurStr=='%') // Expand environment variables in Windows.
{
std::wstring ExpName=CurStr;
ExpandEnvironmentStr(ExpName);
if (!ExpName.empty())
List->AddString(ExpName);
Expanded=true;
}
#endif
if (!Expanded && *CurStr!=0)
List->AddString(CurStr);
if (Done)
break;
CurStr=NextStr+1;
while (*CurStr=='\r' || *CurStr=='\n')
CurStr++;
}
return true;
}
RAR_CHARSET DetectTextEncoding(const byte *Data,size_t DataSize)
{
if (DataSize>3 && Data[0]==0xef && Data[1]==0xbb && Data[2]==0xbf &&
IsTextUtf8(Data+3,DataSize-3))
return RCH_UTF8;
bool LittleEndian=DataSize>2 && Data[0]==255 && Data[1]==254;
bool BigEndian=DataSize>2 && Data[0]==254 && Data[1]==255;
if (LittleEndian || BigEndian)
for (size_t I=LittleEndian ? 3 : 2;I<DataSize;I+=2)
if (Data[I]<32 && Data[I]!='\r' && Data[I]!='\n')
return RCH_UNICODE; // High byte in UTF-16 char is found.
return RCH_DEFAULT;
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef _RAR_FILESTR_
#define _RAR_FILESTR_
bool ReadTextFile(
const std::wstring &Name,
StringList *List,
bool Config,
bool AbortOnError=false,
RAR_CHARSET SrcCharset=RCH_DEFAULT,
bool Unquote=false,
bool SkipComments=false,
bool ExpandEnvStr=false
);
RAR_CHARSET DetectTextEncoding(const byte *Data,size_t DataSize);
#endif
+208
View File
@@ -0,0 +1,208 @@
#include "rar.hpp"
FindFile::FindFile()
{
FirstCall=true;
#ifdef _WIN_ALL
hFind=INVALID_HANDLE_VALUE;
#else
dirp=NULL;
#endif
}
FindFile::~FindFile()
{
#ifdef _WIN_ALL
if (hFind!=INVALID_HANDLE_VALUE)
FindClose(hFind);
#else
if (dirp!=NULL)
closedir(dirp);
#endif
}
void FindFile::SetMask(const std::wstring &Mask)
{
FindMask=Mask;
FirstCall=true;
}
bool FindFile::Next(FindData *fd,bool GetSymLink)
{
fd->Error=false;
if (FindMask.empty())
return false;
#ifdef _WIN_ALL
if (FirstCall)
{
if ((hFind=Win32Find(INVALID_HANDLE_VALUE,FindMask,fd))==INVALID_HANDLE_VALUE)
return false;
}
else
if (Win32Find(hFind,FindMask,fd)==INVALID_HANDLE_VALUE)
return false;
#else
if (FirstCall)
{
std::wstring DirName;
DirName=FindMask;
RemoveNameFromPath(DirName);
if (DirName.empty())
DirName=L".";
std::string DirNameA;
WideToChar(DirName,DirNameA);
if ((dirp=opendir(DirNameA.c_str()))==NULL)
{
fd->Error=(errno!=ENOENT);
return false;
}
}
while (1)
{
std::wstring Name;
struct dirent *ent=readdir(dirp);
if (ent==NULL)
return false;
if (strcmp(ent->d_name,".")==0 || strcmp(ent->d_name,"..")==0)
continue;
if (!CharToWide(std::string(ent->d_name),Name))
uiMsg(UIERROR_INVALIDNAME,L"",Name);
if (CmpName(FindMask,Name,MATCH_NAMES))
{
std::wstring FullName=FindMask;
FullName.erase(GetNamePos(FullName));
if (FullName.size()+Name.size()>=MAXPATHSIZE)
{
uiMsg(UIERROR_PATHTOOLONG,FullName,L"",Name);
return false;
}
FullName+=Name;
if (!FastFind(FullName,fd,GetSymLink))
{
ErrHandler.OpenErrorMsg(FullName);
continue;
}
fd->Name=FullName;
break;
}
}
#endif
fd->Flags=0;
fd->IsDir=IsDir(fd->FileAttr);
fd->IsLink=IsLink(fd->FileAttr);
FirstCall=false;
std::wstring NameOnly=PointToName(fd->Name);
if (NameOnly==L"." || NameOnly==L"..")
return Next(fd);
return true;
}
bool FindFile::FastFind(const std::wstring &FindMask,FindData *fd,bool GetSymLink)
{
fd->Error=false;
#ifndef _UNIX
if (IsWildcard(FindMask))
return false;
#endif
#ifdef _WIN_ALL
HANDLE hFind=Win32Find(INVALID_HANDLE_VALUE,FindMask,fd);
if (hFind==INVALID_HANDLE_VALUE)
return false;
FindClose(hFind);
#elif defined(_UNIX)
std::string FindMaskA;
WideToChar(FindMask,FindMaskA);
struct stat st;
if (GetSymLink)
{
#ifdef SAVE_LINKS
if (lstat(FindMaskA.c_str(),&st)!=0)
#else
if (stat(FindMaskA.c_str(),&st)!=0)
#endif
{
fd->Error=(errno!=ENOENT);
return false;
}
}
else
if (stat(FindMaskA.c_str(),&st)!=0)
{
fd->Error=(errno!=ENOENT);
return false;
}
fd->FileAttr=st.st_mode;
fd->Size=st.st_size;
File::StatToRarTime(st,&fd->mtime,&fd->ctime,&fd->atime);
fd->Name=FindMask;
#endif
fd->Flags=0;
fd->IsDir=IsDir(fd->FileAttr);
fd->IsLink=IsLink(fd->FileAttr);
return true;
}
#ifdef _WIN_ALL
HANDLE FindFile::Win32Find(HANDLE hFind,const std::wstring &Mask,FindData *fd)
{
WIN32_FIND_DATA FindData;
if (hFind==INVALID_HANDLE_VALUE)
{
hFind=FindFirstFile(Mask.c_str(),&FindData);
if (hFind==INVALID_HANDLE_VALUE)
{
std::wstring LongMask;
if (GetWinLongPath(Mask,LongMask))
hFind=FindFirstFile(LongMask.c_str(),&FindData);
}
if (hFind==INVALID_HANDLE_VALUE)
{
int SysErr=GetLastError();
// We must not issue an error for "file not found" and "path not found",
// because it is normal to not find anything for wildcard mask when
// archiving. Also searching for non-existent file is normal in some
// other modules, like WinRAR scanning for winrar_theme_description.txt
// to check if any themes are available.
fd->Error=SysErr!=ERROR_FILE_NOT_FOUND &&
SysErr!=ERROR_PATH_NOT_FOUND &&
SysErr!=ERROR_NO_MORE_FILES;
}
}
else
if (!FindNextFile(hFind,&FindData))
{
hFind=INVALID_HANDLE_VALUE;
fd->Error=GetLastError()!=ERROR_NO_MORE_FILES;
}
if (hFind!=INVALID_HANDLE_VALUE)
{
fd->Name=Mask;
SetName(fd->Name,FindData.cFileName);
fd->Size=INT32TO64(FindData.nFileSizeHigh,FindData.nFileSizeLow);
fd->FileAttr=FindData.dwFileAttributes;
fd->ftCreationTime=FindData.ftCreationTime;
fd->ftLastAccessTime=FindData.ftLastAccessTime;
fd->ftLastWriteTime=FindData.ftLastWriteTime;
fd->mtime.SetWinFT(&FindData.ftLastWriteTime);
fd->ctime.SetWinFT(&FindData.ftCreationTime);
fd->atime.SetWinFT(&FindData.ftLastAccessTime);
}
fd->Flags=0;
return hFind;
}
#endif
+49
View File
@@ -0,0 +1,49 @@
#ifndef _RAR_FINDDATA_
#define _RAR_FINDDATA_
enum FINDDATA_FLAGS {
FDDF_SECONDDIR=1 // Second encounter of same directory in SCAN_GETDIRSTWICE ScanTree mode.
};
struct FindData
{
std::wstring Name;
uint64 Size;
uint FileAttr;
bool IsDir;
bool IsLink;
RarTime mtime;
RarTime ctime;
RarTime atime;
#ifdef _WIN_ALL
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
#endif
uint Flags;
bool Error;
};
class FindFile
{
private:
#ifdef _WIN_ALL
static HANDLE Win32Find(HANDLE hFind,const std::wstring &Mask,FindData *fd);
#endif
std::wstring FindMask;
bool FirstCall;
#ifdef _WIN_ALL
HANDLE hFind;
#else
DIR *dirp;
#endif
public:
FindFile();
~FindFile();
void SetMask(const std::wstring &Mask);
bool Next(FindData *fd,bool GetSymLink=false);
static bool FastFind(const std::wstring &FindMask,FindData *fd,bool GetSymLink=false);
};
#endif
+52
View File
@@ -0,0 +1,52 @@
#include "rar.hpp"
BitInput::BitInput(bool AllocBuffer)
{
ExternalBuffer=false;
if (AllocBuffer)
{
// getbits*() attempt to read data from InAddr, ... InAddr+8 positions.
// So let's allocate 8 additional bytes for situation, when we need to
// read only 1 byte from the last position of buffer and avoid a crash
// from access to next 8 bytes, which contents we do not need.
size_t BufSize=MAX_SIZE+8;
InBuf=new byte[BufSize];
// Ensure that we get predictable results when accessing bytes in area
// not filled with read data.
memset(InBuf,0,BufSize);
}
else
InBuf=nullptr;
}
BitInput::~BitInput()
{
if (!ExternalBuffer)
delete[] InBuf;
}
void BitInput::faddbits(uint Bits)
{
// Function wrapped version of inline addbits to reduce the code size.
addbits(Bits);
}
uint BitInput::fgetbits()
{
// Function wrapped version of inline getbits to reduce the code size.
return getbits();
}
void BitInput::SetExternalBuffer(byte *Buf)
{
if (InBuf!=nullptr && !ExternalBuffer)
delete[] InBuf;
InBuf=Buf;
ExternalBuffer=true;
}
+81
View File
@@ -0,0 +1,81 @@
#ifndef _RAR_GETBITS_
#define _RAR_GETBITS_
class BitInput
{
public:
enum BufferSize {MAX_SIZE=0x8000}; // Size of input buffer.
int InAddr; // Curent byte position in the buffer.
int InBit; // Current bit position in the current byte.
bool ExternalBuffer;
public:
BitInput(bool AllocBuffer);
~BitInput();
byte *InBuf; // Dynamically allocated input buffer.
void InitBitInput()
{
InAddr=InBit=0;
}
// Move forward by 'Bits' bits.
void addbits(uint Bits)
{
Bits+=InBit;
InAddr+=Bits>>3;
InBit=Bits&7;
}
// Return 16 bits from current position in the buffer.
// Bit at (InAddr,InBit) has the highest position in returning data.
uint getbits()
{
#if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED)
uint32 BitField=RawGetBE4(InBuf+InAddr);
BitField >>= (16-InBit);
#else
uint BitField=(uint)InBuf[InAddr] << 16;
BitField|=(uint)InBuf[InAddr+1] << 8;
BitField|=(uint)InBuf[InAddr+2];
BitField >>= (8-InBit);
#endif
return BitField & 0xffff;
}
// Return 32 bits from current position in the buffer.
// Bit at (InAddr,InBit) has the highest position in returning data.
uint getbits32()
{
uint BitField=RawGetBE4(InBuf+InAddr);
BitField <<= InBit;
BitField|=(uint)InBuf[InAddr+4] >> (8-InBit);
return BitField & 0xffffffff;
}
// Return 64 bits from current position in the buffer.
// Bit at (InAddr,InBit) has the highest position in returning data.
uint64 getbits64()
{
uint64 BitField=RawGetBE8(InBuf+InAddr);
BitField <<= InBit;
BitField|=(uint)InBuf[InAddr+8] >> (8-InBit);
return BitField;
}
void faddbits(uint Bits);
uint fgetbits();
// Check if buffer has enough space for IncPtr bytes. Returns 'true'
// if buffer will be overflown.
bool Overflow(uint IncPtr)
{
return InAddr+IncPtr>=MAX_SIZE;
}
void SetExternalBuffer(byte *Buf);
};
#endif
+7
View File
@@ -0,0 +1,7 @@
#define INCLUDEGLOBAL
#ifdef _MSC_VER
#pragma hdrstop
#endif
#include "rar.hpp"
+14
View File
@@ -0,0 +1,14 @@
#ifndef _RAR_GLOBAL_
#define _RAR_GLOBAL_
#ifdef INCLUDEGLOBAL
#define EXTVAR
#else
#define EXTVAR extern
#endif
EXTVAR ErrorHandler ErrHandler;
#endif
+37
View File
@@ -0,0 +1,37 @@
bool ExtractHardlink(CommandData *Cmd,const std::wstring &NameNew,const std::wstring &NameExisting)
{
if (!FileExist(NameExisting))
{
uiMsg(UIERROR_HLINKCREATE,NameNew);
uiMsg(UIERROR_NOLINKTARGET);
ErrHandler.SetErrorCode(RARX_CREATE);
return false;
}
CreatePath(NameNew,true,Cmd->DisableNames);
#ifdef _WIN_ALL
bool Success=CreateHardLink(NameNew.c_str(),NameExisting.c_str(),NULL)!=0;
if (!Success)
{
uiMsg(UIERROR_HLINKCREATE,NameNew);
ErrHandler.SysErrMsg();
ErrHandler.SetErrorCode(RARX_CREATE);
}
return Success;
#elif defined(_UNIX)
std::string NameExistingA,NameNewA;
WideToChar(NameExisting,NameExistingA);
WideToChar(NameNew,NameNewA);
bool Success=link(NameExistingA.c_str(),NameNewA.c_str())==0;
if (!Success)
{
uiMsg(UIERROR_HLINKCREATE,NameNew);
ErrHandler.SysErrMsg();
ErrHandler.SetErrorCode(RARX_CREATE);
}
return Success;
#else
return false;
#endif
}
+283
View File
@@ -0,0 +1,283 @@
#include "rar.hpp"
void HashValue::Init(HASH_TYPE Type)
{
HashValue::Type=Type;
// Zero length data CRC32 is 0. It is important to set it when creating
// headers with no following data like directories or symlinks.
if (Type==HASH_RAR14 || Type==HASH_CRC32)
CRC32=0;
if (Type==HASH_BLAKE2)
{
// dd0e891776933f43c7d032b08a917e25741f8aa9a12c12e1cac8801500f2ca4f
// is BLAKE2sp hash of empty data. We init the structure to this value,
// so if we create a file or service header with no following data like
// "file copy" or "symlink", we set the checksum to proper value avoiding
// additional header type or size checks when extracting.
static byte EmptyHash[32]={
0xdd, 0x0e, 0x89, 0x17, 0x76, 0x93, 0x3f, 0x43,
0xc7, 0xd0, 0x32, 0xb0, 0x8a, 0x91, 0x7e, 0x25,
0x74, 0x1f, 0x8a, 0xa9, 0xa1, 0x2c, 0x12, 0xe1,
0xca, 0xc8, 0x80, 0x15, 0x00, 0xf2, 0xca, 0x4f
};
memcpy(Digest,EmptyHash,sizeof(Digest));
}
}
bool HashValue::operator == (const HashValue &cmp) const
{
if (Type==HASH_NONE || cmp.Type==HASH_NONE)
return true;
if (Type==HASH_RAR14 && cmp.Type==HASH_RAR14 ||
Type==HASH_CRC32 && cmp.Type==HASH_CRC32)
return CRC32==cmp.CRC32;
if (Type==HASH_BLAKE2 && cmp.Type==HASH_BLAKE2)
return memcmp(Digest,cmp.Digest,sizeof(Digest))==0;
return false;
}
DataHash::DataHash()
{
blake2ctx=NULL;
HashType=HASH_NONE;
#ifdef RAR_SMP
ThPool=NULL;
MaxThreads=0;
#endif
}
DataHash::~DataHash()
{
#ifdef RAR_SMP
delete ThPool;
#endif
cleandata(&CurCRC32, sizeof(CurCRC32));
if (blake2ctx!=NULL)
{
cleandata(blake2ctx, sizeof(blake2sp_state));
delete blake2ctx;
}
}
void DataHash::Init(HASH_TYPE Type,uint MaxThreads)
{
if (blake2ctx==NULL)
blake2ctx=new blake2sp_state;
HashType=Type;
if (Type==HASH_RAR14)
CurCRC32=0;
if (Type==HASH_CRC32)
CurCRC32=0xffffffff; // Initial CRC32 value.
if (Type==HASH_BLAKE2)
blake2sp_init(blake2ctx);
#ifdef RAR_SMP
DataHash::MaxThreads=Min(MaxThreads,HASH_POOL_THREADS);
#endif
}
void DataHash::Update(const void *Data,size_t DataSize)
{
#ifndef SFX_MODULE
if (HashType==HASH_RAR14)
CurCRC32=Checksum14((ushort)CurCRC32,Data,DataSize);
#endif
if (HashType==HASH_CRC32)
{
#ifdef RAR_SMP
UpdateCRC32MT(Data,DataSize);
#else
CurCRC32=CRC32(CurCRC32,Data,DataSize);
#endif
}
if (HashType==HASH_BLAKE2)
{
#ifdef RAR_SMP
if (MaxThreads>1 && ThPool==nullptr)
ThPool=new ThreadPool(HASH_POOL_THREADS);
blake2ctx->ThPool=ThPool;
blake2ctx->MaxThreads=MaxThreads;
#endif
blake2sp_update( blake2ctx, (byte *)Data, DataSize);
}
}
#ifdef RAR_SMP
THREAD_PROC(BuildCRC32Thread)
{
DataHash::CRC32ThreadData *td=(DataHash::CRC32ThreadData *)Data;
// Use 0 initial value to simplify combining the result with existing CRC32.
// It doesn't affect the first initial 0xffffffff in the data beginning.
// If we used 0xffffffff here, we would need to shift 0xffffffff left to
// block width and XOR it with block CRC32 to reset its initial value to 0.
td->DataCRC=CRC32(0,td->Data,td->DataSize);
}
// CRC is linear and distributive over addition, so CRC(a+b)=CRC(a)+CRC(b).
// Since addition in finite field is XOR, we have CRC(a^b)=CRC(a)^CRC(b).
// So CRC(aaabbb) = CRC(aaa000) ^ CRC(000bbb) = CRC(aaa000) ^ CRC(bbb),
// because CRC ignores leading zeroes. Thus to split CRC calculations
// to "aaa" and "bbb" blocks and then to threads we need to be able to
// find CRC(aaa000) knowing "aaa" quickly. We use Galois finite field to
// calculate the power of 2 to get "1000" and multiply it by "aaa".
void DataHash::UpdateCRC32MT(const void *Data,size_t DataSize)
{
const size_t MinBlock=0x4000;
if (DataSize<2*MinBlock || MaxThreads<2)
{
CurCRC32=CRC32(CurCRC32,Data,DataSize);
return;
}
if (ThPool==nullptr)
ThPool=new ThreadPool(HASH_POOL_THREADS);
size_t Threads=MaxThreads;
size_t BlockSize=DataSize/Threads;
if (BlockSize<MinBlock)
{
BlockSize=MinBlock;
Threads=DataSize/BlockSize;
}
CRC32ThreadData td[MaxPoolThreads];
//#undef USE_THREADS
for (size_t I=0;I<Threads;I++)
{
td[I].Data=(byte*)Data+I*BlockSize;
td[I].DataSize=(I+1==Threads) ? DataSize-I*BlockSize : BlockSize;
#ifdef USE_THREADS
ThPool->AddTask(BuildCRC32Thread,(void*)&td[I]);
#else
BuildCRC32Thread((void*)&td[I]);
#endif
}
#ifdef USE_THREADS
ThPool->WaitDone();
#endif // USE_THREADS
uint StdShift=gfExpCRC(uint(8*td[0].DataSize));
for (size_t I=0;I<Threads;I++)
{
// Prepare the multiplier to shift CRC to proper position.
uint ShiftMult;
if (td[I].DataSize==td[0].DataSize)
ShiftMult=StdShift; // Reuse the shift value for typical block size.
else
ShiftMult=gfExpCRC(uint(8*td[I].DataSize)); // 2 power "shift bits".
// To combine the cumulative total and current block CRC32, we multiply
// the total data CRC32 to shift value to place it to proper position.
// Invoke BitReverse32(), because 0xEDB88320 is the reversed polynomial.
// Alternatively we could adjust the multiplication function for reversed
// polynomials, but it would make it less readable without real speed gain.
// If CRC32 threads used 0xffffffff initial value, we would need
// to XOR the total data CRC32 with 0xffffffff before multiplication,
// so 0xffffffff is also shifted left to current block width and replaces
// the initial 0xffffffff CRC32 value with 0 in the current block CRC32
// after XOR'ing it with total data CRC32. Since now CRC32 threads use 0
// initial value, this is not necessary.
CurCRC32=BitReverse32(gfMulCRC(BitReverse32(CurCRC32), ShiftMult));
// Combine the total data and current block CRC32.
CurCRC32^=td[I].DataCRC;
}
}
#endif
uint DataHash::BitReverse32(uint N)
{
uint Reversed=0;
for (uint I=0;I<32;I++,N>>=1)
Reversed|=(N & 1)<<(31-I);
return Reversed;
}
// Galois field multiplication modulo POLY.
uint DataHash::gfMulCRC(uint A, uint B)
{
// For reversed 0xEDB88320 polynomial we bit reverse CRC32 before passing
// to this function, so we must use the normal polynomial here.
// We set the highest polynomial bit 33 for proper multiplication
// in case uint is larger than 32-bit.
const uint POLY=uint(0x104c11db7);
uint R = 0 ; // Multiplication result.
while (A != 0 && B != 0) // If any of multipliers becomes 0, quit early.
{
// For non-zero lowest B bit, add A to result.
R ^= (B & 1)!=0 ? A : 0;
// Make A twice larger before the next iteration.
// Subtract POLY to keep it modulo POLY if high bit is set.
A = (A << 1) ^ ((A & 0x80000000)!=0 ? POLY : 0);
B >>= 1; // Move next B bit to lowest position.
}
return R;
}
// Calculate 2 power N with square-and-multiply algorithm.
uint DataHash::gfExpCRC(uint N)
{
uint S = 2; // Starts from base value and contains the current square.
uint R = 1; // Exponentiation result.
while (N > 1)
{
if ((N & 1)!=0) // If N is odd.
R = gfMulCRC(R, S);
S = gfMulCRC(S, S); // Next square.
N >>= 1;
}
// We could change the loop condition to N > 0 and return R at expense
// of one additional gfMulCRC(S, S).
return gfMulCRC(R, S);
}
void DataHash::Result(HashValue *Result)
{
Result->Type=HashType;
if (HashType==HASH_RAR14)
Result->CRC32=CurCRC32;
if (HashType==HASH_CRC32)
Result->CRC32=CurCRC32^0xffffffff;
if (HashType==HASH_BLAKE2)
{
// Preserve the original context, so we can continue hashing if necessary.
blake2sp_state res=*blake2ctx;
blake2sp_final(&res,Result->Digest);
}
}
uint DataHash::GetCRC32()
{
return HashType==HASH_CRC32 ? CurCRC32^0xffffffff : 0;
}
bool DataHash::Cmp(HashValue *CmpValue,byte *Key)
{
HashValue Final;
Result(&Final);
#ifndef RAR_NOCRYPT
if (Key!=nullptr)
ConvertHashToMAC(&Final,Key);
#endif
return Final==*CmpValue;
}
+75
View File
@@ -0,0 +1,75 @@
#ifndef _RAR_DATAHASH_
#define _RAR_DATAHASH_
enum HASH_TYPE {HASH_NONE,HASH_RAR14,HASH_CRC32,HASH_BLAKE2};
struct HashValue
{
void Init(HASH_TYPE Type);
// Use the const member, so types on both sides of "==" match.
// Otherwise clang -std=c++20 issues "ambiguity is between a regular call
// to this operator and a call with the argument order reversed" warning.
bool operator == (const HashValue &cmp) const;
// Not actually used now. Const member for same reason as operator == above.
// Can be removed after switching to C++20, which automatically provides "!="
// if operator == is defined.
bool operator != (const HashValue &cmp) const {return !(*this==cmp);}
HASH_TYPE Type;
union
{
uint CRC32;
byte Digest[SHA256_DIGEST_SIZE];
};
};
#ifdef RAR_SMP
class ThreadPool;
class DataHash;
#endif
class DataHash
{
public:
struct CRC32ThreadData
{
void *Data;
size_t DataSize;
uint DataCRC;
};
private:
void UpdateCRC32MT(const void *Data,size_t DataSize);
uint BitReverse32(uint N);
uint gfMulCRC(uint A, uint B);
uint gfExpCRC(uint N);
// Speed gain seems to vanish above 8 CRC32 threads.
static const uint CRC32_POOL_THREADS=8;
// Thread pool must allow at least BLAKE2_THREADS_NUMBER threads.
static const uint HASH_POOL_THREADS=Max(BLAKE2_THREADS_NUMBER,CRC32_POOL_THREADS);
HASH_TYPE HashType;
uint CurCRC32;
blake2sp_state *blake2ctx;
#ifdef RAR_SMP
ThreadPool *ThPool;
uint MaxThreads;
#endif
public:
DataHash();
~DataHash();
void Init(HASH_TYPE Type,uint MaxThreads);
void Update(const void *Data,size_t DataSize);
void Result(HashValue *Result);
uint GetCRC32();
bool Cmp(HashValue *CmpValue,byte *Key);
HASH_TYPE Type() {return HashType;}
};
#endif
+55
View File
@@ -0,0 +1,55 @@
#include "rar.hpp"
void FileHeader::Reset(size_t SubDataSize)
{
SubData.resize(SubDataSize);
BaseBlock::Reset();
FileHash.Init(HASH_NONE);
mtime.Reset();
atime.Reset();
ctime.Reset();
SplitBefore=false;
SplitAfter=false;
UnknownUnpSize=0;
SubFlags=0; // Important for RAR 3.0 subhead.
CryptMethod=CRYPT_NONE;
Encrypted=false;
SaltSet=false;
UsePswCheck=false;
UseHashKey=false;
Lg2Count=0;
Solid=false;
Dir=false;
WinSize=0;
Inherited=false;
SubBlock=false;
CommentInHeader=false;
Version=false;
LargeFile=false;
RedirType=FSREDIR_NONE;
DirTarget=false;
UnixOwnerSet=false;
}
/*
FileHeader& FileHeader::operator = (FileHeader &hd)
{
SubData.Reset();
memcpy(this,&hd,sizeof(*this));
SubData.CleanData();
SubData=hd.SubData;
return *this;
}
*/
void MainHeader::Reset()
{
*this={};
}
+357
View File
@@ -0,0 +1,357 @@
#ifndef _RAR_HEADERS_
#define _RAR_HEADERS_
#define SIZEOF_MARKHEAD3 7 // Size of RAR 4.x archive mark header.
#define SIZEOF_MAINHEAD14 7 // Size of RAR 1.4 main archive header.
#define SIZEOF_MAINHEAD3 13 // Size of RAR 4.x main archive header.
#define SIZEOF_FILEHEAD14 21 // Size of RAR 1.4 file header.
#define SIZEOF_FILEHEAD3 32 // Size of RAR 3.0 file header.
#define SIZEOF_SHORTBLOCKHEAD 7 // Smallest RAR 4.x block size.
#define SIZEOF_LONGBLOCKHEAD 11
#define SIZEOF_SUBBLOCKHEAD 14
#define SIZEOF_COMMHEAD 13
#define SIZEOF_PROTECTHEAD 26
#define SIZEOF_STREAMHEAD 26
#define VER_PACK 29U
#define VER_PACK5 50U // It is stored as 0, but we subtract 50 when saving an archive.
#define VER_PACK7 70U // It is stored as 1, but we subtract 70 when saving an archive.
#define VER_UNPACK 29U
#define VER_UNPACK5 50U // It is stored as 0, but we add 50 when reading an archive.
#define VER_UNPACK7 70U // It is stored as 1, but we add 50 when reading an archive.
#define VER_UNKNOWN 9999U // Just some large value.
#define MHD_VOLUME 0x0001U
// Old style main archive comment embed into main archive header. Must not
// be used in new archives anymore.
#define MHD_COMMENT 0x0002U
#define MHD_LOCK 0x0004U
#define MHD_SOLID 0x0008U
#define MHD_PACK_COMMENT 0x0010U
#define MHD_NEWNUMBERING 0x0010U
#define MHD_AV 0x0020U
#define MHD_PROTECT 0x0040U
#define MHD_PASSWORD 0x0080U
#define MHD_FIRSTVOLUME 0x0100U
#define LHD_SPLIT_BEFORE 0x0001U
#define LHD_SPLIT_AFTER 0x0002U
#define LHD_PASSWORD 0x0004U
// Old style file comment embed into file header. Must not be used
// in new archives anymore.
#define LHD_COMMENT 0x0008U
// For non-file subheaders it denotes 'subblock having a parent file' flag.
#define LHD_SOLID 0x0010U
#define LHD_WINDOWMASK 0x00e0U
#define LHD_WINDOW64 0x0000U
#define LHD_WINDOW128 0x0020U
#define LHD_WINDOW256 0x0040U
#define LHD_WINDOW512 0x0060U
#define LHD_WINDOW1024 0x0080U
#define LHD_WINDOW2048 0x00a0U
#define LHD_WINDOW4096 0x00c0U
#define LHD_DIRECTORY 0x00e0U
#define LHD_LARGE 0x0100U
#define LHD_UNICODE 0x0200U
#define LHD_SALT 0x0400U
#define LHD_VERSION 0x0800U
#define LHD_EXTTIME 0x1000U
#define SKIP_IF_UNKNOWN 0x4000U
#define LONG_BLOCK 0x8000U
#define EARC_NEXT_VOLUME 0x0001U // Not last volume.
#define EARC_DATACRC 0x0002U // Store CRC32 of RAR archive (now is used only in volumes).
#define EARC_REVSPACE 0x0004U // Reserve space for end of REV file 7 byte record.
#define EARC_VOLNUMBER 0x0008U // Store a number of current volume.
enum HEADER_TYPE {
// RAR 5.0 header types.
HEAD_MARK=0x00, HEAD_MAIN=0x01, HEAD_FILE=0x02, HEAD_SERVICE=0x03,
HEAD_CRYPT=0x04, HEAD_ENDARC=0x05, HEAD_UNKNOWN=0xff,
// RAR 1.5 - 4.x header types.
HEAD3_MARK=0x72,HEAD3_MAIN=0x73,HEAD3_FILE=0x74,HEAD3_CMT=0x75,
HEAD3_AV=0x76,HEAD3_OLDSERVICE=0x77,HEAD3_PROTECT=0x78,HEAD3_SIGN=0x79,
HEAD3_SERVICE=0x7a,HEAD3_ENDARC=0x7b
};
// RAR 2.9 and earlier service haeders, mostly outdated and not supported.
enum { EA_HEAD=0x100,UO_HEAD=0x101,MAC_HEAD=0x102,BEEA_HEAD=0x103,
NTACL_HEAD=0x104,STREAM_HEAD=0x105 };
// Internal implementation, depends on archive format version.
enum HOST_SYSTEM {
// RAR 5.0 host OS
HOST5_WINDOWS=0,HOST5_UNIX=1,
// RAR 3.0 host OS.
HOST_MSDOS=0,HOST_OS2=1,HOST_WIN32=2,HOST_UNIX=3,HOST_MACOS=4,
HOST_BEOS=5,HOST_MAX
};
// Unified archive format independent implementation.
enum HOST_SYSTEM_TYPE {
HSYS_WINDOWS, HSYS_UNIX, HSYS_UNKNOWN
};
// We also use these values in extra field, so do not modify them.
enum FILE_SYSTEM_REDIRECT {
FSREDIR_NONE=0, FSREDIR_UNIXSYMLINK, FSREDIR_WINSYMLINK, FSREDIR_JUNCTION,
FSREDIR_HARDLINK, FSREDIR_FILECOPY
};
#define SUBHEAD_TYPE_CMT L"CMT"
#define SUBHEAD_TYPE_QOPEN L"QO"
#define SUBHEAD_TYPE_ACL L"ACL"
#define SUBHEAD_TYPE_STREAM L"STM"
#define SUBHEAD_TYPE_UOWNER L"UOW"
#define SUBHEAD_TYPE_AV L"AV"
#define SUBHEAD_TYPE_RR L"RR"
#define SUBHEAD_TYPE_OS2EA L"EA2"
/* new file inherits a subblock when updating a host file */
#define SUBHEAD_FLAGS_INHERITED 0x80000000
#define SUBHEAD_FLAGS_CMT_UNICODE 0x00000001
struct MarkHeader
{
byte Mark[8];
// Following fields are virtual and not present in real blocks.
uint HeadSize;
};
struct BaseBlock
{
uint HeadCRC; // 'ushort' for RAR 1.5.
HEADER_TYPE HeaderType; // 1 byte for RAR 1.5.
uint Flags; // 'ushort' for RAR 1.5.
uint HeadSize; // 'ushort' for RAR 1.5, up to 2 MB for RAR 5.0.
bool SkipIfUnknown;
void Reset()
{
SkipIfUnknown=false;
}
// We use it to assign this block data to inherited blocks.
// Such function seems to be cleaner than '(BaseBlock&)' cast or adding
// 'using BaseBlock::operator=;' to every inherited header.
void SetBaseBlock(BaseBlock &Src)
{
*this=Src;
}
};
struct BlockHeader:BaseBlock
{
uint DataSize;
};
struct MainHeader:BaseBlock
{
ushort HighPosAV;
uint PosAV;
bool CommentInHeader;
bool PackComment; // For RAR 1.4 archive format only.
bool Locator;
uint64 QOpenOffset; // Offset of quick list record.
uint64 QOpenMaxSize; // Maximum size of QOpen offset in locator extra field.
uint64 RROffset; // Offset of recovery record.
uint64 RRMaxSize; // Maximum size of RR offset in locator extra field.
size_t MetaNameMaxSize; // Maximum size of archive name in metadata extra field.
std::wstring OrigName; // Original archive name.
RarTime OrigTime; // Original archive time.
void Reset();
};
struct FileHeader:BlockHeader
{
byte HostOS;
uint UnpVer; // It is 1 byte in RAR29 and bit field in RAR5.
byte Method;
union {
uint FileAttr;
uint SubFlags;
};
std::wstring FileName;
std::vector<byte> SubData;
RarTime mtime;
RarTime ctime;
RarTime atime;
int64 PackSize;
int64 UnpSize;
int64 MaxSize; // Reserve packed and unpacked size bytes for vint of this size.
HashValue FileHash;
uint FileFlags;
bool SplitBefore;
bool SplitAfter;
bool UnknownUnpSize;
bool Encrypted;
CRYPT_METHOD CryptMethod;
bool SaltSet;
byte Salt[SIZE_SALT50];
byte InitV[SIZE_INITV];
bool UsePswCheck;
byte PswCheck[SIZE_PSWCHECK];
// Use HMAC calculated from HashKey and checksum instead of plain checksum.
bool UseHashKey;
// Key to convert checksum to HMAC. Derived from password with PBKDF2
// using additional iterations.
byte HashKey[SHA256_DIGEST_SIZE];
uint Lg2Count; // Log2 of PBKDF2 repetition count.
bool Solid;
bool Dir;
bool CommentInHeader; // RAR 2.0 file comment.
bool Version; // name.ext;ver file name containing the version number.
uint64 WinSize;
bool Inherited; // New file inherits a subblock when updating a host file (for subblocks only).
// 'true' if file sizes use 8 bytes instead of 4. Not used in RAR 5.0.
bool LargeFile;
// 'true' for HEAD_SERVICE block, which is a child of preceding file block.
// RAR 4.x uses 'solid' flag to indicate children subheader blocks in archives.
bool SubBlock;
HOST_SYSTEM_TYPE HSType;
FILE_SYSTEM_REDIRECT RedirType;
std::wstring RedirName;
bool DirTarget;
bool UnixOwnerSet,UnixOwnerNumeric,UnixGroupNumeric;
char UnixOwnerName[256],UnixGroupName[256];
#ifdef _UNIX
uid_t UnixOwnerID;
gid_t UnixGroupID;
#else // Need these Unix fields in Windows too for 'list' command.
uint UnixOwnerID;
uint UnixGroupID;
#endif
void Reset(size_t SubDataSize=0);
bool CmpName(const wchar *Name)
{
return FileName==Name;
}
// FileHeader& operator = (FileHeader &hd);
};
struct EndArcHeader:BaseBlock
{
// Optional CRC32 of entire archive up to start of EndArcHeader block.
// Present in RAR 4.x archives if EARC_DATACRC flag is set.
uint ArcDataCRC;
uint VolNumber; // Optional number of current volume.
// 7 additional zero bytes can be stored here if EARC_REVSPACE is set.
bool NextVolume; // Not last volume.
bool DataCRC;
bool RevSpace;
bool StoreVolNumber;
void Reset()
{
BaseBlock::Reset();
NextVolume=false;
DataCRC=false;
RevSpace=false;
StoreVolNumber=false;
}
};
struct CryptHeader:BaseBlock
{
bool UsePswCheck;
uint Lg2Count; // Log2 of PBKDF2 repetition count.
byte Salt[SIZE_SALT50];
byte PswCheck[SIZE_PSWCHECK];
};
// SubBlockHeader and its successors were used in RAR 2.x format.
// RAR 4.x uses FileHeader with HEAD_SERVICE HeaderType for subblocks.
struct SubBlockHeader:BlockHeader
{
ushort SubType;
byte Level;
};
struct CommentHeader:BaseBlock
{
ushort UnpSize;
byte UnpVer;
byte Method;
ushort CommCRC;
};
struct ProtectHeader:BlockHeader
{
byte Version;
ushort RecSectors;
uint TotalBlocks;
byte Mark[8];
};
struct EAHeader:SubBlockHeader
{
uint UnpSize;
byte UnpVer;
byte Method;
uint EACRC;
};
struct StreamHeader:SubBlockHeader
{
uint UnpSize;
byte UnpVer;
byte Method;
uint StreamCRC;
ushort StreamNameSize;
std::string StreamName;
};
#endif
+114
View File
@@ -0,0 +1,114 @@
#ifndef _RAR_HEADERS5_
#define _RAR_HEADERS5_
#define SIZEOF_MARKHEAD5 8 // RAR 5.0 signature length.
#define SIZEOF_SHORTBLOCKHEAD5 7 // Smallest RAR 5.0 block size.
// RAR 5.0 block flags common for all blocks.
// Additional extra area is present in the end of block header.
#define HFL_EXTRA 0x0001
// Additional data area is present in the end of block header.
#define HFL_DATA 0x0002
// Unknown blocks with this flag must be skipped when updating an archive.
#define HFL_SKIPIFUNKNOWN 0x0004
// Data area of this block is continuing from previous volume.
#define HFL_SPLITBEFORE 0x0008
// Data area of this block is continuing in next volume.
#define HFL_SPLITAFTER 0x0010
// Block depends on preceding file block.
#define HFL_CHILD 0x0020
// Preserve a child block if host is modified.
#define HFL_INHERITED 0x0040
// RAR 5.0 main archive header specific flags.
#define MHFL_VOLUME 0x0001 // Volume.
#define MHFL_VOLNUMBER 0x0002 // Volume number field is present. True for all volumes except first.
#define MHFL_SOLID 0x0004 // Solid archive.
#define MHFL_PROTECT 0x0008 // Recovery record is present.
#define MHFL_LOCK 0x0010 // Locked archive.
// RAR 5.0 file header specific flags.
#define FHFL_DIRECTORY 0x0001 // Directory.
#define FHFL_UTIME 0x0002 // Time field in Unix format is present.
#define FHFL_CRC32 0x0004 // CRC32 field is present.
#define FHFL_UNPUNKNOWN 0x0008 // Unknown unpacked size.
// RAR 5.0 end of archive header specific flags.
#define EHFL_NEXTVOLUME 0x0001 // Not last volume.
// RAR 5.0 archive encryption header specific flags.
#define CHFL_CRYPT_PSWCHECK 0x0001 // Password check data is present.
// RAR 5.0 file compression flags.
#define FCI_ALGO_BIT0 0x00000001 // Version of compression algorithm.
#define FCI_ALGO_BIT1 0x00000002 // 0 .. 63.
#define FCI_ALGO_BIT2 0x00000004
#define FCI_ALGO_BIT3 0x00000008
#define FCI_ALGO_BIT4 0x00000010
#define FCI_ALGO_BIT5 0x00000020
#define FCI_SOLID 0x00000040 // Solid flag.
#define FCI_METHOD_BIT0 0x00000080 // Compression method.
#define FCI_METHOD_BIT1 0x00000100 // 0 .. 5 (6 and 7 are not used).
#define FCI_METHOD_BIT2 0x00000200
#define FCI_DICT_BIT0 0x00000400 // Dictionary size.
#define FCI_DICT_BIT1 0x00000800 // 128 KB .. 1 TB.
#define FCI_DICT_BIT2 0x00001000
#define FCI_DICT_BIT3 0x00002000
#define FCI_DICT_BIT4 0x00004000
#define FCI_DICT_FRACT0 0x00008000 // Dictionary fraction in 1/32 of size.
#define FCI_DICT_FRACT1 0x00010000
#define FCI_DICT_FRACT2 0x00020000
#define FCI_DICT_FRACT3 0x00040000
#define FCI_DICT_FRACT4 0x00080000
#define FCI_RAR5_COMPAT 0x00100000 // RAR7 compression flags and RAR5 compression algorithm.
// Main header extra field values.
#define MHEXTRA_LOCATOR 0x01 // Position of quick list and other blocks.
#define MHEXTRA_METADATA 0x02 // Archive metadata.
// Flags for MHEXTRA_LOCATOR.
#define MHEXTRA_LOCATOR_QLIST 0x01 // Quick open offset is present.
#define MHEXTRA_LOCATOR_RR 0x02 // Recovery record offset is present.
// Flags for MHEXTRA_METADATA.
#define MHEXTRA_METADATA_NAME 0x01 // Archive name is present.
#define MHEXTRA_METADATA_CTIME 0x02 // Archive creation time is present.
#define MHEXTRA_METADATA_UNIXTIME 0x04 // Use Unix nanosecond time format.
#define MHEXTRA_METADATA_UNIX_NS 0x08 // Unix format with nanosecond precision.
// File and service header extra field values.
#define FHEXTRA_CRYPT 0x01 // Encryption parameters.
#define FHEXTRA_HASH 0x02 // File hash.
#define FHEXTRA_HTIME 0x03 // High precision file time.
#define FHEXTRA_VERSION 0x04 // File version information.
#define FHEXTRA_REDIR 0x05 // File system redirection (links, etc.).
#define FHEXTRA_UOWNER 0x06 // Unix owner and group information.
#define FHEXTRA_SUBDATA 0x07 // Service header subdata array.
// Hash type values for FHEXTRA_HASH.
#define FHEXTRA_HASH_BLAKE2 0x00
// Flags for FHEXTRA_HTIME.
#define FHEXTRA_HTIME_UNIXTIME 0x01 // Use Unix time_t format.
#define FHEXTRA_HTIME_MTIME 0x02 // mtime is present.
#define FHEXTRA_HTIME_CTIME 0x04 // ctime is present.
#define FHEXTRA_HTIME_ATIME 0x08 // atime is present.
#define FHEXTRA_HTIME_UNIX_NS 0x10 // Unix format with nanosecond precision.
// Flags for FHEXTRA_CRYPT.
#define FHEXTRA_CRYPT_PSWCHECK 0x01 // Store password check data.
#define FHEXTRA_CRYPT_HASHMAC 0x02 // Use MAC for unpacked data checksums.
// Flags for FHEXTRA_REDIR.
#define FHEXTRA_REDIR_DIR 0x01 // Link target is directory.
// Flags for FHEXTRA_UOWNER.
#define FHEXTRA_UOWNER_UNAME 0x01 // User name string is present.
#define FHEXTRA_UOWNER_GNAME 0x02 // Group name string is present.
#define FHEXTRA_UOWNER_NUMUID 0x04 // Numeric user ID is present.
#define FHEXTRA_UOWNER_NUMGID 0x08 // Numeric group ID is present.
#endif
+111
View File
@@ -0,0 +1,111 @@
#include "rar.hpp"
DWORD WinNT()
{
static int dwPlatformId=-1;
static DWORD dwMajorVersion,dwMinorVersion;
if (dwPlatformId==-1)
{
OSVERSIONINFO WinVer;
WinVer.dwOSVersionInfoSize=sizeof(WinVer);
GetVersionEx(&WinVer);
dwPlatformId=WinVer.dwPlatformId;
dwMajorVersion=WinVer.dwMajorVersion;
dwMinorVersion=WinVer.dwMinorVersion;
}
DWORD Result=0;
if (dwPlatformId==VER_PLATFORM_WIN32_NT)
Result=dwMajorVersion*0x100+dwMinorVersion;
return Result;
}
// Since Windows 10 development is stopped, we can assume that its build
// number never reaches 22000. So we do not need WMI anymore.
#if 0
// Replace it with documented Windows 11 check when available.
#include <comdef.h>
#include <Wbemidl.h>
#pragma comment(lib, "wbemuuid.lib")
static bool WMI_IsWindows10()
{
IWbemLocator *pLoc = NULL;
HRESULT hres = CoCreateInstance(CLSID_WbemLocator,0,CLSCTX_INPROC_SERVER,
IID_IWbemLocator,(LPVOID *)&pLoc);
if (FAILED(hres))
return false;
IWbemServices *pSvc = NULL;
hres = pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"),NULL,NULL,NULL,0,NULL,NULL,&pSvc);
if (FAILED(hres))
{
pLoc->Release();
return false;
}
hres = CoSetProxyBlanket(pSvc,RPC_C_AUTHN_WINNT,RPC_C_AUTHZ_NONE,NULL,
RPC_C_AUTHN_LEVEL_CALL,RPC_C_IMP_LEVEL_IMPERSONATE,NULL,EOAC_NONE);
if (FAILED(hres))
{
pSvc->Release();
pLoc->Release();
return false;
}
IEnumWbemClassObject *pEnumerator = NULL;
hres = pSvc->ExecQuery(bstr_t("WQL"), bstr_t("SELECT * FROM Win32_OperatingSystem"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator);
if (FAILED(hres) || pEnumerator==NULL)
{
pSvc->Release();
pLoc->Release();
return false;
}
bool Win10=false;
IWbemClassObject *pclsObj = NULL;
ULONG uReturn = 0;
pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
if (pclsObj!=NULL && uReturn>0)
{
VARIANT vtProp;
pclsObj->Get(L"Name", 0, &vtProp, 0, 0);
Win10|=wcsstr(vtProp.bstrVal,L"Windows 10")!=NULL;
VariantClear(&vtProp);
pclsObj->Release();
}
pSvc->Release();
pLoc->Release();
pEnumerator->Release();
return Win10;
}
#endif
// Replace it with actual check when available.
bool IsWindows11OrGreater()
{
static bool IsSet=false,IsWin11=false;
if (!IsSet)
{
OSVERSIONINFO WinVer;
WinVer.dwOSVersionInfoSize=sizeof(WinVer);
GetVersionEx(&WinVer);
IsWin11=WinVer.dwMajorVersion>10 ||
WinVer.dwMajorVersion==10 && WinVer.dwBuildNumber >= 22000/* && !WMI_IsWindows10()*/;
IsSet=true;
}
return IsWin11;
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef _RAR_ISNT_
#define _RAR_ISNT_
enum WINNT_VERSION {
WNT_NONE=0,WNT_NT351=0x0333,WNT_NT4=0x0400,WNT_W2000=0x0500,
WNT_WXP=0x0501,WNT_W2003=0x0502,WNT_VISTA=0x0600,WNT_W7=0x0601,
WNT_W8=0x0602,WNT_W81=0x0603,WNT_W10=0x0a00
};
DWORD WinNT();
// Replace it with actual check when available.
bool IsWindows11OrGreater();
#endif
+201
View File
@@ -0,0 +1,201 @@
#include "rar.hpp"
/*
To enable, disable or check Large Memory pages manually:
- open "Local Security Policy" from "Start Menu";
- open "Lock Pages in Memory" in "Local Policies\User Rights Assignment";
- add or remove the user and sign out and sign in or restart Windows.
*/
#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(RARDLL)
#define ALLOW_LARGE_PAGES
#endif
LargePageAlloc::LargePageAlloc()
{
UseLargePages=false;
#ifdef ALLOW_LARGE_PAGES
PageSize=0;
#endif
}
void LargePageAlloc::AllowLargePages(bool Allow)
{
#ifdef ALLOW_LARGE_PAGES
if (Allow && PageSize==0)
{
HMODULE hKernel=GetModuleHandle(L"kernel32.dll");
if (hKernel!=nullptr)
{
typedef SIZE_T (*GETLARGEPAGEMINIMUM)();
GETLARGEPAGEMINIMUM pGetLargePageMinimum=(GETLARGEPAGEMINIMUM)GetProcAddress(hKernel, "GetLargePageMinimum");
if (pGetLargePageMinimum!=nullptr)
PageSize=pGetLargePageMinimum();
}
if (PageSize==0 || !SetPrivilege(SE_LOCK_MEMORY_NAME))
{
UseLargePages=false;
return;
}
}
UseLargePages=Allow;
#endif
}
bool LargePageAlloc::IsPrivilegeAssigned()
{
#ifdef ALLOW_LARGE_PAGES
return SetPrivilege(SE_LOCK_MEMORY_NAME);
#else
return true;
#endif
}
bool LargePageAlloc::AssignPrivilege()
{
#ifdef ALLOW_LARGE_PAGES
HANDLE hToken = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
return false;
// Get the required buffer size.
DWORD BufSize=0;
GetTokenInformation(hToken, TokenUser, NULL, 0, &BufSize);
if (BufSize==0 || BufSize>1000000) // Sanity check for returned value.
{
CloseHandle(hToken);
return false;
}
TOKEN_USER *TokenInfo = (TOKEN_USER*)malloc(BufSize);
// Get the current user token information.
if (GetTokenInformation(hToken,TokenUser,TokenInfo,BufSize,&BufSize)==0)
{
CloseHandle(hToken);
return false;
}
// Get SID string for the current user.
LPWSTR ApiSidStr;
ConvertSidToStringSid(TokenInfo->User.Sid, &ApiSidStr);
// Convert SID to C++ string and release API based buffer.
std::wstring SidStr=ApiSidStr;
LocalFree(ApiSidStr);
CloseHandle(hToken);
if (IsUserAdmin())
AssignPrivilegeBySid(SidStr);
else
{
// Define here, so they survive until ShellExecuteEx call.
std::wstring ExeName=GetModuleFileStr();
std::wstring Param=std::wstring(L"-") + LOCKMEM_SWITCH + SidStr;
SHELLEXECUTEINFO shExecInfo{};
shExecInfo.cbSize = sizeof(shExecInfo);
shExecInfo.hwnd = NULL; // Specifying WinRAR main window here does not work well in command line mode.
shExecInfo.lpVerb = L"runas";
shExecInfo.lpFile = ExeName.c_str();
shExecInfo.lpParameters = Param.c_str();
shExecInfo.nShow = SW_SHOWNORMAL;
BOOL Result=ShellExecuteEx(&shExecInfo);
}
#endif
return true;
}
bool LargePageAlloc::AssignPrivilegeBySid(const std::wstring &Sid)
{
#ifdef ALLOW_LARGE_PAGES
LSA_HANDLE PolicyHandle;
LSA_OBJECT_ATTRIBUTES ObjectAttributes{}; // Docs require to zero initalize it.
#ifndef STATUS_SUCCESS // Can be defined through WIL package in WinRAR.
// We define STATUS_SUCCESS here instead of including ntstatus.h to avoid
// macro redefinition warnings. We tried UMDF_USING_NTSTATUS define
// and other workarounds, but it didn't help.
const uint STATUS_SUCCESS=0;
#endif
if (LsaOpenPolicy(NULL,&ObjectAttributes,POLICY_CREATE_ACCOUNT|
POLICY_LOOKUP_NAMES,&PolicyHandle)!=STATUS_SUCCESS)
return false;
PSID UserSid;
ConvertStringSidToSid(Sid.c_str(),&UserSid);
LSA_UNICODE_STRING LsaString;
LsaString.Buffer=(PWSTR)SE_LOCK_MEMORY_NAME;
// It must be in bytes, so multiple it to sizeof(wchar_t).
LsaString.Length=(USHORT)wcslen(LsaString.Buffer)*sizeof(LsaString.Buffer[0]);
LsaString.MaximumLength=LsaString.Length;
bool Success=LsaAddAccountRights(PolicyHandle,UserSid,&LsaString,1)==STATUS_SUCCESS;
LocalFree(UserSid);
LsaClose(PolicyHandle);
mprintf(St(MPrivilegeAssigned));
if (Ask(St(MYesNo)) == 1)
Shutdown(POWERMODE_RESTART);
return Success;
#else
return true;
#endif
}
bool LargePageAlloc::AssignConfirmation()
{
#ifdef ALLOW_LARGE_PAGES
mprintf(St(MLockInMemoryNeeded));
return Ask(St(MYesNo)) == 1;
#else
return false;
#endif
}
void* LargePageAlloc::new_large(size_t Size)
{
void *Allocated=nullptr;
#ifdef ALLOW_LARGE_PAGES
if (UseLargePages && Size>=PageSize)
{
// VirtualAlloc fails if allocation size isn't multiple of page size.
SIZE_T AllocSize=Size%PageSize==0 ? Size:(Size/PageSize+1)*PageSize;
Allocated=VirtualAlloc(nullptr,AllocSize,MEM_COMMIT|MEM_RESERVE|MEM_LARGE_PAGES,PAGE_READWRITE);
if (Allocated!=nullptr)
LargeAlloc.push_back(Allocated);
}
#endif
return Allocated;
}
bool LargePageAlloc::delete_large(void *Addr)
{
#ifdef ALLOW_LARGE_PAGES
if (Addr!=nullptr)
for (size_t I=0;I<LargeAlloc.size();I++)
if (LargeAlloc[I]==Addr)
{
LargeAlloc[I]=nullptr;
VirtualFree(Addr,0,MEM_RELEASE);
return true;
}
#endif
return false;
}
+54
View File
@@ -0,0 +1,54 @@
#ifndef _RAR_LARGEPAGE_
#define _RAR_LARGEPAGE_
class LargePageAlloc
{
private:
static constexpr const wchar *LOCKMEM_SWITCH=L"isetup_privilege_lockmem";
void* new_large(size_t Size);
bool delete_large(void *Addr);
#ifdef _WIN_ALL
std::vector<void*> LargeAlloc;
SIZE_T PageSize;
#endif
bool UseLargePages;
public:
LargePageAlloc();
void AllowLargePages(bool Allow);
static bool IsPrivilegeAssigned();
static bool AssignPrivilege();
static bool AssignPrivilegeBySid(const std::wstring &Sid);
static bool AssignConfirmation();
static bool ProcessSwitch(CommandData *Cmd,const wchar *Switch)
{
if (Switch[0]==LOCKMEM_SWITCH[0])
{
size_t Length=wcslen(LOCKMEM_SWITCH);
if (wcsncmp(Switch,LOCKMEM_SWITCH,Length)==0)
{
LargePageAlloc::AssignPrivilegeBySid(Switch+Length);
return true;
}
}
return false;
}
template <class T> T* new_l(size_t Size,bool Clear=false)
{
T *Allocated=(T*)new_large(Size*sizeof(T));
if (Allocated==nullptr)
Allocated=Clear ? new T[Size]{} : new T[Size];
return Allocated;
}
template <class T> void delete_l(T *Addr)
{
if (!delete_large(Addr))
delete[] Addr;
}
};
#endif
+42
View File
@@ -0,0 +1,42 @@
****** ***** ****** UnRAR - free utility for RAR archives
** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
****** ******* ****** License for use and distribution of
** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
** ** ** ** ** ** FREE portable version
~~~~~~~~~~~~~~~~~~~~~
The source code of UnRAR utility is freeware. This means:
1. All copyrights to RAR and the utility UnRAR are exclusively
owned by the author - Alexander Roshal.
2. UnRAR source code may be used in any software to handle
RAR archives without limitations free of charge, but cannot be
used to develop RAR (WinRAR) compatible archiver and to
re-create RAR compression algorithm, which is proprietary.
Distribution of modified UnRAR source code in separate form
or as a part of other software is permitted, provided that
full text of this paragraph, starting from "UnRAR source code"
words, is included in license, or in documentation if license
is not available, and in source code comments of resulting package.
3. The UnRAR utility may be freely distributed. It is allowed
to distribute UnRAR inside of other software packages.
4. THE RAR ARCHIVER AND THE UnRAR UTILITY ARE DISTRIBUTED "AS IS".
NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT
YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS,
DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING
OR MISUSING THIS SOFTWARE.
5. Installing and using the UnRAR utility signifies acceptance of
these terms and conditions of the license.
6. If you don't agree with terms of the license you must remove
UnRAR files from your storage devices and cease to use the
utility.
Thank you for your interest in RAR and UnRAR.
Alexander L. Roshal
+501
View File
@@ -0,0 +1,501 @@
#include "rar.hpp"
static void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare,bool DisableNames);
static void ListFileAttr(uint A,HOST_SYSTEM_TYPE HostType,wchar *AttrStr,size_t AttrSize);
static void ListOldSubHeader(Archive &Arc);
static void ListNewSubHeader(CommandData *Cmd,Archive &Arc);
void ListArchive(CommandData *Cmd)
{
int64 SumPackSize=0,SumUnpSize=0;
uint ArcCount=0,SumFileCount=0;
bool Technical=(Cmd->Command[1]=='T');
bool ShowService=Technical && Cmd->Command[2]=='A';
bool Bare=(Cmd->Command[1]=='B');
bool Verbose=(Cmd->Command[0]=='V');
std::wstring ArcName;
while (Cmd->GetArcName(ArcName))
{
if (Cmd->ManualPassword)
Cmd->Password.Clean(); // Clean user entered password before processing next archive.
Archive Arc(Cmd);
if (!Arc.WOpen(ArcName))
continue;
bool FileMatched=true;
while (true)
{
int64 TotalPackSize=0,TotalUnpSize=0;
uint FileCount=0;
if (Arc.IsArchive(true))
{
bool TitleShown=false;
if (!Bare)
{
Arc.ViewComment();
mprintf(L"\n%s: %s",St(MListArchive),Arc.FileName.c_str());
mprintf(L"\n%s: ",St(MListDetails));
const wchar *Fmt=Arc.Format==RARFMT14 ? L"RAR 1.4":(Arc.Format==RARFMT15 ? L"RAR 1.5":L"RAR 5");
mprintf(L"%s", Fmt);
if (Arc.Solid)
mprintf(L", %s", St(MListSolid));
if (Arc.SFXSize>0)
mprintf(L", %s", St(MListSFX));
if (Arc.Volume)
if (Arc.Format==RARFMT50)
{
// RAR 5.0 archives store the volume number in main header,
// so it is already available now.
mprintf(L", ");
mprintf(St(MVolumeNumber),Arc.VolNumber+1);
}
else
mprintf(L", %s", St(MListVolume));
if (Arc.Protected)
mprintf(L", %s", St(MListRR));
if (Arc.Locked)
mprintf(L", %s", St(MListLock));
if (Arc.Encrypted)
mprintf(L", %s", St(MListEncHead));
if (!Arc.MainHead.OrigName.empty())
mprintf(L"\n%s: %s",St(MOrigName),Arc.MainHead.OrigName.c_str());
if (Arc.MainHead.OrigTime.IsSet())
{
wchar DateStr[50];
Arc.MainHead.OrigTime.GetText(DateStr,ASIZE(DateStr),Technical);
mprintf(L"\n%s: %s",St(MOriginalTime),DateStr);
}
mprintf(L"\n");
}
wchar VolNumText[50];
*VolNumText=0;
while (Arc.ReadHeader()>0)
{
Wait(); // Allow quit listing with Ctrl+C.
HEADER_TYPE HeaderType=Arc.GetHeaderType();
if (HeaderType==HEAD_ENDARC)
{
#ifndef SFX_MODULE
// Only RAR 1.5 archives store the volume number in end record.
if (Arc.EndArcHead.StoreVolNumber && Arc.Format==RARFMT15)
swprintf(VolNumText,ASIZE(VolNumText),L"%.10ls %u",St(MListVolume),Arc.VolNumber+1);
#endif
if (Technical && ShowService)
{
mprintf(L"\n%12ls: %ls",St(MListService),L"EOF");
if (*VolNumText!=0)
mprintf(L"\n%12ls: %ls",St(MListFlags),VolNumText);
mprintf(L"\n");
}
break;
}
switch(HeaderType)
{
case HEAD_FILE:
FileMatched=Cmd->IsProcessFile(Arc.FileHead,NULL,MATCH_WILDSUBPATH,0,NULL)!=0;
if (FileMatched)
{
ListFileHeader(Arc,Arc.FileHead,TitleShown,Verbose,Technical,Bare,Cmd->DisableNames);
if (!Arc.FileHead.SplitBefore)
{
TotalUnpSize+=Arc.FileHead.UnpSize;
FileCount++;
}
TotalPackSize+=Arc.FileHead.PackSize;
}
break;
case HEAD_SERVICE:
// For service blocks dependent on previous block, such as ACL
// or NTFS stream, we use "file matched" flag of host file.
// Independent blocks like RR are matched separately,
// so we can list them by their name. Also we match even
// dependent blocks separately if "vta -idn" are set. User may
// want to see service blocks only in this case.
if (!Arc.SubHead.SubBlock || Cmd->DisableNames)
FileMatched=Cmd->IsProcessFile(Arc.SubHead,NULL,MATCH_WILDSUBPATH,0,NULL)!=0;
if (FileMatched && !Bare)
{
// Here we set DisableNames parameter to true regardless of
// Cmd->DisableNames. If "vta -idn" are set together, user
// wants to see service blocks like RR only.
if (Technical && ShowService)
ListFileHeader(Arc,Arc.SubHead,TitleShown,Verbose,true,false,false);
}
break;
}
Arc.SeekToNext();
}
if (!Bare && !Technical)
if (TitleShown)
{
wchar UnpSizeText[20];
itoa(TotalUnpSize,UnpSizeText,ASIZE(UnpSizeText));
wchar PackSizeText[20];
itoa(TotalPackSize,PackSizeText,ASIZE(PackSizeText));
if (Verbose)
{
mprintf(L"\n----------- --------- -------- ----- ---------- ----- -------- ----");
mprintf(L"\n%21ls %9ls %3d%% %-27ls %u",UnpSizeText,
PackSizeText,ToPercentUnlim(TotalPackSize,TotalUnpSize),
VolNumText,FileCount);
}
else
{
mprintf(L"\n----------- --------- ---------- ----- ----");
mprintf(L"\n%21ls %-16ls %u",UnpSizeText,VolNumText,FileCount);
}
SumFileCount+=FileCount;
SumUnpSize+=TotalUnpSize;
SumPackSize+=TotalPackSize;
mprintf(L"\n");
}
else
mprintf(St(MListNoFiles));
ArcCount++;
#ifndef NOVOLUME
if (Cmd->VolSize==VOLSIZE_AUTO && (Arc.FileHead.SplitAfter ||
Arc.GetHeaderType()==HEAD_ENDARC && Arc.EndArcHead.NextVolume) &&
MergeArchive(Arc,NULL,false,Cmd->Command[0]))
Arc.Seek(0,SEEK_SET);
else
#endif
break;
}
else
{
if (Cmd->ArcNames.ItemsCount()<2 && !Bare)
mprintf(St(MNotRAR),Arc.FileName.c_str());
break;
}
}
}
// Clean user entered password. Not really required, just for extra safety.
if (Cmd->ManualPassword)
Cmd->Password.Clean();
if (ArcCount>1 && !Bare && !Technical)
{
wchar UnpSizeText[20],PackSizeText[20];
itoa(SumUnpSize,UnpSizeText,ASIZE(UnpSizeText));
itoa(SumPackSize,PackSizeText,ASIZE(PackSizeText));
if (Verbose)
mprintf(L"%21ls %9ls %3d%% %28ls %u",UnpSizeText,PackSizeText,
ToPercentUnlim(SumPackSize,SumUnpSize),L"",SumFileCount);
else
mprintf(L"%21ls %18s %lu",UnpSizeText,L"",SumFileCount);
}
}
enum LISTCOL_TYPE {
LCOL_NAME,LCOL_ATTR,LCOL_SIZE,LCOL_PACKED,LCOL_RATIO,LCOL_CSUM,LCOL_ENCR
};
void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare,bool DisableNames)
{
if (!TitleShown && !Technical && !Bare)
{
if (Verbose)
{
mprintf(L"\n%ls",St(MListTitleV));
if (!DisableNames)
mprintf(L"\n----------- --------- -------- ----- ---------- ----- -------- ----");
}
else
{
mprintf(L"\n%ls",St(MListTitleL));
if (!DisableNames)
mprintf(L"\n----------- --------- ---------- ----- ----");
}
// Must be set even in DisableNames mode to suppress "0 files" output
// unless no files are matched.
TitleShown=true;
}
if (DisableNames)
return;
const wchar *Name=hd.FileName.c_str();
RARFORMAT Format=Arc.Format;
if (Bare)
{
mprintf(L"%s\n",Name);
return;
}
wchar UnpSizeText[30],PackSizeText[30];
if (hd.UnpSize==INT64NDF)
wcsncpyz(UnpSizeText,L"?",ASIZE(UnpSizeText));
else
itoa(hd.UnpSize,UnpSizeText,ASIZE(UnpSizeText));
itoa(hd.PackSize,PackSizeText,ASIZE(PackSizeText));
wchar AttrStr[30];
if (hd.HeaderType==HEAD_SERVICE)
swprintf(AttrStr,ASIZE(AttrStr),L"%cB",hd.Inherited ? 'I' : '.');
else
ListFileAttr(hd.FileAttr,hd.HSType,AttrStr,ASIZE(AttrStr));
wchar RatioStr[10];
if (hd.SplitBefore && hd.SplitAfter)
wcsncpyz(RatioStr,L"<->",ASIZE(RatioStr));
else
if (hd.SplitBefore)
wcsncpyz(RatioStr,L"<--",ASIZE(RatioStr));
else
if (hd.SplitAfter)
wcsncpyz(RatioStr,L"-->",ASIZE(RatioStr));
else
swprintf(RatioStr,ASIZE(RatioStr),L"%u%%",ToPercentUnlim(hd.PackSize,hd.UnpSize));
wchar DateStr[50];
hd.mtime.GetText(DateStr,ASIZE(DateStr),Technical);
if (Technical)
{
mprintf(L"\n%12s: %s",St(MListName),Name);
bool FileBlock=hd.HeaderType==HEAD_FILE;
if (!FileBlock && Arc.SubHead.CmpName(SUBHEAD_TYPE_STREAM))
{
mprintf(L"\n%12ls: %ls",St(MListType),St(MListStream));
std::wstring StreamName=GetStreamNameNTFS(Arc);
mprintf(L"\n%12ls: %ls",St(MListTarget),StreamName.c_str());
}
else
{
const wchar *Type=St(FileBlock ? (hd.Dir ? MListDir:MListFile):MListService);
if (hd.RedirType!=FSREDIR_NONE)
switch(hd.RedirType)
{
case FSREDIR_UNIXSYMLINK:
Type=St(MListUSymlink); break;
case FSREDIR_WINSYMLINK:
Type=St(MListWSymlink); break;
case FSREDIR_JUNCTION:
Type=St(MListJunction); break;
case FSREDIR_HARDLINK:
Type=St(MListHardlink); break;
case FSREDIR_FILECOPY:
Type=St(MListCopy); break;
}
mprintf(L"\n%12ls: %ls",St(MListType),Type);
if (hd.RedirType!=FSREDIR_NONE)
if (Format==RARFMT15)
{
std::string LinkTargetA;
if (Arc.FileHead.Encrypted)
{
// Link data are encrypted. We would need to ask for password
// and initialize decryption routine to display the link target.
LinkTargetA="*<-?->";
}
else
{
size_t DataSize=(size_t)Min(hd.PackSize,MAXPATHSIZE);
std::vector<char> Buf(DataSize+1);
Arc.Read(Buf.data(),DataSize);
Buf[DataSize] = 0;
LinkTargetA=Buf.data();
}
std::wstring LinkTarget;
CharToWide(LinkTargetA,LinkTarget);
mprintf(L"\n%12ls: %ls",St(MListTarget),LinkTarget.c_str());
}
else
mprintf(L"\n%12ls: %ls",St(MListTarget),hd.RedirName.c_str());
}
if (!hd.Dir)
{
mprintf(L"\n%12ls: %ls",St(MListSize),UnpSizeText);
mprintf(L"\n%12ls: %ls",St(MListPacked),PackSizeText);
mprintf(L"\n%12ls: %ls",St(MListRatio),RatioStr);
if (!FileBlock && Arc.SubHead.CmpName(SUBHEAD_TYPE_RR))
{
// Display the original -rrN percent if available.
int RecoveryPercent=Arc.GetRecoveryPercent();
if (RecoveryPercent>0) // It can be -1 if failed to detect.
mprintf(L"\n%12ls: %u%%",L"RR%", RecoveryPercent);
}
}
bool WinTitles=false;
#ifdef _WIN_ALL
WinTitles=true;
#endif
if (hd.mtime.IsSet())
mprintf(L"\n%12ls: %ls",St(WinTitles ? MListModified:MListMtime),DateStr);
if (hd.ctime.IsSet())
{
hd.ctime.GetText(DateStr,ASIZE(DateStr),true);
mprintf(L"\n%12ls: %ls",St(WinTitles ? MListCreated:MListCtime),DateStr);
}
if (hd.atime.IsSet())
{
hd.atime.GetText(DateStr,ASIZE(DateStr),true);
mprintf(L"\n%12ls: %ls",St(WinTitles ? MListAccessed:MListAtime),DateStr);
}
mprintf(L"\n%12ls: %ls",St(MListAttr),AttrStr);
if (hd.FileHash.Type==HASH_CRC32)
mprintf(L"\n%12ls: %8.8X",
hd.UseHashKey ? L"CRC32 MAC":hd.SplitAfter ? L"Pack-CRC32":L"CRC32",
hd.FileHash.CRC32);
if (hd.FileHash.Type==HASH_BLAKE2)
{
std::wstring BlakeStr;
BinToHex(hd.FileHash.Digest,BLAKE2_DIGEST_SIZE,BlakeStr);
mprintf(L"\n%12ls: %ls",
hd.UseHashKey ? L"BLAKE2 MAC":hd.SplitAfter ? L"Pack-BLAKE2":L"BLAKE2",
BlakeStr.c_str());
}
const wchar *HostOS=L"";
if (Format==RARFMT50 && hd.HSType!=HSYS_UNKNOWN)
HostOS=hd.HSType==HSYS_WINDOWS ? L"Windows":L"Unix";
if (Format==RARFMT15)
{
static const wchar *RarOS[]={
L"DOS",L"OS/2",L"Windows",L"Unix",L"Mac OS",L"BeOS",L"WinCE",L"",L"",L""
};
if (hd.HostOS<ASIZE(RarOS))
HostOS=RarOS[hd.HostOS];
}
if (*HostOS!=0)
mprintf(L"\n%12ls: %ls",St(MListHostOS),HostOS);
std::wstring WinSize;
if (!hd.Dir)
if (hd.WinSize%1073741824==0)
WinSize=L" -md=" + std::to_wstring(hd.WinSize/1073741824) + L"g";
else
if (hd.WinSize%1048576==0)
WinSize=L" -md=" + std::to_wstring(hd.WinSize/1048576) + L"m";
else
if (hd.WinSize>=1024)
WinSize=L" -md=" + std::to_wstring(hd.WinSize/1024) + L"k";
else
WinSize=L" -md=?";
mprintf(L"\n%12ls: RAR %ls(v%d) -m%d%s",St(MListCompInfo),
Format==RARFMT15 ? L"1.5":L"5.0",
hd.UnpVer==VER_UNKNOWN ? 0 : hd.UnpVer,hd.Method,WinSize.c_str());
if (hd.Solid || hd.Encrypted)
{
mprintf(L"\n%12ls: ",St(MListFlags));
if (hd.Solid)
mprintf(L"%ls ",St(MListSolid));
if (hd.Encrypted)
mprintf(L"%ls ",St(MListEnc));
}
if (hd.Version)
{
uint Version=ParseVersionFileName(hd.FileName,false);
if (Version!=0)
mprintf(L"\n%12ls: %u",St(MListFileVer),Version);
}
if (hd.UnixOwnerSet)
{
mprintf(L"\n%12ls: ",L"Unix owner");
if (*hd.UnixOwnerName!=0)
mprintf(L"%ls",GetWide(hd.UnixOwnerName).c_str());
else
if (hd.UnixOwnerNumeric)
mprintf(L"#%d",hd.UnixOwnerID);
mprintf(L":");
if (*hd.UnixGroupName!=0)
mprintf(L"%ls",GetWide(hd.UnixGroupName).c_str());
else
if (hd.UnixGroupNumeric)
mprintf(L"#%d",hd.UnixGroupID);
}
mprintf(L"\n");
return;
}
mprintf(L"\n%c%10ls %9ls ",hd.Encrypted ? '*' : ' ',AttrStr,UnpSizeText);
if (Verbose)
mprintf(L"%9ls %4ls ",PackSizeText,RatioStr);
mprintf(L" %ls ",DateStr);
if (Verbose)
{
if (hd.FileHash.Type==HASH_CRC32)
mprintf(L"%8.8X ",hd.FileHash.CRC32);
else
if (hd.FileHash.Type==HASH_BLAKE2)
{
byte *S=hd.FileHash.Digest;
mprintf(L"%02x%02x..%02x ",S[0],S[1],S[31]);
}
else
mprintf(hd.Dir ? L" ":L"???????? "); // Missing checksum is ok for folder, not for file.
}
mprintf(L"%ls",Name);
}
void ListFileAttr(uint A,HOST_SYSTEM_TYPE HostType,wchar *AttrStr,size_t AttrSize)
{
switch(HostType)
{
case HSYS_WINDOWS:
swprintf(AttrStr,AttrSize,L"%c%c%c%c%c%c%c",
(A & 0x2000)!=0 ? 'I' : '.', // Not content indexed.
(A & 0x0800)!=0 ? 'C' : '.', // Compressed.
(A & 0x0020)!=0 ? 'A' : '.', // Archive.
(A & 0x0010)!=0 ? 'D' : '.', // Directory.
(A & 0x0004)!=0 ? 'S' : '.', // System.
(A & 0x0002)!=0 ? 'H' : '.', // Hidden.
(A & 0x0001)!=0 ? 'R' : '.'); // Read-only.
break;
case HSYS_UNIX:
switch (A & 0xF000)
{
case 0x4000:
AttrStr[0]='d';
break;
case 0xA000:
AttrStr[0]='l';
break;
default:
AttrStr[0]='-';
break;
}
swprintf(AttrStr+1,AttrSize-1,L"%c%c%c%c%c%c%c%c%c",
(A & 0x0100) ? 'r' : '-',
(A & 0x0080) ? 'w' : '-',
(A & 0x0040) ? ((A & 0x0800)!=0 ? 's':'x'):((A & 0x0800)!=0 ? 'S':'-'),
(A & 0x0020) ? 'r' : '-',
(A & 0x0010) ? 'w' : '-',
(A & 0x0008) ? ((A & 0x0400)!=0 ? 's':'x'):((A & 0x0400)!=0 ? 'S':'-'),
(A & 0x0004) ? 'r' : '-',
(A & 0x0002) ? 'w' : '-',
(A & 0x0001) ? ((A & 0x200)!=0 ? 't' : 'x') : '-');
break;
case HSYS_UNKNOWN:
wcsncpyz(AttrStr,L"?",AttrSize);
break;
}
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef _RAR_LIST_
#define _RAR_LIST_
void ListArchive(CommandData *Cmd);
#endif
Loaded 100 of 192 files, more files were not shown because too many files have changed in this diff. Show more