From 0b9e60b2823bcceb8b360e2e5543465dc1b199a8 Mon Sep 17 00:00:00 2001 From: BinFlip Date: Fri, 18 Sep 2026 20:23:04 -0700 Subject: [PATCH] chore: release 0.1.4 - Replace em-dashes and en-dashes with ASCII hyphens across rustdoc, README, and sample notes. Reflow three doc comments where the replacement left a line starting with "- ", which rustdoc rendered as a stray list item. - Raise dependency minimums to current releases: pascalscript 0.1.3, bitflags 2.13.2, chacha20 0.10.2, codepage 0.1.3, encoding_rs 0.8.41, flate2 1.1.10. - Bump actions/checkout from v4 to v7 in CI and publish workflows. - Fix lints reported by current stable Clippy: use as_chunks::<2>() for UTF-16LE byte pairing and drop an unused test import. --- .github/workflows/ci.yml | 8 +-- .github/workflows/publish.yml | 2 +- CHANGELOG.md | 24 +++++++ Cargo.toml | 14 ++-- README.md | 18 ++--- examples/dump.rs | 4 +- src/analysis/compiledcode/innoapi.rs | 20 +++--- src/analysis/compiledcode/mod.rs | 4 +- src/analysis/exec.rs | 6 +- src/analysis/mod.rs | 8 +-- src/analysis/shortcut.rs | 4 +- src/crypto/arc4.rs | 6 +- src/crypto/kdflegacy.rs | 16 ++--- src/crypto/mod.rs | 8 +-- src/crypto/pbkdf2.rs | 12 ++-- src/crypto/xchacha20.rs | 10 +-- src/decompress/block.rs | 18 ++--- src/error.rs | 4 +- src/extract/bcj.rs | 10 +-- src/extract/chunk.rs | 16 ++--- src/extract/file.rs | 4 +- src/extract/slice.rs | 6 +- src/header.rs | 92 +++++++++++++------------- src/installer.rs | 56 ++++++++-------- src/overlay/offsettable.rs | 16 ++--- src/overlay/pe.rs | 8 +-- src/records/component.rs | 6 +- src/records/dataentry.rs | 30 ++++----- src/records/delete.rs | 12 ++-- src/records/directory.rs | 6 +- src/records/file.rs | 30 ++++----- src/records/icon.rs | 8 +-- src/records/ini.rs | 8 +-- src/records/isssigkey.rs | 4 +- src/records/item.rs | 16 ++--- src/records/language.rs | 16 ++--- src/records/message.rs | 4 +- src/records/mod.rs | 2 +- src/records/permission.rs | 2 +- src/records/registry.rs | 2 +- src/records/run.rs | 8 +-- src/records/task.rs | 6 +- src/records/type_.rs | 18 ++--- src/records/windows.rs | 26 ++++---- src/util/checksum.rs | 4 +- src/util/encoding.rs | 8 +-- src/version.rs | 2 +- tests/integration.rs | 30 ++++----- tests/samples/README.md | 30 ++++----- tests/samples/build/encrypted-full.iss | 6 +- tests/samples/build/encrypted.iss | 2 +- tests/samples/build/plain.iss | 2 +- 52 files changed, 353 insertions(+), 329 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52132bd..91437e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: matrix: rust: [stable, "1.88"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} @@ -27,7 +27,7 @@ jobs: name: Clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: components: clippy @@ -37,7 +37,7 @@ jobs: name: Format runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt @@ -47,7 +47,7 @@ jobs: name: Docs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable - run: cargo doc --no-deps --all-features env: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ab6da95..2c15968 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,7 +18,7 @@ jobs: contents: read id-token: write # required to mint the crates.io OIDC token steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 # merge-base needs real history diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c8acec..d212c7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project 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). +## [0.1.4] - 2026-09-18 + +### Changed + +- LZMA1 and LZMA2 decoding now uses `lzma-rust2` instead of `lzma-rs` (about 1.4x faster + on LZMA1, still pure Rust), and `flate2` uses the `zlib-rs` backend instead of + `miniz_oxide`. The LZMA2 dictionary size is taken from the property byte Inno writes + ahead of each chunk, so installers built with large dictionaries (for example + `lzma2/ultra64`) decode correctly. `lzma-rs` remains a dev-dependency for test fixtures. + Contributed by @maboloshi in #3. +- Replaced em-dashes and en-dashes with plain ASCII hyphens throughout rustdoc comments, + the README, and test-sample notes, so generated documentation renders consistently. + This also touches a few runtime strings: the one-line summaries returned by + `inno_api_description` and the section banners printed by the `dump` example. +- Raised the `pascalscript` minimum to 0.1.3 (documentation-only release upstream). +- Refreshed dependencies and raised the manifest minimums to the current releases: + `bitflags` 2.13.2, `chacha20` 0.10.2, `codepage` 0.1.3, `encoding_rs` 0.8.41, + `flate2` 1.1.10. +- Updated CI and publish workflows from `actions/checkout@v4` to `actions/checkout@v7`. +- UTF-16LE decoding now pairs bytes with `as_chunks::<2>()` instead of `chunks_exact(2)` + plus a slice copy, which clears the new `chunks_exact_to_as_chunks` lint on current + stable Clippy. Also dropped a test import that newer compilers report as unused. + ## [0.1.3] - 2026-08-09 ### Fixed @@ -58,6 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial published release. +[0.1.4]: https://github.com/ATRAPSLLC/innospect/compare/v0.1.3...v0.1.4 [0.1.3]: https://github.com/ATRAPSLLC/innospect/compare/v0.1.2...v0.1.3 [0.1.2]: https://github.com/ATRAPSLLC/innospect/compare/v0.1.1...v0.1.2 [0.1.1]: https://github.com/ATRAPSLLC/innospect/releases/tag/v0.1.1 diff --git a/Cargo.toml b/Cargo.toml index 55a76a5..4e49a04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "innospect" -version = "0.1.3" +version = "0.1.4" edition = "2024" rust-version = "1.88" description = "Parse and inspect Inno Setup installer binaries" @@ -28,11 +28,11 @@ arithmetic_side_effects = "deny" indexing_slicing = "deny" [dependencies] -bitflags = "2.13.0" +bitflags = "2.13.2" goblin = "0.10.7" -encoding_rs = "0.8.35" -codepage = "0.1.2" -flate2 = { version = "1.1.9", default-features = false, features = ["zlib-rs"] } +encoding_rs = "0.8.41" +codepage = "0.1.3" +flate2 = { version = "1.1.10", default-features = false, features = ["zlib-rs"] } lzma-rust2 = { version = "0.18", default-features = false, features = ["std", "optimization"] } bzip2 = "0.6.1" sha1 = "0.11.0" @@ -40,8 +40,8 @@ sha2 = "0.11.0" md-5 = "0.11.0" pbkdf2 = { version = "0.13.0", default-features = false } hmac = "0.13.0" -chacha20 = { version = "0.10.1", features = ["xchacha"] } -pascalscript = "0.1.2" +chacha20 = { version = "0.10.2", features = ["xchacha"] } +pascalscript = "0.1.3" [dev-dependencies] lzma-rs = "0.3" diff --git a/README.md b/README.md index 0747fc9..410be91 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A pure-Rust parser for [Inno Setup](https://jrsoftware.org/isinfo.php) installer binaries. Provides typed access to the loader stub overlay, setup headers, every typed record stream, and on-demand file extraction across Inno Setup 5.0 through -the 7.x preview series — including the modern XChaCha20 / `euFiles` / `euFull` +the 7.x preview series - including the modern XChaCha20 / `euFiles` / `euFull` encryption modes and the legacy ARC4 + SHA-1 / MD5 password-verifier path. Built for **malware analysis** and **reverse engineering**. The crate is @@ -16,31 +16,31 @@ arithmetic-with-side-effects are all denied at the lib root. ## Features -- **PE locator** — both the modern signature-scan path (5.1.5+) and the legacy +- **PE locator** - both the modern signature-scan path (5.1.5+) and the legacy `0x30` file-offset pointer used by pre-5.1.5 installers; tolerates BlackBox / GOG re-packagers. -- **Setup-0 decompression** — Stored / Zlib / LZMA1 / LZMA2 across every +- **Setup-0 decompression** - Stored / Zlib / LZMA1 / LZMA2 across every documented version cutover, with the standalone-encryption-header path for 6.5+ and the inline-verifier path for 6.4.x. -- **Setup-1 file extraction** — solid-LZMA chunks with per-file BCJ inverse +- **Setup-1 file extraction** - solid-LZMA chunks with per-file BCJ inverse filtering (4108 / 5200 / 5.3.9-flip variants) and SHA-256 / SHA-1 / MD5 / CRC-32 / Adler-32 checksum verification at EOF. -- **Per-version `TSetupHeader`** — every `String` / `AnsiString` / count / tail +- **Per-version `TSetupHeader`** - every `String` / `AnsiString` / count / tail field across the 1.x..7.x history, with `[Files]` / `[Run]` / `[Icons]` / `[Registry]` / `[INI]` / `[Components]` / `[Tasks]` / `[Types]` / `[Languages]` / `[CustomMessages]` / `[Permissions]` records typed individually. -- **Encryption** — XChaCha20 with PBKDF2-SHA-256 key derivation (Inno 6.4+); +- **Encryption** - XChaCha20 with PBKDF2-SHA-256 key derivation (Inno 6.4+); ARC4 with salted SHA-1 (5.3.9..6.4) or MD5 (4.2..5.3.9); CRC32 verifier (pre-4.2). Password trial via `from_bytes_with_passwords`. -- **Embedded `[Code]` / IFPS** — re-exports the +- **Embedded `[Code]` / IFPS** - re-exports the [`pascalscript`](https://crates.io/crates/pascalscript) container parser; the `inno_api_description` table maps Inno-runtime imports (`RegWriteStringValue`, `Exec`, `ShellExec`, …) to one-line summaries for triage. -- **Analysis API** — `exec_commands()` / `registry_ops()` / `shortcuts()` walk +- **Analysis API** - `exec_commands()` / `registry_ops()` / `shortcuts()` walk the relevant record streams and tag each entry with install-vs-uninstall phase, registry-write classification, or icon-target resolution. -- **Uninstaller reconstruction** — `extract_uninstaller()` patches the loader +- **Uninstaller reconstruction** - `extract_uninstaller()` patches the loader stub bytes back to the canonical `InUn` form. ## Quick start diff --git a/examples/dump.rs b/examples/dump.rs index 1bc6192..8137a8a 100644 --- a/examples/dump.rs +++ b/examples/dump.rs @@ -173,7 +173,7 @@ fn main() { println!(); println!( - "== Records (3c lightweight) — languages={} messages={} permissions={} types={} components={} tasks={} ==", + "== Records (3c lightweight) - languages={} messages={} permissions={} types={} components={} tasks={} ==", installer.languages().len(), installer.messages().len(), installer.permissions().len(), @@ -204,7 +204,7 @@ fn main() { println!(); println!( - "== Records (3d heavy) — dirs={} files={} icons={} ini={} reg={} ins_del={} unins_del={} run={} unins_run={} file_loc={} ==", + "== Records (3d heavy) - dirs={} files={} icons={} ini={} reg={} ins_del={} unins_del={} run={} unins_run={} file_loc={} ==", installer.directories().len(), installer.files().len(), installer.icons().len(), diff --git a/src/analysis/compiledcode/innoapi.rs b/src/analysis/compiledcode/innoapi.rs index 0ea35e5..07e415c 100644 --- a/src/analysis/compiledcode/innoapi.rs +++ b/src/analysis/compiledcode/innoapi.rs @@ -3,23 +3,23 @@ //! //! Inno's `Setup.exe` registers ~200 functions / procedures that //! `[Code]` scripts can call. The canonical inventory lives in -//! `research/issrc/Projects/Src/Shared.ScriptFunc.pas` — +//! `research/issrc/Projects/Src/Shared.ScriptFunc.pas` - //! `ScriptFuncTables[sftScriptDlg]`, `[sftCommonFunc]`, //! `[sftPathFunc]`, `[sftInstall]`, `[sftInstFunc]`, -//! `[sftMainFunc]`, etc. — plus the `DelphiScriptFuncTable` of +//! `[sftMainFunc]`, etc. - plus the `DelphiScriptFuncTable` of //! pre-registered runtime helpers. //! //! This table covers the security-relevant subset analysts care //! about most: registry mutations, command execution, file //! operations, network downloads, and privilege checks. It is -//! curated rather than exhaustive — adding remaining categories +//! curated rather than exhaustive - adding remaining categories //! is mechanical (transcribe the names + write one-liner //! descriptions) and tracked separately. Lookups are by exact //! ASCII name, case-sensitive (PascalScript identifiers //! collide-detect on hash, but the wire form preserves the //! author's casing). -/// One row of the lookup table — name + short, single-line +/// One row of the lookup table - name + short, single-line /// description. type Row = (&'static str, &'static str); @@ -104,7 +104,7 @@ pub const INNO_API: &[Row] = &[ ), ( "ShellExec", - "Wraps `ShellExecuteEx` — opens a file or URL via the shell's verb dispatcher.", + "Wraps `ShellExecuteEx` - opens a file or URL via the shell's verb dispatcher.", ), ( "ShellExecAsOriginalUser", @@ -159,11 +159,11 @@ pub const INNO_API: &[Row] = &[ ), ( "DownloadTemporaryFileSize", - "HEAD request — returns the Content-Length of a remote resource.", + "HEAD request - returns the Content-Length of a remote resource.", ), ( "DownloadTemporaryFileDate", - "HEAD request — returns the Last-Modified header of a remote resource.", + "HEAD request - returns the Last-Modified header of a remote resource.", ), ( "SetDownloadCredentials", @@ -270,21 +270,21 @@ pub const INNO_API: &[Row] = &[ ), ( "WizardForm", - "Returns the running `TWizardForm` instance — exposes the live install UI.", + "Returns the running `TWizardForm` instance - exposes the live install UI.", ), ]; /// Looks up `name` in [`INNO_API`] via binary search and returns /// the short description if found. /// -/// Returns `None` for names not in the table — this includes both +/// Returns `None` for names not in the table - this includes both /// genuinely-unknown imports and the long tail of registered /// helpers that aren't worth a description for analyst purposes /// (sorting, string-mangling, math, etc.). pub fn inno_api_description(name: &str) -> Option<&'static str> { // Lookup is **case-insensitive ASCII**. PascalScript hashes // identifiers via `MakeHash` (`uPSUtils.pas:701-708`), which - // is itself case-sensitive — but Inno's own registration path + // is itself case-sensitive - but Inno's own registration path // upper-cases names before they hit the wire, so a `[Code]` // import for `ShellExec` materializes as `SHELLEXEC`. The // table is hand-keyed mixed-case for readability; the diff --git a/src/analysis/compiledcode/mod.rs b/src/analysis/compiledcode/mod.rs index e2fe82d..9c11592 100644 --- a/src/analysis/compiledcode/mod.rs +++ b/src/analysis/compiledcode/mod.rs @@ -1,12 +1,12 @@ //! Inno-side wrapper for the embedded `[Code]` PascalScript blob. //! //! The structural parser lives in the standalone -//! [`pascalscript`] crate (sibling repo at `../pascalscript/`) — +//! [`pascalscript`] crate (sibling repo at `../pascalscript/`) - //! fully host-agnostic, since RemObjects PascalScript III is //! used by many Delphi applications beyond Inno Setup. This //! module re-exports the parser's [`Container`] type for //! analyst convenience and adds one Inno-specific helper: -//! [`inno_api_description`] — a table mapping +//! [`inno_api_description`] - a table mapping //! Inno's runtime-registered PascalScript externals (the names a //! `[Code]` script can call) to short descriptions. diff --git a/src/analysis/exec.rs b/src/analysis/exec.rs index 7d2f8b4..5d79b89 100644 --- a/src/analysis/exec.rs +++ b/src/analysis/exec.rs @@ -14,9 +14,9 @@ use crate::records::run::RunEntry; /// Whether a command runs at install time or at uninstall time. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ExecPhase { - /// `[Run]` — fires after files are copied. + /// `[Run]` - fires after files are copied. Install, - /// `[UninstallRun]` — fires before files are removed. + /// `[UninstallRun]` - fires before files are removed. Uninstall, } @@ -35,7 +35,7 @@ pub struct ExecCommand<'a> { } impl<'a> ExecCommand<'a> { - /// Returns the `Filename:` directive — the executable, batch + /// Returns the `Filename:` directive - the executable, batch /// file, or shell URL to invoke. Inno calls this field `Name` /// internally; the analyst-friendly view exposes it as /// `filename` to match the `[Run]` directive name. diff --git a/src/analysis/mod.rs b/src/analysis/mod.rs index cce309c..9f373b2 100644 --- a/src/analysis/mod.rs +++ b/src/analysis/mod.rs @@ -6,12 +6,12 @@ //! parsed records. The submodules here re-shape those records into //! analyst-friendly views: //! -//! - [`exec`] — install + uninstall command execution. -//! - [`registryop`] — registry mutations classified by operation +//! - [`exec`] - install + uninstall command execution. +//! - [`registryop`] - registry mutations classified by operation //! kind. -//! - [`shortcut`] — `[Icons]` entries joined to their `[Files]` +//! - [`shortcut`] - `[Icons]` entries joined to their `[Files]` //! targets where possible. -//! - [`compiledcode`] — IFPS container fingerprint for the +//! - [`compiledcode`] - IFPS container fingerprint for the //! compiled `[Code]` blob (header-only at the moment). //! //! Every view here borrows from the underlying record or buffer; diff --git a/src/analysis/shortcut.rs b/src/analysis/shortcut.rs index 1fb21e6..f9ccb6b 100644 --- a/src/analysis/shortcut.rs +++ b/src/analysis/shortcut.rs @@ -3,7 +3,7 @@ //! //! Inno's `[Icons]` directive declares shortcut metadata //! (`Filename:`, `Parameters:`, `WorkingDir:`, `IconFile:`) but -//! doesn't itself materialize the binary the shortcut points at — +//! doesn't itself materialize the binary the shortcut points at - //! that comes from a separate `[Files]` entry. For analyst code //! that wants to know "which executables in this installer get a //! shortcut?", the natural shape is the join. @@ -24,7 +24,7 @@ pub struct Shortcut<'a> { /// The icon entry that defines the shortcut. pub icon: &'a IconEntry, /// The matched [`FileEntry`] whose `destination` equals - /// `icon.filename`, or `None` if no `[Files]` entry matches — + /// `icon.filename`, or `None` if no `[Files]` entry matches - /// e.g. system-path shortcuts (`{sys}\notepad.exe`), URLs, or /// shortcuts to files installed by an `[Code]` script. pub target: Option<&'a FileEntry>, diff --git a/src/crypto/arc4.rs b/src/crypto/arc4.rs index 4f7e951..a85c82a 100644 --- a/src/crypto/arc4.rs +++ b/src/crypto/arc4.rs @@ -7,7 +7,7 @@ //! in a separate crate because: //! //! - The algorithm is tiny and trivially auditable. -//! - It's only used for legacy chunk decryption — no other crate +//! - It's only used for legacy chunk decryption - no other crate //! in our dep graph wants it. //! - Innoextract takes the same approach (`research/src/crypto/arc4.cpp`). //! @@ -64,13 +64,13 @@ impl Rc4 { /// Advances the keystream by `n` bytes without applying them /// to any buffer. Inno's pre-6.4 chunk decryption discards the - /// first 1000 bytes of the keystream after KSA — biased early + /// first 1000 bytes of the keystream after KSA - biased early /// bytes were the WEP attack surface, and ISCrypt.dll mirrors /// the standard RC4-drop hardening (`research/src/stream/chunk.cpp:72`, /// `arc4.discard(1000)`). Skipping the discard produces a /// keystream that XORs cleanly through password verification /// (which doesn't use the chunk RC4) but yields garbage at the - /// chunk-decrypt step — observable as malformed LZMA streams. + /// chunk-decrypt step - observable as malformed LZMA streams. pub(crate) fn discard(&mut self, n: usize) { for _ in 0..n { self.i = self.i.wrapping_add(1); diff --git a/src/crypto/kdflegacy.rs b/src/crypto/kdflegacy.rs index e0ad7ff..20159a6 100644 --- a/src/crypto/kdflegacy.rs +++ b/src/crypto/kdflegacy.rs @@ -58,12 +58,12 @@ pub(crate) fn legacy_hash_family(version: &crate::version::Version) -> LegacyHas pub(crate) enum LegacyStoredHash { /// `< 4.2.0`. Crc32(u32), - /// `4.2.0..4.2.2` — bare MD5 (no salt). `salt` is unused but + /// `4.2.0..4.2.2` - bare MD5 (no salt). `salt` is unused but /// kept for API uniformity. Md5Bare([u8; 16]), - /// `4.2.2..5.3.9` — MD5 + 8-byte salt + literal prefix. + /// `4.2.2..5.3.9` - MD5 + 8-byte salt + literal prefix. Md5Salted { hash: [u8; 16], salt: [u8; 8] }, - /// `5.3.9..6.4` — SHA-1 + 8-byte salt + literal prefix. + /// `5.3.9..6.4` - SHA-1 + 8-byte salt + literal prefix. Sha1Salted { hash: [u8; 20], salt: [u8; 8] }, } @@ -74,7 +74,7 @@ pub(crate) const PASSWORD_CHECK_HASH_PREFIX: &[u8] = b"PasswordCheckHash"; /// Verifies a candidate password against the stored hash form. /// `password` is encoded according to the installer's wire string -/// type — UTF-16LE for Unicode builds (5.6+ default, plus any pre-5.6 +/// type - UTF-16LE for Unicode builds (5.6+ default, plus any pre-5.6 /// build with the `(u)` marker), Windows-1252 for ANSI builds. This /// matches innoextract `info::get_key` (`research/src/setup/info.cpp:322-352`) /// which feeds the password to the hasher via `util::from_utf8(..., @@ -135,10 +135,10 @@ pub(crate) fn verify_password_legacy( /// (`research/src/setup/info.cpp:322-352`), and the chunk /// decryptor (`research/src/stream/chunk.cpp:196-202`) hashes /// `salt || key` as-is. Using the wrong encoding produces a -/// valid-looking RC4 key that decrypts to garbage — observable as +/// valid-looking RC4 key that decrypts to garbage - observable as /// LZMA stream-format errors, not a clean failure. /// -/// Returns the **full** hash digest as the RC4 key — 20 bytes for +/// Returns the **full** hash digest as the RC4 key - 20 bytes for /// SHA-1 (5.3.9..6.4 era) and 16 bytes for MD5 (pre-5.3.9). RC4's /// KSA accepts variable-length keys, so the digest is fed in /// unmodified. @@ -282,7 +282,7 @@ mod tests { let sha1_key = arc4_chunk_key("test", &salt, true, true); assert_eq!(md5_key.len(), 16, "MD5 digest"); assert_eq!(sha1_key.len(), 20, "SHA-1 digest"); - // First 16 bytes are guaranteed to differ — different + // First 16 bytes are guaranteed to differ - different // hash families. assert_ne!(&md5_key[..], &sha1_key[..16]); } @@ -298,7 +298,7 @@ mod tests { } /// ANSI vs Unicode password encoding produces different keys - /// for the same password — the bug surfaced by the new + /// for the same password - the bug surfaced by the new /// chunk-encrypted ANSI samples (5.5.7 / 6.0.0u / 6.3.0). #[test] fn arc4_key_ansi_differs_from_unicode() { diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 534811d..dad8e66 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -2,15 +2,15 @@ //! //! Layout: //! -//! - [`pbkdf2`] — PBKDF2-HMAC-SHA256 KDF for 6.4+, with the +//! - [`pbkdf2`] - PBKDF2-HMAC-SHA256 KDF for 6.4+, with the //! UTF-16LE password encoding the Pascal source uses. -//! - [`xchacha20`] — XChaCha20 stream cipher used by 6.4+ for +//! - [`xchacha20`] - XChaCha20 stream cipher used by 6.4+ for //! chunk encryption and `euFull` setup-0 encryption. Includes //! the per-chunk nonce derivation (`base XOR (start, slice)`) //! and the three "special" crypt contexts (`sccPasswordTest`, //! `sccCompressedBlocks1`, `sccCompressedBlocks2`). -//! - [`arc4`] — inline RC4 for pre-6.4 chunk encryption. -//! - [`kdflegacy`] — pre-6.4 password-verification families +//! - [`arc4`] - inline RC4 for pre-6.4 chunk encryption. +//! - [`kdflegacy`] - pre-6.4 password-verification families //! (CRC32 / MD5 / SHA-1) and ARC4 chunk-key derivation. //! //! Everything in this module is validated against published test diff --git a/src/crypto/pbkdf2.rs b/src/crypto/pbkdf2.rs index 2bb0945..79ee4d2 100644 --- a/src/crypto/pbkdf2.rs +++ b/src/crypto/pbkdf2.rs @@ -7,7 +7,7 @@ //! `research-notes/08-issrc-encryption.md` §D. //! //! The Pascal `String → bytes` conversion for the password is -//! **UTF-16LE** (no BOM, no NUL terminator) — Delphi 2009+ uses +//! **UTF-16LE** (no BOM, no NUL terminator) - Delphi 2009+ uses //! `UnicodeString` and `StringToBytes` does a raw `Move` of the //! `WideChar` array (`SizeOf(S[1]) = 2`). Failing to encode this //! way is a silent password-mismatch on every candidate. @@ -37,10 +37,10 @@ pub(crate) fn password_bytes_utf16le(password: &str) -> Vec { /// security tweak that did `var F := U; FillChar(U[0], ...)`, /// which (because Delphi dynamic arrays don't copy on assign) /// zeroed `F` along with `U` after the first iteration's -/// `U := NewU`. Net effect for a single-block (L=1) key — which +/// `U := NewU`. Net effect for a single-block (L=1) key - which /// is every Inno verifier and chunk key, since /// `TSetupEncryptionKey` is 32 bytes and SHA-256's HashSize is -/// also 32 — is that the result is missing one XOR'd term: +/// also 32 - is that the result is missing one XOR'd term: /// `result = U_1 XOR U_2 XOR ... XOR U_N` becomes /// `result = U_2 XOR ... XOR U_N`. Equivalently: /// `result_buggy = result_standard XOR U_1`. @@ -48,7 +48,7 @@ pub(crate) fn password_bytes_utf16le(password: &str) -> Vec { /// Fixed in commit `90f06e4d` (2026-04-21, post `is-7_0_0_2`). /// Apply this helper only when the installer's SetupBinVersion /// matches preview-3 (marker `(7.0.0.1)` and SetupBinVersion -/// 7.0.0.2 — they coexist because `is-7_0_0_2` did not bump +/// 7.0.0.2 - they coexist because `is-7_0_0_2` did not bump /// `SetupID`). pub(crate) fn derive_key_buggy_700_preview3( password: &str, @@ -104,7 +104,7 @@ mod tests { /// PBKDF2-HMAC-SHA256 reference vector (RFC 7914-derived; /// matches Python's `hashlib.pbkdf2_hmac('sha256', ...)`). /// Note: our `derive_key` UTF-16LE-encodes the password, so we - /// can't use it directly for the raw RFC vector — that test + /// can't use it directly for the raw RFC vector - that test /// goes through the lower-level `pbkdf2` crate API directly. #[test] fn raw_pbkdf2_sha256_one_iteration_matches_python() { @@ -147,7 +147,7 @@ mod tests { // (re-verifiable via `python3 -c` if needed) let salt = [0u8; 16]; let key = derive_key("test123", &salt, 1); - // Round-trip through our `pbkdf2` call — the UTF-16LE + // Round-trip through our `pbkdf2` call - the UTF-16LE // encoding here is `74 00 65 00 73 00 74 00 31 00 32 00 33 00`. let mut expected = [0u8; 32]; let pwd_utf16: Vec = "test123" diff --git a/src/crypto/xchacha20.rs b/src/crypto/xchacha20.rs index a52a197..9d9b1ac 100644 --- a/src/crypto/xchacha20.rs +++ b/src/crypto/xchacha20.rs @@ -30,11 +30,11 @@ pub(crate) type Nonce = [u8; 24]; /// `SpecialFirstSlice = -1 - typ_index`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum SpecialContext { - /// Verifier — `SpecialFirstSlice = -1`. + /// Verifier - `SpecialFirstSlice = -1`. PasswordTest, - /// `euFull` setup-0 first block — `SpecialFirstSlice = -2`. + /// `euFull` setup-0 first block - `SpecialFirstSlice = -2`. CompressedBlocks1, - /// `euFull` setup-0 second block — `SpecialFirstSlice = -3`. + /// `euFull` setup-0 second block - `SpecialFirstSlice = -3`. CompressedBlocks2, } @@ -67,7 +67,7 @@ pub(crate) fn chunk_nonce(base: &Nonce, start_offset: u64, first_slice: i32) -> let mut out = *base; // Bytes 0..7: i64 LE XOR with `start_offset` (Inno treats as - // signed Int64 — but XOR is bit-identical between the two). + // signed Int64 - but XOR is bit-identical between the two). let off_bytes = start_offset.to_le_bytes(); for i in 0..8 { let Some(dst) = out.get_mut(i) else { @@ -189,7 +189,7 @@ mod tests { /// XChaCha20 IETF reference test vector from /// . /// Note the nonce's LAST byte is `0x58` (not the natural - /// `0x57` sequence) — the draft's chosen nonce is + /// `0x57` sequence) - the draft's chosen nonce is /// `404142...5658`. Verbatim copy from the `chacha20` crate's /// own test (`tests/mod.rs::xchacha20::xchacha20_keystream`) /// so we get an end-to-end check that the bytes coming out of diff --git a/src/decompress/block.rs b/src/decompress/block.rs index 790c80a..69009b2 100644 --- a/src/decompress/block.rs +++ b/src/decompress/block.rs @@ -10,7 +10,7 @@ //! [ expected_crc32 (4) | stored_size (4) | compressed_flag (1) ] //! ``` //! -//! `expected_crc32` covers `stored_size` + `compressed_flag` only — +//! `expected_crc32` covers `stored_size` + `compressed_flag` only - //! it is the integrity check on the *outer* header itself, not the //! payload. `compressed_flag == 0` ⇒ Stored. Otherwise: Zlib for //! `< 4.1.6`, LZMA1 for `≥ 4.1.6`. @@ -65,12 +65,12 @@ const CHUNK_CRC_LEN: usize = 4; #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum BlockCompression { - /// `compressed_flag == 0` — sub-chunks are uncompressed. + /// `compressed_flag == 0` - sub-chunks are uncompressed. Stored, - /// `< 4.1.6` and `compressed_flag != 0` — Deflate stream after + /// `< 4.1.6` and `compressed_flag != 0` - Deflate stream after /// CRC stripping. Zlib, - /// `≥ 4.1.6` and `compressed_flag != 0` — LZMA1 stream with + /// `≥ 4.1.6` and `compressed_flag != 0` - LZMA1 stream with /// Inno's 5-byte properties header. Lzma1, } @@ -84,7 +84,7 @@ pub struct DecompressedBlock { pub bytes: Box<[u8]>, /// Compression method that produced `bytes`. pub compression: BlockCompression, - /// Total bytes consumed from the input — outer header (9 bytes + /// Total bytes consumed from the input - outer header (9 bytes /// for ≥ 4.0.9, 12 for < 4.0.9) plus `stored_size`. pub consumed: usize, } @@ -92,7 +92,7 @@ pub struct DecompressedBlock { /// Decompresses one block from `setup0[start..]`. /// /// `start` is the byte offset of the block within the (already-located) -/// setup-0 region — i.e. where the outer header begins. The function +/// setup-0 region - i.e. where the outer header begins. The function /// reads the outer header, validates its CRC, walks the inner 4 KiB /// CRC-framed sub-chunks, and feeds the concatenated compressed bytes /// to the appropriate decompressor. @@ -204,7 +204,7 @@ fn parse_outer_header( if version.at_least(6, 7, 0) { // Inno Setup 6.7.0+ widened `TCompressedBlockHeader.StoredSize` - // from `Integer` (Int32) to `Int64` — see issrc commit + // from `Integer` (Int32) to `Int64` - see issrc commit // `8f02a4c0` (2025-11-26, "Update totals to Int64"). The // outer header is now 4 (CRC) + 8 (size) + 1 (flag) = 13 // bytes; the CRC covers the trailing 9 bytes only. @@ -220,7 +220,7 @@ fn parse_outer_header( let [s0, s1, s2, s3, s4, s5, s6, s7, flag] = header9; let stored_size_u64 = u64::from_le_bytes([s0, s1, s2, s3, s4, s5, s6, s7]); // Downstream still expects u32. Reject sizes ≥ 4 GiB rather - // than truncate silently — these would be pathological setup-0 + // than truncate silently - these would be pathological setup-0 // blocks (or adversarial input). let stored_size = u32::try_from(stored_size_u64).map_err(|_| Error::Overflow { what: "block stored_size > u32::MAX", @@ -272,7 +272,7 @@ fn parse_outer_header( (compressed_size, BlockCompression::Zlib) }; // Older path stores the 4 KiB CRC overhead *outside* the size - // figure — add it back so callers can locate the next block. + // figure - add it back so callers can locate the next block. // Compute `chunks = ceil(stored_size / CHUNK_SIZE)` = // `(stored_size + CHUNK_SIZE - 1) / CHUNK_SIZE` using // checked arithmetic. diff --git a/src/error.rs b/src/error.rs index ddff77e..092b018 100644 --- a/src/error.rs +++ b/src/error.rs @@ -72,7 +72,7 @@ pub enum Error { /// uninstaller stub which doesn't carry payload bytes. NoLocation, /// The chunk is encrypted (`ChunkEncrypted` flag set on the - /// `DataEntry`) and no decryption key is available — typically + /// `DataEntry`) and no decryption key is available - typically /// because the caller didn't supply a password. Encrypted, /// The installer is encrypted but the candidate-password list @@ -125,7 +125,7 @@ pub enum Error { actual: String, }, /// Wrapper for a parse failure inside the embedded - /// PascalScript blob — surfaced through + /// PascalScript blob - surfaced through /// [`crate::InnoInstaller::compiledcode`]. The wrapped /// [`pascalscript::Error`] is fully self-contained; /// this variant exists so `innospect::Error` can be the single diff --git a/src/extract/bcj.rs b/src/extract/bcj.rs index dfe4f65..f8fc118 100644 --- a/src/extract/bcj.rs +++ b/src/extract/bcj.rs @@ -26,12 +26,12 @@ use crate::error::Error; /// + version. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Filter { - /// Pre-5.2.0 — `inno_exe_decoder_4108`, byte-by-byte CALL/JMP + /// Pre-5.2.0 - `inno_exe_decoder_4108`, byte-by-byte CALL/JMP /// address rewrite. V4108, - /// 5.2.0..5.3.9 — `inno_exe_decoder_5200` without high-byte flip. + /// 5.2.0..5.3.9 - `inno_exe_decoder_5200` without high-byte flip. V5200, - /// 5.3.9+ — `inno_exe_decoder_5200` with high-byte flip. + /// 5.3.9+ - `inno_exe_decoder_5200` with high-byte flip. V5_3_9, } @@ -51,7 +51,7 @@ impl Filter { } /// Applies the inverse filter to `buf` in place. The whole file - /// content must be passed in one call — internal state is + /// content must be passed in one call - internal state is /// initialised at the start and not preserved across calls. /// /// # Errors @@ -93,7 +93,7 @@ fn apply_4108(buf: &mut [u8]) -> Result<(), Error> { } } else { addr = addr.wrapping_add(u32::from(byte)); - // Cast cannot truncate meaningfully — we want the low byte. + // Cast cannot truncate meaningfully - we want the low byte. #[allow(clippy::cast_possible_truncation)] { emit = addr as u8; diff --git a/src/extract/chunk.rs b/src/extract/chunk.rs index 9a4950d..88e1298 100644 --- a/src/extract/chunk.rs +++ b/src/extract/chunk.rs @@ -1,7 +1,7 @@ //! Chunk reader: takes a `(first_slice, start_offset)` pair and //! returns the **decompressed** bytes for that chunk. //! -//! Chunks are deduplicated by `(first_slice, start_offset)` — +//! Chunks are deduplicated by `(first_slice, start_offset)` - //! multiple `FileLocation` entries can point at the same compressed //! chunk (solid LZMA mode is the common case). The //! [`crate::installer::InnoInstaller`] holds one @@ -40,7 +40,7 @@ use crate::{ /// Encryption parameters threaded through chunk decompression. /// Two variants distinguish the modern (6.4+ XChaCha20) and the -/// legacy (pre-6.4 ARC4) paths — they have different key +/// legacy (pre-6.4 ARC4) paths - they have different key /// derivation, different on-disk chunk layouts (legacy has an /// 8-byte per-chunk salt prefix), and obviously different ciphers. pub(crate) enum EncryptionContext<'a> { @@ -61,7 +61,7 @@ pub(crate) enum EncryptionContext<'a> { /// MD5 (pre-5.3.9). The 8-byte salt sits inside the chunk /// body, just after the `zlb\x1a` magic. `password_bytes` is /// UTF-16LE for Unicode builds and Windows-1252 for ANSI - /// builds — innoextract picks the codepage in `info::get_key` + /// builds - innoextract picks the codepage in `info::get_key` /// (`research/src/setup/info.cpp:322-352`). Legacy { password: &'a str, @@ -110,7 +110,7 @@ pub(crate) fn decompress_chunk( // Read magic + payload from setup-1. // `chunk_compressed_size` is the size of the encrypted + - // compressed body — it does NOT include the 4-byte magic. + // compressed body - it does NOT include the 4-byte magic. // The legacy ARC4 path additionally has an 8-byte per-chunk // salt sitting between the magic and the body, which is // OUTSIDE `chunk_compressed_size` (matches innoextract @@ -145,7 +145,7 @@ pub(crate) fn decompress_chunk( // Decrypt in place when applicable (we copy the body to an // owned buffer so we can mutate it). For plaintext chunks the - // borrow stays — we only allocate when encryption forces us to. + // borrow stays - we only allocate when encryption forces us to. let owned_body: Vec; let compressed: &[u8] = if chunk_is_encrypted { let ctx = encryption.ok_or(Error::Encrypted)?; @@ -199,7 +199,7 @@ pub(crate) fn decompress_chunk( // Allocate `original_size`-sized output up front. We don't have // an explicit decompressed size on the wire; the caller knows // the sum-of-files-in-this-chunk (from data entries that share - // it) but doesn't pre-aggregate that — let the decompressor + // it) but doesn't pre-aggregate that - let the decompressor // grow as needed. let mut out = Vec::::new(); @@ -210,7 +210,7 @@ pub(crate) fn decompress_chunk( CompressMethod::Zlib => { // Inno's Zlib chunks use the raw deflate stream (no // zlib wrapper). innoextract uses - // `boost::iostreams::zlib_decompressor` — we use + // `boost::iostreams::zlib_decompressor` - we use // flate2's `ZlibDecoder` first and fall back to // `DeflateDecoder` if a sample later proves the wrapper // is absent. @@ -288,7 +288,7 @@ mod tests { /// /// 1. Take a known plaintext payload. /// 2. Compress it via Inno's LZMA1 wrap (5-byte properties + - /// LZMA1 stream — same as the setup-0 outer block). + /// LZMA1 stream - same as the setup-0 outer block). /// 3. Pick a per-chunk salt; derive RC4 key via the /// `chunk_salt || password_utf16le` rule. /// 4. RC4-encrypt the compressed body. diff --git a/src/extract/file.rs b/src/extract/file.rs index 4e24f23..30aaca5 100644 --- a/src/extract/file.rs +++ b/src/extract/file.rs @@ -1,4 +1,4 @@ -//! [`FileReader`] — streaming `std::io::Read` for one extracted +//! [`FileReader`] - streaming `std::io::Read` for one extracted //! file's content. //! //! Pipeline (per `research/src/stream/file.cpp`): @@ -18,7 +18,7 @@ //! caller's `Read::read` buffer //! ``` //! -//! BCJ runs on the **whole file** before the first `read` call — +//! BCJ runs on the **whole file** before the first `read` call - //! we copy the slice into an owned buffer (so we can mutate it in //! place) and apply the filter once. Streaming `read` then drains //! that buffer, feeding the hasher incrementally and verifying at diff --git a/src/extract/slice.rs b/src/extract/slice.rs index c61bbfe..501a58e 100644 --- a/src/extract/slice.rs +++ b/src/extract/slice.rs @@ -1,9 +1,9 @@ //! Slice reader for `setup-1` payload bytes. //! //! Inno Setup stores the payload chunks either: -//! - **Embedded** — appended after the PE sections inside the +//! - **Embedded** - appended after the PE sections inside the //! installer EXE (when `OffsetTable.offset_setup1 != 0`). -//! - **External** — in sibling files `setup-1.bin`, `setup-2.bin`, +//! - **External** - in sibling files `setup-1.bin`, `setup-2.bin`, //! ... (when `offset_setup1 == 0`). Not currently supported. //! //! The embedded path is implemented here. Each chunk lives at @@ -19,7 +19,7 @@ use crate::error::Error; /// embedded region, surface [`Error::ExternalSlice`]. #[derive(Debug, Clone, Copy)] pub(crate) struct SliceReader<'a> { - /// Embedded `setup-1` bytes — the slice of the input EXE + /// Embedded `setup-1` bytes - the slice of the input EXE /// starting at `offset_setup1` and running to end-of-payload. /// Empty when external slices are in use. setup1: &'a [u8], diff --git a/src/header.rs b/src/header.rs index a55241b..f837fd0 100644 --- a/src/header.rs +++ b/src/header.rs @@ -103,7 +103,7 @@ pub enum HeaderString { ChangesEnvironment, /// `ChangesAssociations` directive. ChangesAssociations, - /// `ArchitecturesAllowed` (added at 6.4 as a `String` — earlier + /// `ArchitecturesAllowed` (added at 6.4 as a `String` - earlier /// versions stored this as a packed enum-set in the fixed /// portion). ArchitecturesAllowed, @@ -139,7 +139,7 @@ pub enum HeaderAnsi { InfoBeforeText, /// Info text shown after installation. InfoAfterText, - /// Compiled PascalScript bytecode blob — opaque from this + /// Compiled PascalScript bytecode blob - opaque from this /// crate's perspective; the IFPS container is not parsed. CompiledCodeText, } @@ -165,7 +165,7 @@ pub struct EntryCounts { pub tasks: u32, /// `NumDirEntries`. pub directories: u32, - /// `NumISSigKeyEntries` — only present in Inno Setup 6.5.0+ + /// `NumISSigKeyEntries` - only present in Inno Setup 6.5.0+ /// (commit `ac2b262d`, ships in tag `is-6_5_0`). pub iss_sig_keys: Option, /// `NumFileEntries`. @@ -189,7 +189,7 @@ pub struct EntryCounts { pub uninstall_run: u32, } -/// `TSetupHeaderOption` — declared options bit. The numeric value of +/// `TSetupHeaderOption` - declared options bit. The numeric value of /// each variant has no on-wire meaning; bit positions are /// version-dependent and resolved by [`SetupHeader::options`]. /// @@ -308,9 +308,9 @@ stable_flag_enum!(HeaderOption, { #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum WizardStyle { - /// `wsClassic` — classic Inno Setup wizard look. + /// `wsClassic` - classic Inno Setup wizard look. Classic, - /// `wsModern` — modern Inno Setup wizard look (Inno Setup 6+). + /// `wsModern` - modern Inno Setup wizard look (Inno Setup 6+). Modern, } @@ -320,11 +320,11 @@ stable_name_enum!(WizardStyle, { Self::Classic => "classic", Self::Modern => "mo #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum ImageAlphaFormat { - /// `afIgnored` — alpha channel ignored. + /// `afIgnored` - alpha channel ignored. Ignored, - /// `afDefined` — alpha defined but not premultiplied. + /// `afDefined` - alpha defined but not premultiplied. Defined, - /// `afPremultiplied` — alpha premultiplied. + /// `afPremultiplied` - alpha premultiplied. Premultiplied, } @@ -338,11 +338,11 @@ stable_name_enum!(ImageAlphaFormat, { #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum UninstallLogMode { - /// `lmAppend` — append to existing log. + /// `lmAppend` - append to existing log. Append, - /// `lmNew` — create new log file. + /// `lmNew` - create new log file. New, - /// `lmOverwrite` — overwrite existing log. + /// `lmOverwrite` - overwrite existing log. Overwrite, } @@ -381,17 +381,17 @@ pub enum YesNoAuto { stable_name_enum!(YesNoAuto, { Self::Yes => "yes", Self::No => "no", Self::Auto => "auto" }); -/// `TSetupPrivilegesRequired` — privilege level required to install. +/// `TSetupPrivilegesRequired` - privilege level required to install. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum PrivilegesRequired { - /// `prNone` — no special privileges. + /// `prNone` - no special privileges. None, - /// `prPowerUser` — power-user (legacy). + /// `prPowerUser` - power-user (legacy). PowerUser, - /// `prAdmin` — administrator privileges. + /// `prAdmin` - administrator privileges. Admin, - /// `prLowest` — lowest available privileges (5.7.0+). + /// `prLowest` - lowest available privileges (5.7.0+). Lowest, } @@ -420,7 +420,7 @@ stable_name_enum!(LanguageDetectionMethod, { Self::None => "none", }); -/// `TSetupCompressMethod` — compression method for `setup-1` chunks +/// `TSetupCompressMethod` - compression method for `setup-1` chunks /// (note: distinct from the `setup-0` block compression discovered at /// runtime via [`crate::Compression`]). #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -460,7 +460,7 @@ stable_name_enum!(CompressMethod, { /// `Arm32` is reachable only via the 6.4+ string-expression form /// (`arm32compatible`); the pre-6.4 packed-set wire format had no /// bit position for it. The [`crate::InnoInstaller::architecture`] -/// accessor unifies both forms — see its docs for the parsing +/// accessor unifies both forms - see its docs for the parsing /// semantics. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] #[non_exhaustive] @@ -506,11 +506,11 @@ pub enum PrivilegesRequiredOverride { /// [`HashSet`]-decoded form and the raw bytes. #[derive(Clone, Debug, Default)] pub struct HeaderTail { - /// `CompiledCodeVersion` — the compiler's `SetupBinVersion` + /// `CompiledCodeVersion` - the compiler's `SetupBinVersion` /// constant, written into the header at 7.0.0.3 and later (issrc /// commit `f9095e91`, 2026-04-15). The low 31 bits are a packed - /// `(major,minor,patch,build)` quad — same layout as - /// [`crate::Version::a`]/`b`/`c`/`d` — and bit 31 is set when + /// `(major,minor,patch,build)` quad - same layout as + /// [`crate::Version::a`]/`b`/`c`/`d` - and bit 31 is set when /// the producing compiler ran in 64-bit mode. /// /// `None` for every release older than 7.0.0.3, including the @@ -525,13 +525,13 @@ pub struct HeaderTail { pub windows_version_range: WindowsVersionRange, // --- pre-6.4 wizard colors --- - /// `BackColor` — wizard background (pre-6.4 only). + /// `BackColor` - wizard background (pre-6.4 only). pub back_color: Option, - /// `BackColor2` — wizard background gradient (pre-6.4 only). + /// `BackColor2` - wizard background gradient (pre-6.4 only). pub back_color2: Option, // --- wizard layout --- - /// `WizardStyle` — `Classic` or `Modern`. Raw byte preserved as + /// `WizardStyle` - `Classic` or `Modern`. Raw byte preserved as /// `wizard_style_raw` for forward-compatibility. pub wizard_style: Option, /// Raw `WizardStyle` byte as read. @@ -554,7 +554,7 @@ pub struct HeaderTail { pub legacy_password_crc32: Option, /// `PasswordSalt`: 8 bytes for 4.2.2..6.3.x. pub legacy_password_salt: Option<[u8; 8]>, - /// `PasswordTest` (PBKDF2-SHA256-XChaCha20 verifier; 6.4.x only — + /// `PasswordTest` (PBKDF2-SHA256-XChaCha20 verifier; 6.4.x only - /// at 6.5.0 this moved into [`crate::EncryptionInfo`]). pub password_test: Option, /// `EncryptionKDFSalt` (16 bytes; 6.4.x only). @@ -629,7 +629,7 @@ pub struct HeaderTail { // --- options bitset --- /// Decoded `Options` set with version-mapped bit positions. - /// Unrecognized bits are dropped silently — the raw bytes remain + /// Unrecognized bits are dropped silently - the raw bytes remain /// available via [`Self::options_raw`]. pub options: HashSet, /// Raw `Options` bitfield bytes. @@ -676,7 +676,7 @@ impl SetupHeader { // bytes). Present for non-Unicode installers from 2.0.6 onward; // removed at 6.3.0 when the ANSI build was dropped (innoextract // `setup/header.cpp:283-287`; research-notes/10-version-evolution.md - // §B.2.b "LeadBytes"). Read past — not yet exposed. + // §B.2.b "LeadBytes"). Read past - not yet exposed. if version.at_least(2, 0, 6) && !version.at_least(6, 3, 0) && !version.is_unicode() { reader.skip(32, "LeadBytes")?; } @@ -786,7 +786,7 @@ impl SetupHeader { /// The on-disk shape evolves field-by-field across the entire 1.x..7.x /// history. `SECompressedBlockRead` (`Shared.SetupEntFunc.pas`) serializes /// `TSetupHeader` by walking every `String` field first, then every -/// `AnsiString` field — so the string table is always contiguous and the +/// `AnsiString` field - so the string table is always contiguous and the /// ansi blobs always trail it. Any `String` field added in a later release /// therefore reads *before* the ansi tail even when its version gate is /// higher; see the `CloseApplicationsFilterExcludes` note below. Three @@ -800,7 +800,7 @@ impl SetupHeader { /// `DefaultUserInfoSerial` for the compiled-code blob); 5.2.5+ /// moved them to the tail, after the whole string table. /// 2. `UninstallerSignature` is a `String` field that exists only in -/// the narrow 5.2.1..5.3.10 window — read past, not exposed. +/// the narrow 5.2.1..5.3.10 window - read past, not exposed. /// 3. The `String` fields added at 6.4.3+ (`CloseApplicationsFilterExcludes`, /// `SevenZipLibraryName`, `UsePrevious*`) come after the architecture /// expressions but still ahead of the ansi tail. innoextract's reference @@ -940,14 +940,14 @@ fn read_header_strings_and_ansi( put_str(reader, HeaderString::UsePreviousTasks, strings)?; put_str(reader, HeaderString::UsePreviousUserInfo, strings)?; } - // 5.2.5+: license/info blobs — the first three `AnsiString` fields, + // 5.2.5+: license/info blobs - the first three `AnsiString` fields, // read after the entire string table (see note above). if version.at_least(5, 2, 5) { put_ansi(reader, HeaderAnsi::LicenseText, ansi)?; put_ansi(reader, HeaderAnsi::InfoBeforeText, ansi)?; put_ansi(reader, HeaderAnsi::InfoAfterText, ansi)?; } - // 5.2.1..5.3.10: UninstallerSignature String — read past. This narrow + // 5.2.1..5.3.10: UninstallerSignature String - read past. This narrow // legacy window predates the 5.2.5 tail move, so in that era the ansi // blobs are interleaved and the signature lands between InfoAfter and // CompiledCode; every version in the window is < 6.4.3, so the string @@ -955,7 +955,7 @@ fn read_header_strings_and_ansi( if version.at_least(5, 2, 1) && !version.at_least(5, 3, 10) { let _ = read_setup_string(reader, version, "UninstallerSignature")?; } - // 5.2.5+: compiled-code blob — the fourth `AnsiString` field. + // 5.2.5+: compiled-code blob - the fourth `AnsiString` field. if version.at_least(5, 2, 5) { put_ansi(reader, HeaderAnsi::CompiledCodeText, ansi)?; } @@ -1083,7 +1083,7 @@ fn parse_tail(reader: &mut Reader<'_>, version: &Version) -> Result, version: &Version) -> Result, version: &Version) -> Result, version: &Version) -> Result usize { fn decode_options(raw: &[u8], version: &Version) -> HashSet { let mut out = HashSet::new(); if version.at_least(5, 5, 0) { - // 5.5.0+ uses one of the curated static slot tables — preferred + // 5.5.0+ uses one of the curated static slot tables - preferred // because the bit positions are stable and easy to audit. for (i, slot) in static_options_bit_table(version).iter().enumerate() { if bit_at(raw, i) @@ -1551,7 +1551,7 @@ fn static_options_bit_table(version: &Version) -> &'static [Option // 5.5.7..6.0.0. Ordering mirrors innoextract `header::load_flags` // for that range. The ANSI build adds `ShowUndisplayableLanguages` // at bit 39; Unicode builds skip that bit and shift the tail down - // by one. We model the ANSI variant — bits 0..38 are identical + // by one. We model the ANSI variant - bits 0..38 are identical // for both, which covers the Password (bit 10) and // EncryptionUsed (bit 37) lookups callers actually care about. OPTIONS_V5_5_7 @@ -1711,8 +1711,8 @@ fn dynamic_options_bit_table(version: &Version) -> Vec> { if version.at_least(5, 3, 9) { t.push(Some(HeaderOption::DisableWelcomePage)); } - // 5.5.0+ slots are unreachable here — the static tables cover that - // range — but include them for symmetry with `options_bit_count`. + // 5.5.0+ slots are unreachable here - the static tables cover that + // range - but include them for symmetry with `options_bit_count`. if version.at_least(5, 5, 0) { t.push(Some(HeaderOption::CloseApplications)); t.push(Some(HeaderOption::RestartApplications)); @@ -1785,7 +1785,7 @@ const OPTIONS_V5_5_0: &[Option] = &[ ]; /// 5.5.7..6.0.0 ANSI flag bit table. Ordering follows innoextract -/// `setup/header.cpp:569-733` (`header::load_flags`) — every +/// `setup/header.cpp:569-733` (`header::load_flags`) - every /// `flagreader.add(...)` call that's gated on a version range /// covering 5.5.7 contributes one bit slot in declaration order. const OPTIONS_V5_5_7: &[Option] = &[ @@ -1892,7 +1892,7 @@ const OPTIONS_V6_1: &[Option] = &[ Some(HeaderOption::WizardResizable), ]; -/// 6.3.0..6.4.0 — same shape as `OPTIONS_V6_1` plus `UninstallLogging` +/// 6.3.0..6.4.0 - same shape as `OPTIONS_V6_1` plus `UninstallLogging` /// at slot 48 (innoextract `header.cpp:729-731`). 49 bits → 7 bytes /// on wire. const OPTIONS_V6_3: &[Option] = &[ @@ -2147,7 +2147,7 @@ mod tests { /// script has a `[Code]` section) landed in the UTF-16 decode and failed /// with "invalid UTF-16LE in CloseApplicationsFilterExcludes". The /// synthetic sample installers have no `[Code]` section, so every tail - /// field is empty and the misordering stayed invisible to them — hence + /// field is empty and the misordering stayed invisible to them - hence /// this byte-level fixture with a deliberately non-empty, odd-length /// compiled-code blob (odd length is never valid UTF-16, so a regression /// re-surfaces as the exact original error rather than a silent shift). @@ -2161,7 +2161,7 @@ mod tests { for _ in 0..32 { push_utf16(&mut buf, ""); } - // Field 33: CloseApplicationsFilterExcludes — a real string. + // Field 33: CloseApplicationsFilterExcludes - a real string. push_utf16(&mut buf, "notepad.exe,calc.exe"); // AnsiString tail: License / InfoBefore / InfoAfter / CompiledCode. push_ansi(&mut buf, b"LICENSE TEXT"); diff --git a/src/installer.rs b/src/installer.rs index 0501da6..63f9ac3 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -190,7 +190,7 @@ pub struct InnoInstaller<'a> { password_used: Option, } -/// Eagerly-parsed record sections — every record type stored in +/// Eagerly-parsed record sections - every record type stored in /// the two block streams of `setup-0`. #[derive(Clone, Debug, Default)] struct ParsedRecords { @@ -216,7 +216,7 @@ struct ParsedRecords { impl<'a> InnoInstaller<'a> { /// Parses an Inno Setup installer from a byte slice. **Does - /// not attempt to decrypt** — encrypted chunks remain + /// not attempt to decrypt** - encrypted chunks remain /// observable as `ChunkEncrypted` data-entry flags, and /// [`Self::extract`] returns [`Error::Encrypted`] for them. /// Use [`Self::from_bytes_with_passwords`] to additionally @@ -239,9 +239,9 @@ impl<'a> InnoInstaller<'a> { /// /// # Errors /// - /// - [`Error::PasswordRequired`] — installer is encrypted but + /// - [`Error::PasswordRequired`] - installer is encrypted but /// `passwords` is empty. - /// - [`Error::WrongPassword`] — no candidate matched. + /// - [`Error::WrongPassword`] - no candidate matched. /// - All standard parse errors per [`Self::from_bytes`]. pub fn from_bytes_with_passwords(data: &'a [u8], passwords: &[&str]) -> Result { Self::parse(data, Some(passwords)) @@ -290,7 +290,7 @@ impl<'a> InnoInstaller<'a> { // streams themselves are XChaCha20-encrypted and need the // derived key to decrypt. For `euFiles` the trial would // run the same way; for the no-encryption case it's a - // no-op. The legacy (pre-6.4) trial sits below — that path + // no-op. The legacy (pre-6.4) trial sits below - that path // needs `HeaderTail.legacy_password_*` which only exists // post-decompression. let (mut encryption_key, mut password_used): (Option<[u8; 32]>, Option) = @@ -340,7 +340,7 @@ impl<'a> InnoInstaller<'a> { } else { // Encrypted but no key supplied (caller went // through `from_bytes` or `from_bytes_with_passwords` - // for an encrypted installer — the plain + // for an encrypted installer - the plain // `from_bytes` path lands here for euFull). ( Box::<[u8]>::default(), @@ -519,8 +519,8 @@ impl<'a> InnoInstaller<'a> { } /// Returns the encryption metadata for 6.4.0+ installers, or - /// `None` if not encrypted (or if the version is older than 6.4 - /// — pre-6.4 encryption is per-chunk, surfaced through the + /// `None` if not encrypted (or if the version is older than 6.4 - + /// pre-6.4 encryption is per-chunk, surfaced through the /// `DataEntry::ChunkEncrypted` flag on each affected chunk). #[must_use] pub fn encryption(&self) -> Option<&EncryptionInfo> { @@ -547,7 +547,7 @@ impl<'a> InnoInstaller<'a> { /// **best-effort** for known architecture atoms /// (`x86`, `x86os`, `x86compatible`, `x64`, `x64os`, /// `x64compatible`, `arm32compatible`, `arm64`, `ia64`). - /// Boolean operators (`and`, `or`, `not`) are NOT evaluated — + /// Boolean operators (`and`, `or`, `not`) are NOT evaluated - /// any atom that appears in the expression produces its /// architecture in the result, so `not x64` reports `Amd64` /// the same as bare `x64`. Callers that need a faithful @@ -583,7 +583,7 @@ impl<'a> InnoInstaller<'a> { /// Modern Inno builds always carry the `LicenseText` AnsiString /// slot, but populate it with an empty string when no license /// is configured. This accessor folds that empty case into - /// `None` — `Some(bytes)` always has `bytes.len() > 0`. + /// `None` - `Some(bytes)` always has `bytes.len() > 0`. /// /// The bytes are codepage-encoded per the installer's language /// table; callers that need a `String` should run them through @@ -631,7 +631,7 @@ impl<'a> InnoInstaller<'a> { /// Returns the compiled `[Code]` PascalScript blob, or `None` /// if the installer does not ship a `[Code]` section. /// - /// The blob is an IFPS container — `IFPS`-magic followed by a + /// The blob is an IFPS container - `IFPS`-magic followed by a /// header, name table, type table, globals, procedures, and /// bytecode. This accessor surfaces the raw bytes; the parsed /// container view lives at [`Self::compiledcode`]. Empty @@ -650,7 +650,7 @@ impl<'a> InnoInstaller<'a> { /// blob is present. /// /// The full - /// [`pascalscript::Container`] surface is exposed — + /// [`pascalscript::Container`] surface is exposed - /// header, types, procs (script-defined and imported /// externals), vars. The container's API is Inno-agnostic /// (designed for an eventual standalone-crate split); see @@ -659,7 +659,7 @@ impl<'a> InnoInstaller<'a> { /// /// # Errors /// - /// `Some(Err(_))` when the blob is present but malformed — + /// `Some(Err(_))` when the blob is present but malformed - /// bad magic, unsupported `PSBuildNo`, truncated table, or /// out-of-range type / bytecode reference. The error wraps a /// [`pascalscript::Error`] inside @@ -678,7 +678,7 @@ impl<'a> InnoInstaller<'a> { /// Returns a one-line description for the security-relevant /// subset of registered functions (registry mutations, command /// execution, file operations, network downloads, privilege - /// checks). Returns `None` for names outside that subset — + /// checks). Returns `None` for names outside that subset - /// see [`crate::analysis::compiledcode::INNO_API`] for the /// curated list. #[must_use] @@ -784,7 +784,7 @@ impl<'a> InnoInstaller<'a> { &self.records.uninstall_run } - /// Parsed file-location records — bookkeeping for the chunks of + /// Parsed file-location records - bookkeeping for the chunks of /// `setup-1` payload (one per file-content slot, including /// embedded files like the uninstaller). Lives in setup-0's /// second decompressed block. @@ -821,7 +821,7 @@ impl<'a> InnoInstaller<'a> { /// or [`DeleteKey`](crate::analysis::RegistryOpKind::DeleteKey) /// based on the entry's flag bitset. /// - /// Uninstall-time effects are not classified here — inspect + /// Uninstall-time effects are not classified here - inspect /// the underlying entry's `flags` for `UninsDeleteValue`, /// `UninsDeleteEntireKey`, etc. #[must_use] @@ -840,7 +840,7 @@ impl<'a> InnoInstaller<'a> { } /// Returns `true` when the installer carries any kind of - /// encryption — modern (6.4+ XChaCha20) or legacy (pre-6.4 + /// encryption - modern (6.4+ XChaCha20) or legacy (pre-6.4 /// ARC4-via-`shPassword` flag). #[must_use] pub fn is_encrypted(&self) -> bool { @@ -922,7 +922,7 @@ impl<'a> InnoInstaller<'a> { /// `SetupExeMode` slot to the uninstaller magic. /// /// Inno's uninstaller stub isn't a separately-packaged file in - /// the chunk stream — at install time `Setup.exe` copies its + /// the chunk stream - at install time `Setup.exe` copies its /// own bytes (`NewParamStr(0)` in /// `Setup.Install.pas:1629-1631`) and overwrites the four-byte /// mode marker at offset `0x30` with `0x6E556E49` @@ -930,8 +930,8 @@ impl<'a> InnoInstaller<'a> { /// (`Setup.Install.HelperFunc.pas:469-473`). On launch the /// same EXE reads that marker (`Setup.Start.pas:148`) and /// dispatches into uninstaller mode rather than installer - /// mode. Both constants — `SetupExeModeOffset = $30` and - /// `SetupExeModeUninstaller = $6E556E49` — have been stable + /// mode. Both constants - `SetupExeModeOffset = $30` and + /// `SetupExeModeUninstaller = $6E556E49` - have been stable /// across the version range we cover (verified at `is-5_5_5` /// through `is-7_0_0_2`, `Shared.Struct.pas`). /// @@ -984,7 +984,7 @@ impl<'a> InnoInstaller<'a> { /// Iteration order matches [`Self::files`] declaration order. /// When solid-LZMA mode places multiple files in the same /// chunk, the per-chunk `OnceLock` cache makes the second and - /// subsequent extractions from that chunk free — i.e. running + /// subsequent extractions from that chunk free - i.e. running /// the iterator end-to-end touches each chunk's decompression /// path exactly once even though there are many file /// extractions. @@ -1003,7 +1003,7 @@ impl<'a> InnoInstaller<'a> { /// for plaintext installers / installers that haven't been /// unlocked. fn encryption_context(&self) -> Option> { - // Modern (6.4+) — derived 32-byte key + base nonce. + // Modern (6.4+) - derived 32-byte key + base nonce. if let (Some(key), Some(info)) = (self.encryption_key.as_ref(), self.encryption.as_ref()) { return Some(crate::extract::chunk::EncryptionContext::Modern { key, @@ -1011,7 +1011,7 @@ impl<'a> InnoInstaller<'a> { mode: info.mode, }); } - // Legacy (pre-6.4) — keep the password verbatim (per-chunk + // Legacy (pre-6.4) - keep the password verbatim (per-chunk // RC4 keys are derived from password + each chunk's salt). // 5.3.9..6.4 uses SHA-1 keying; pre-5.3.9 uses MD5. if let Some(password) = self.legacy_password.as_deref() { @@ -1066,7 +1066,7 @@ impl<'a> InnoInstaller<'a> { compression, self.encryption_context().as_ref(), )?; - // If another thread raced ahead, `set` will fail — fall + // If another thread raced ahead, `set` will fail - fall // back to whatever value got installed. let bytes = match slot.set(bytes) { Ok(()) => slot.get().ok_or(Error::Truncated { @@ -1183,7 +1183,7 @@ fn decompress_blocks_eufull( fn parse_architecture_expression(s: &str) -> HashSet { let lower = s.to_ascii_lowercase(); let mut set = HashSet::new(); - // Order doesn't matter — duplicate hits collapse in the set. + // Order doesn't matter - duplicate hits collapse in the set. let atoms: &[(&str, Architecture)] = &[ ("x86compatible", Architecture::X86), ("x86os", Architecture::X86), @@ -1213,11 +1213,11 @@ fn legacy_stored_hash( version: &Version, ) -> Option { // The `shPassword` Options bit is the discriminator for - // pre-6.4 encryption — without it, the legacy hash fields are + // pre-6.4 encryption - without it, the legacy hash fields are // either absent or zero. Since options decoding is per-version // and exposed via header.has_option, we check via the parsed // tail's options bytes directly to avoid a dependency cycle. - // Easier: inspect the field availability — every encrypted + // Easier: inspect the field availability - every encrypted // pre-6.4 installer populates the relevant hash field per // its version family. match legacy_hash_family(version) { @@ -1256,7 +1256,7 @@ fn try_passwords_legacy( Err(Error::WrongPassword) } -/// `(chunk_cache, file_loc_to_chunk)` — return type of +/// `(chunk_cache, file_loc_to_chunk)` - return type of /// [`build_chunk_index`]. Aliased here for clippy. type ChunkIndex = (Box<[OnceLock>]>, Box<[u32]>); diff --git a/src/overlay/offsettable.rs b/src/overlay/offsettable.rs index ac733b5..4909313 100644 --- a/src/overlay/offsettable.rs +++ b/src/overlay/offsettable.rs @@ -31,21 +31,21 @@ use crate::{ #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum SetupLdrFamily { - /// `rDlPtS02\x87eVx` — Inno Setup ≥ 1.2.10. + /// `rDlPtS02\x87eVx` - Inno Setup ≥ 1.2.10. V1_2_10, - /// `rDlPtS04\x87eVx` — Inno Setup ≥ 4.0.0. + /// `rDlPtS04\x87eVx` - Inno Setup ≥ 4.0.0. V4_0_0, - /// `rDlPtS05\x87eVx` — Inno Setup ≥ 4.0.3. + /// `rDlPtS05\x87eVx` - Inno Setup ≥ 4.0.3. V4_0_3, - /// `rDlPtS06\x87eVx` — Inno Setup ≥ 4.0.10. + /// `rDlPtS06\x87eVx` - Inno Setup ≥ 4.0.10. V4_0_10, - /// `rDlPtS07\x87eVx` — Inno Setup ≥ 4.1.6. + /// `rDlPtS07\x87eVx` - Inno Setup ≥ 4.1.6. V4_1_6, - /// `rDlPtS\xCD\xE6\xD7{\x0B*` — Inno Setup ≥ 5.1.5; this magic is + /// `rDlPtS\xCD\xE6\xD7{\x0B*` - Inno Setup ≥ 5.1.5; this magic is /// also reused by 6.x and 7.x. The record-format generation is /// then determined by the `Version` field (v1 vs v2). V5_1_5, - /// `nS5W7dT\x83\xAA\x1B\x0Fj` — alternative 5.1.5+ magic seen on + /// `nS5W7dT\x83\xAA\x1B\x0Fj` - alternative 5.1.5+ magic seen on /// some modified variants. V5_1_5Alt, } @@ -225,7 +225,7 @@ impl OffsetTable { _ => { // Some pre-bumped 5.1.5 builds emit the v1 layout // without an explicit version dword. We fall back to - // legacy parsing — the magic is enough to identify the + // legacy parsing - the magic is enough to identify the // file. Rewind 4 bytes so the legacy parser sees the // u32 we just consumed. let pos_after = reader.pos(); diff --git a/src/overlay/pe.rs b/src/overlay/pe.rs index 365cfdd..6017697 100644 --- a/src/overlay/pe.rs +++ b/src/overlay/pe.rs @@ -1,6 +1,6 @@ //! Locate the `SetupLdrOffsetTable` bytes inside the PE container. //! -//! # Strategy 1 — legacy (pre-5.1.5) +//! # Strategy 1 - legacy (pre-5.1.5) //! //! Read three dwords at file offset `0x30`: `'Inno'` magic //! (`0x6f6e6e49`), then a pointer to the offset-table record, then @@ -8,11 +8,11 @@ //! the magic matches and pointer + ~pointer round-trip, we follow //! the pointer. This must be tried **before** the signature scan //! because the same `rDlPtS` magic appears as a literal string -//! constant inside the loader's code section in pre-5.1.5 builds — +//! constant inside the loader's code section in pre-5.1.5 builds - //! a naïve scan would lock onto that false match instead of the //! real offset-table copy that the `0x30` pointer leads to. //! -//! # Strategy 2 — modern (5.1.5+) +//! # Strategy 2 - modern (5.1.5+) //! //! Scan the input for the 12-byte SetupLdr magic. The 5.1.5+ format //! moved the offset table into a PE resource (id 11111) and the @@ -130,7 +130,7 @@ fn try_signature_scan(input: &[u8]) -> Option { }) } -/// Naïve memmem — fine for our inputs (typical Inno installers are +/// Naïve memmem - fine for our inputs (typical Inno installers are /// 1–50 MB, signature is 12 bytes, only one match expected). Avoids a /// dependency on the `memchr` crate. fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option { diff --git a/src/records/component.rs b/src/records/component.rs index ff9522c..40c0784 100644 --- a/src/records/component.rs +++ b/src/records/component.rs @@ -1,4 +1,4 @@ -//! `TSetupComponentEntry` — installable component definition. +//! `TSetupComponentEntry` - installable component definition. //! //! Pascal layout (`is-6_4_1:Projects/Src/Shared.Struct.pas`): //! @@ -63,7 +63,7 @@ pub struct ComponentEntry { pub languages: String, /// `Check:` directive. 4.0.0+ (or ISX 1.3.24+). pub check: String, - /// `ExtraDiskSpaceRequired` — disk usage attributable to this + /// `ExtraDiskSpaceRequired` - disk usage attributable to this /// component beyond the file payload sum. pub extra_disk_space_required: i64, /// `Level:` directive (component priority). 4.0.0+ (or ISX 3.0.3+). @@ -76,7 +76,7 @@ pub struct ComponentEntry { pub flags: HashSet, /// Raw `Options` byte. pub options_raw: u8, - /// `Size` — total file size when this component is selected. + /// `Size` - total file size when this component is selected. /// `u64` from 4.0.0+; `u32`-promoted from earlier versions. pub size: u64, } diff --git a/src/records/dataentry.rs b/src/records/dataentry.rs index 5da0172..5456c6a 100644 --- a/src/records/dataentry.rs +++ b/src/records/dataentry.rs @@ -1,4 +1,4 @@ -//! `TSetupFileLocationEntry` (innoextract `dataentry`) — the +//! `TSetupFileLocationEntry` (innoextract `dataentry`) - the //! contents of the **second** decompressed block (a.k.a. block 2). //! Each entry is the bookkeeping for one chunk of `setup-1` payload: //! offsets, size, checksum, timestamp, version info, encryption / @@ -32,15 +32,15 @@ use crate::{error::Error, util::read::Reader, version::Version}; #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum DataChecksum { - /// `Adler32` — pre-4.0.1 only. + /// `Adler32` - pre-4.0.1 only. Adler32(u32), - /// `CRC32` — 4.0.1..4.2.0. + /// `CRC32` - 4.0.1..4.2.0. Crc32(u32), - /// `MD5` — 4.2.0..5.3.9. + /// `MD5` - 4.2.0..5.3.9. Md5([u8; 16]), - /// `SHA1` — 5.3.9..6.4.0. + /// `SHA1` - 5.3.9..6.4.0. Sha1([u8; 20]), - /// `SHA256` — 6.4.0+. + /// `SHA256` - 6.4.0+. Sha256([u8; 32]), } @@ -87,7 +87,7 @@ stable_flag_enum!(DataFlag, { SignOnce => "sign_once", }); -/// `TSetupFileLocationSignMode` — 6.3.0+ replacement for the +/// `TSetupFileLocationSignMode` - 6.3.0+ replacement for the /// per-entry `Sign` / `SignOnce` flags. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] @@ -112,20 +112,20 @@ stable_name_enum!(SignMode, { /// Parsed `TSetupFileLocationEntry`. #[derive(Clone, Debug)] pub struct DataEntry { - /// `FirstSlice` — index of the first slice (`setup-1.bin`, + /// `FirstSlice` - index of the first slice (`setup-1.bin`, /// `setup-2.bin`, …) this chunk belongs to. pub first_slice: u32, - /// `LastSlice` — index of the last slice this chunk belongs to. + /// `LastSlice` - index of the last slice this chunk belongs to. pub last_slice: u32, - /// `StartOffset` — byte offset within `FirstSlice`. + /// `StartOffset` - byte offset within `FirstSlice`. pub start_offset: u32, - /// `ChunkSubOffset` — byte offset within the (possibly + /// `ChunkSubOffset` - byte offset within the (possibly /// compressed/encrypted) chunk where this file's bytes begin. /// `0` for pre-4.0.1 versions. pub chunk_sub_offset: u64, - /// `OriginalSize` — uncompressed size of this file. + /// `OriginalSize` - uncompressed size of this file. pub original_size: u64, - /// `ChunkCompressedSize` — compressed-on-disk size of the chunk + /// `ChunkCompressedSize` - compressed-on-disk size of the chunk /// (note: a chunk may contain *multiple* file payloads). pub chunk_compressed_size: u64, /// File-content checksum. @@ -140,7 +140,7 @@ pub struct DataEntry { pub flags: HashSet, /// Raw `Flags` bytes. pub flags_raw: Vec, - /// `SignMode` — 6.3.0+. Synthesized from `Sign` / `SignOnce` + /// `SignMode` - 6.3.0+. Synthesized from `Sign` / `SignOnce` /// flags on older versions. pub sign_mode: SignMode, /// Raw sign-mode byte (only meaningful 6.3.0+). @@ -171,7 +171,7 @@ impl DataEntry { } let start_offset = if version.at_least(6, 5, 2) { - // Promoted to Int64 at 6.5.2 (issrc commit `b5881a9b` — + // Promoted to Int64 at 6.5.2 (issrc commit `b5881a9b` - // "Increase max Setup size without disk spanning from // almost 2 GB to almost 4 GB"; widens StartOffset and // ChunkSuboffset to Int64). Wire is signed but diff --git a/src/records/delete.rs b/src/records/delete.rs index 67b7438..5fab1f9 100644 --- a/src/records/delete.rs +++ b/src/records/delete.rs @@ -1,4 +1,4 @@ -//! `TSetupDeleteEntry` — `[InstallDelete]` / `[UninstallDelete]` +//! `TSetupDeleteEntry` - `[InstallDelete]` / `[UninstallDelete]` //! entry. The same struct is reused for both directives. //! //! Pascal layout (`is-6_4_1:Projects/Src/Shared.Struct.pas`): @@ -21,15 +21,15 @@ use crate::{ version::Version, }; -/// `TSetupDeleteType` — what the entry deletes. +/// `TSetupDeleteType` - what the entry deletes. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum DeleteTargetType { - /// `dtFiles` — file glob (no recursion). + /// `dtFiles` - file glob (no recursion). Files, - /// `dtFilesAndSubdirs` — file glob + recurse subdirectories. + /// `dtFilesAndSubdirs` - file glob + recurse subdirectories. FilesAndSubdirs, - /// `dtDirIfEmpty` — directory, only if empty. + /// `dtDirIfEmpty` - directory, only if empty. DirIfEmpty, } @@ -42,7 +42,7 @@ stable_name_enum!(DeleteTargetType, { /// Parsed `TSetupDeleteEntry`. #[derive(Clone, Debug)] pub struct DeleteEntry { - /// `Name:` directive — file path / glob to delete (with Inno + /// `Name:` directive - file path / glob to delete (with Inno /// constants like `{app}` unresolved). pub name: String, /// Shared conditions + Windows version range. diff --git a/src/records/directory.rs b/src/records/directory.rs index c1600eb..5b6a33a 100644 --- a/src/records/directory.rs +++ b/src/records/directory.rs @@ -1,4 +1,4 @@ -//! `TSetupDirEntry` — `[Dirs]` directive entry. +//! `TSetupDirEntry` - `[Dirs]` directive entry. //! //! Pascal layout (`is-6_4_1:Projects/Src/Shared.Struct.pas`): //! @@ -54,7 +54,7 @@ stable_flag_enum!(DirectoryFlag, { /// Parsed `TSetupDirEntry`. #[derive(Clone, Debug)] pub struct DirectoryEntry { - /// `Name:` directive — directory path with Inno constants. + /// `Name:` directive - directory path with Inno constants. pub name: String, /// `[ItemConditions]`. pub conditions: ItemConditions, @@ -63,7 +63,7 @@ pub struct DirectoryEntry { /// before the `[Permissions]` table was introduced). Empty /// outside that range. pub legacy_permissions: Vec, - /// `Attribs:` directive — Win32 file-attribute flags. + /// `Attribs:` directive - Win32 file-attribute flags. pub attributes: u32, /// `[WindowsVersionRange]`. pub winver: WindowsVersionRange, diff --git a/src/records/file.rs b/src/records/file.rs index 9124892..c94f74a 100644 --- a/src/records/file.rs +++ b/src/records/file.rs @@ -1,4 +1,4 @@ -//! `TSetupFileEntry` — `[Files]` directive entry. +//! `TSetupFileEntry` - `[Files]` directive entry. //! //! Pascal layout (`is-6_4_1:Projects/Src/Shared.Struct.pas`): //! @@ -39,11 +39,11 @@ use crate::{ #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum FileEntryType { - /// `ftUserFile` — regular file from `[Files]`. + /// `ftUserFile` - regular file from `[Files]`. UserFile, - /// `ftUninstExe` — the uninstaller binary. + /// `ftUninstExe` - the uninstaller binary. UninstExe, - /// `ftRegSvrExe` — the embedded RegSvr (pre-5.0 win32 only). + /// `ftRegSvrExe` - the embedded RegSvr (pre-5.0 win32 only). RegSvrExe, } @@ -92,9 +92,9 @@ pub enum FileFlag { SetNtfsCompression, UnsetNtfsCompression, GacInstall, - /// 6.5.0+ — file content fetched at install time. + /// 6.5.0+ - file content fetched at install time. Download, - /// 6.5.0+ — payload is a 7-Zip archive to extract. + /// 6.5.0+ - payload is a 7-Zip archive to extract. ExtractArchive, } @@ -139,18 +139,18 @@ stable_flag_enum!(FileFlag, { /// Parsed `TSetupFileEntry`. #[derive(Clone, Debug)] pub struct FileEntry { - /// `Source:` directive — path on the build machine where the + /// `Source:` directive - path on the build machine where the /// file came from (with Inno constants like `{tmp}`). pub source: String, - /// `DestName:` directive — installation path. + /// `DestName:` directive - installation path. pub destination: String, - /// `FontInstall:` directive — empty unless this is a font. + /// `FontInstall:` directive - empty unless this is a font. pub install_font_name: String, /// `StrongAssemblyName:` directive (5.2.5+). pub strong_assembly_name: String, /// Shared conditions + Windows version range. pub item: ItemBase, - /// `Excludes:` directive (6.5.0+) — empty on older versions. + /// `Excludes:` directive (6.5.0+) - empty on older versions. pub excludes: String, /// `DownloadISSigSource:` directive (6.5.0+). pub download_iss_sig_source: String, @@ -191,11 +191,11 @@ pub struct FileEntry { pub file_type_raw: u8, } -/// `TSetupFileVerification` — added at Inno Setup 6.5.0. Holds the +/// `TSetupFileVerification` - added at Inno Setup 6.5.0. Holds the /// signing-key allowlist and the file content hash. #[derive(Clone, Debug, Default)] pub struct FileVerification { - /// `ISSigAllowedKeys` — newline-separated list of allowed + /// `ISSigAllowedKeys` - newline-separated list of allowed /// signature keys (raw bytes; 6.5.0+). pub iss_sig_allowed_keys: Vec, /// SHA-256 of the file content. All zeros when verification @@ -211,11 +211,11 @@ pub struct FileVerification { #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum FileVerificationKind { - /// `fvNone` — no verification. + /// `fvNone` - no verification. None, - /// `fvHash` — SHA-256 hash check. + /// `fvHash` - SHA-256 hash check. Hash, - /// `fvISSig` — Inno Setup signature check. + /// `fvISSig` - Inno Setup signature check. IsSig, } diff --git a/src/records/icon.rs b/src/records/icon.rs index b764f3b..7803e50 100644 --- a/src/records/icon.rs +++ b/src/records/icon.rs @@ -1,4 +1,4 @@ -//! `TSetupIconEntry` — `[Icons]` directive entry (Start Menu / +//! `TSetupIconEntry` - `[Icons]` directive entry (Start Menu / //! desktop shortcut). //! //! Pascal layout (`is-6_4_1:Projects/Src/Shared.Struct.pas`): @@ -33,7 +33,7 @@ use crate::{ version::Version, }; -/// `TSetupCloseOnExit` — wizard close-on-exit policy for the +/// `TSetupCloseOnExit` - wizard close-on-exit policy for the /// shortcut's launched process. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] @@ -58,7 +58,7 @@ stable_name_enum!(CloseOnExit, { #[allow(missing_docs)] pub enum IconFlag { NeverUninstall, - /// Pre-1.3.26 only — replaced by `ShowCmd` later. + /// Pre-1.3.26 only - replaced by `ShowCmd` later. RunMinimized, CreateOnlyIfFileExists, UseAppPaths, @@ -83,7 +83,7 @@ stable_flag_enum!(IconFlag, { /// Parsed `TSetupIconEntry`. #[derive(Clone, Debug)] pub struct IconEntry { - /// `Name:` directive — full shortcut path including `.lnk`. + /// `Name:` directive - full shortcut path including `.lnk`. pub name: String, /// `Filename:` directive. pub filename: String, diff --git a/src/records/ini.rs b/src/records/ini.rs index c5ca74b..e72f19a 100644 --- a/src/records/ini.rs +++ b/src/records/ini.rs @@ -1,4 +1,4 @@ -//! `TSetupIniEntry` — `[INI]` directive entry. +//! `TSetupIniEntry` - `[INI]` directive entry. //! //! Pascal layout (`is-6_4_1:Projects/Src/Shared.Struct.pas`): //! @@ -12,7 +12,7 @@ //! ``` //! //! Reader reference: `research/src/setup/ini.cpp`. innoextract -//! defaults `inifile` to `{windows}/WIN.INI` when empty — we surface +//! defaults `inifile` to `{windows}/WIN.INI` when empty - we surface //! the field as-stored and let the caller apply that fallback. use std::collections::HashSet; @@ -47,13 +47,13 @@ stable_flag_enum!(IniFlag, { /// Parsed `TSetupIniEntry`. #[derive(Clone, Debug)] pub struct IniEntry { - /// `Filename:` directive — path of the `.ini` file. + /// `Filename:` directive - path of the `.ini` file. pub inifile: String, /// `Section:` directive. pub section: String, /// `Key:` directive. pub key: String, - /// `String:` directive — value to write (when [`IniFlag::HasValue`] + /// `String:` directive - value to write (when [`IniFlag::HasValue`] /// is set). pub value: String, /// Shared conditions + Windows version range. diff --git a/src/records/isssigkey.rs b/src/records/isssigkey.rs index e405982..fe717c2 100644 --- a/src/records/isssigkey.rs +++ b/src/records/isssigkey.rs @@ -1,4 +1,4 @@ -//! `TSetupISSigKeyEntry` — Inno Setup signing-key entry (6.5.0+). +//! `TSetupISSigKeyEntry` - Inno Setup signing-key entry (6.5.0+). //! //! Pascal layout (`is-6_5_0:Projects/Src/Shared.Struct.pas` and //! identical at HEAD): @@ -26,7 +26,7 @@ pub struct ISSigKeyEntry { pub public_x: String, /// Y-coordinate of the public key. pub public_y: String, - /// Runtime identifier — installer-side correlation token. + /// Runtime identifier - installer-side correlation token. pub runtime_id: String, } diff --git a/src/records/item.rs b/src/records/item.rs index ed3df5d..6b968ad 100644 --- a/src/records/item.rs +++ b/src/records/item.rs @@ -15,8 +15,8 @@ //! //! Most records (file, ini, run, icon, registry, delete) read the //! conditions and the version range back-to-back. `directory_entry` -//! is the known exception — it interposes `permissions` and -//! `attributes` between the two — so the readers are split: callers +//! is the known exception - it interposes `permissions` and +//! `attributes` between the two - so the readers are split: callers //! that need the back-to-back form use `ItemBase::read`, callers //! that interpose use `ItemConditions::read` and //! `WindowsVersionRange::read` separately. @@ -36,21 +36,21 @@ use crate::{ /// Conditions section read by every record-with-conditions. /// /// All six fields are length-prefixed strings (UTF-16LE on Unicode -/// builds, ANSI on legacy ones — codepage selection follows +/// builds, ANSI on legacy ones - codepage selection follows /// `util::encoding::is_unicode_for_version`). Fields that /// don't yet exist in the parsed installer's version remain empty. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct ItemConditions { - /// `Components:` directive — semicolon-separated list of + /// `Components:` directive - semicolon-separated list of /// component names this entry is gated on. Inno Setup 2.0+ /// (or ISX 1.3.8+). pub components: String, - /// `Tasks:` directive — semicolon-separated list of task names + /// `Tasks:` directive - semicolon-separated list of task names /// this entry is gated on. Inno Setup 2.0+ (or ISX 1.3.17+). pub tasks: String, /// `Languages:` directive. Inno Setup 4.0.1+. pub languages: String, - /// `Check:` directive — Pascal expression / function name + /// `Check:` directive - Pascal expression / function name /// gating this entry. Inno Setup 4.0.0+ (or ISX 1.3.24+). pub check: String, /// `AfterInstall:` directive. Inno Setup 4.1.0+. Note: declared @@ -102,7 +102,7 @@ impl ItemConditions { /// /// Convenience for record types whose Pascal layout reads /// `load_condition_data` and `load_version_data` consecutively -/// (file / ini / run / icon / registry / delete entries — the +/// (file / ini / run / icon / registry / delete entries - the /// majority). Record types that interpose fields between the two /// (currently only `directory_entry`) must read the parts /// separately. @@ -110,7 +110,7 @@ impl ItemConditions { pub struct ItemBase { /// Conditions strings. pub conditions: ItemConditions, - /// Windows version range — `MinVersion` + `OnlyBelowVersion`. + /// Windows version range - `MinVersion` + `OnlyBelowVersion`. pub winver: WindowsVersionRange, } diff --git a/src/records/language.rs b/src/records/language.rs index 9064dfd..9a73164 100644 --- a/src/records/language.rs +++ b/src/records/language.rs @@ -1,4 +1,4 @@ -//! `TSetupLanguageEntry` — `[Languages]` section entry. +//! `TSetupLanguageEntry` - `[Languages]` section entry. //! //! Pascal layout (`is-6_4_1:Projects/Src/Shared.Struct.pas`): //! @@ -20,7 +20,7 @@ //! ``` //! //! Reader reference: `research/src/setup/language.cpp`. Note that -//! "binary_string" in innoextract is a length-prefixed raw blob — +//! "binary_string" in innoextract is a length-prefixed raw blob - //! always read as the wire bytes regardless of unicode build. We use //! `read_ansi_bytes` here. //! @@ -47,7 +47,7 @@ use crate::{ /// handling. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum LanguageCodepage { - /// `cp_utf16le` — modern unicode builds. + /// `cp_utf16le` - modern unicode builds. Utf16Le, /// Numeric Windows codepage (e.g. 1252, 1251, 932). The variant /// covers every legacy ANSI installer; common values are @@ -138,14 +138,14 @@ impl core::fmt::Display for LanguageCodepage { /// Parsed `TSetupLanguageEntry`. /// -/// Strings are returned as raw bytes (`Vec`) — codepage decoding +/// Strings are returned as raw bytes (`Vec`) - codepage decoding /// to `String` happens above this layer once a target encoding is /// chosen. The associated codepage is exposed via /// [`Self::codepage`]. For 6.x Unicode installers the bytes are /// UTF-16LE. #[derive(Clone, Debug)] pub struct LanguageEntry { - /// `Name:` directive — language identifier (e.g. `"english"`). + /// `Name:` directive - language identifier (e.g. `"english"`). /// 4.0.0+; absent on older versions. pub name: Vec, /// Human-readable language name (e.g. `"English"`). @@ -236,7 +236,7 @@ fn read_v6_6(reader: &mut Reader<'_>, _version: &Version) -> Result, version: &Version) -> Result, version: &Version) -> Result, - /// `Value:` directive — localized message body. Encoded in the + /// `Value:` directive - localized message body. Encoded in the /// codepage of the entry's language; see [`Self::language`]. pub value: Vec, /// Language index into the parsed [`crate::records::language`] diff --git a/src/records/mod.rs b/src/records/mod.rs index 02eae0a..3ffb511 100644 --- a/src/records/mod.rs +++ b/src/records/mod.rs @@ -10,7 +10,7 @@ //! fixed numeric tail of `TSetupHeader` and the per-record //! `ItemBase`. [`item`] is the conditions section + version-range //! bundle every record-with-conditions reads. The remaining modules -//! map one-to-one to Pascal record types — lightweight ones +//! map one-to-one to Pascal record types - lightweight ones //! ([`type_`], [`component`], [`task`], [`language`], [`message`], //! [`permission`]) and heavier ones ([`directory`], [`mod@file`], //! [`icon`], [`ini`], [`registry`], [`run`], [`delete`], diff --git a/src/records/permission.rs b/src/records/permission.rs index 8ce6dbc..b87b91c 100644 --- a/src/records/permission.rs +++ b/src/records/permission.rs @@ -1,4 +1,4 @@ -//! `TSetupPermissionEntry` — opaque permissions blob. +//! `TSetupPermissionEntry` - opaque permissions blob. //! //! Pascal layout (`is-6_4_1:Projects/Src/Shared.Struct.pas`): //! diff --git a/src/records/registry.rs b/src/records/registry.rs index cf5a4d5..0ce4c75 100644 --- a/src/records/registry.rs +++ b/src/records/registry.rs @@ -1,4 +1,4 @@ -//! `TSetupRegistryEntry` — `[Registry]` directive entry. +//! `TSetupRegistryEntry` - `[Registry]` directive entry. //! //! Pascal layout (`is-6_4_1:Projects/Src/Shared.Struct.pas`): //! diff --git a/src/records/run.rs b/src/records/run.rs index 790278a..7fe1e34 100644 --- a/src/records/run.rs +++ b/src/records/run.rs @@ -1,4 +1,4 @@ -//! `TSetupRunEntry` — `[Run]` / `[UninstallRun]` directive entry. +//! `TSetupRunEntry` - `[Run]` / `[UninstallRun]` directive entry. //! The same struct serves both directives. //! //! Pascal layout (`is-6_4_1:Projects/Src/Shared.Struct.pas`): @@ -26,7 +26,7 @@ use crate::{ version::Version, }; -/// `TSetupRunWait` — how to wait for the launched process. +/// `TSetupRunWait` - how to wait for the launched process. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum RunWait { @@ -91,7 +91,7 @@ pub struct RunEntry { pub run_once_id: String, /// `StatusMsg:` directive (2.0.2+). pub status_message: String, - /// `Verb:` directive — ShellExecute verb (5.1.13+). + /// `Verb:` directive - ShellExecute verb (5.1.13+). pub verb: String, /// `Description:` directive (2.0.0+ or any ISX). pub description: String, @@ -147,7 +147,7 @@ impl RunEntry { }; // Format 7.0.0+ inlined `OnLog` between Description and the - // condition strings (issrc commit `5a0ee1d4`, 2026-03-02 — + // condition strings (issrc commit `5a0ee1d4`, 2026-03-02 - // the same commit that bumped SetupID to `7.0.0.x`). let on_log = if version.at_least(7, 0, 0) { read_setup_string(reader, version, "Run.OnLog")? diff --git a/src/records/task.rs b/src/records/task.rs index 77c154a..2840798 100644 --- a/src/records/task.rs +++ b/src/records/task.rs @@ -1,4 +1,4 @@ -//! `TSetupTaskEntry` — installable task definition (`[Tasks]` +//! `TSetupTaskEntry` - installable task definition (`[Tasks]` //! section directives). //! //! Pascal layout (`is-6_4_1:Projects/Src/Shared.Struct.pas`): @@ -57,7 +57,7 @@ pub struct TaskEntry { pub name: String, /// `Description:` directive. pub description: String, - /// `GroupDescription:` directive — section header in the wizard's + /// `GroupDescription:` directive - section header in the wizard's /// task list. pub group_description: String, /// `Components:` filter (semicolon-separated). @@ -66,7 +66,7 @@ pub struct TaskEntry { pub languages: String, /// `Check:` directive. 4.0.0+ (or ISX 1.3.24+). pub check: String, - /// `Level:` directive — task priority. + /// `Level:` directive - task priority. pub level: i32, /// `Used:` boolean. Defaults to `true` on older versions. pub used: bool, diff --git a/src/records/type_.rs b/src/records/type_.rs index 18469fc..0e9427b 100644 --- a/src/records/type_.rs +++ b/src/records/type_.rs @@ -1,4 +1,4 @@ -//! `TSetupTypeEntry` — install-type definition (full / compact / +//! `TSetupTypeEntry` - install-type definition (full / compact / //! custom / user-named) read from setup-0 block 1. //! //! Pascal layout (`is-6_4_1:Projects/Src/Shared.Struct.pas`): @@ -22,18 +22,18 @@ use crate::{ version::Version, }; -/// `TSetupTypeType` — kind of install type. +/// `TSetupTypeType` - kind of install type. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum SetupTypeKind { - /// `User` — user-named type defined in `[Types]`. + /// `User` - user-named type defined in `[Types]`. User, - /// `DefaultFull` — compiler-emitted "Full installation" entry. + /// `DefaultFull` - compiler-emitted "Full installation" entry. DefaultFull, - /// `DefaultCompact` — compiler-emitted "Compact installation" + /// `DefaultCompact` - compiler-emitted "Compact installation" /// entry. DefaultCompact, - /// `DefaultCustom` — compiler-emitted "Custom installation" entry. + /// `DefaultCustom` - compiler-emitted "Custom installation" entry. DefaultCustom, } @@ -47,9 +47,9 @@ stable_name_enum!(SetupTypeKind, { /// Parsed `TSetupTypeEntry`. #[derive(Clone, Debug)] pub struct TypeEntry { - /// `Name:` directive value — the type's identifier. + /// `Name:` directive value - the type's identifier. pub name: String, - /// `Description:` directive value — wizard-shown label. + /// `Description:` directive value - wizard-shown label. pub description: String, /// `Languages:` filter (semicolon-separated). Inno Setup 4.0.1+. pub languages: String, @@ -65,7 +65,7 @@ pub struct TypeEntry { pub kind: Option, /// Raw `Typ` byte. pub kind_raw: u8, - /// `Size` — uncompressed-files total when this type is selected. + /// `Size` - uncompressed-files total when this type is selected. /// `i64` from 4.0.0+; promoted from `i32` on earlier versions. pub size: i64, } diff --git a/src/records/windows.rs b/src/records/windows.rs index 1dcc513..96be059 100644 --- a/src/records/windows.rs +++ b/src/records/windows.rs @@ -4,16 +4,16 @@ //! On-disk layout from `Shared.Struct.pas`: //! //! ```text -//! TSetupVersionDataVersion = packed record // 4 bytes — also used as `Cardinal` +//! TSetupVersionDataVersion = packed record // 4 bytes - also used as `Cardinal` //! Build: Word; // u16_le (2) //! Minor: Byte; // u8 (1) //! Major: Byte; // u8 (1) //! end; //! //! TSetupVersionData = packed record // 10 bytes -//! WinVersion: Cardinal; // u32 (4) — same shape as the triple above -//! NTVersion: Cardinal; // u32 (4) — likewise -//! NTServicePack: Word; // u16 (2) — { minor: u8, major: u8 } on disk +//! WinVersion: Cardinal; // u32 (4) - same shape as the triple above +//! NTVersion: Cardinal; // u32 (4) - likewise +//! NTServicePack: Word; // u16 (2) - { minor: u8, major: u8 } on disk //! end; //! //! windows_version_range = [ MinVersion (10) | OnlyBelowVersion (10) ] // 20 bytes @@ -29,11 +29,11 @@ use crate::{error::Error, util::read::Reader, version::Version}; /// `TSetupVersionData` (4 bytes). #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct WindowsVersion { - /// `Build` — minor build number (e.g. 19045 for Windows 10 22H2). + /// `Build` - minor build number (e.g. 19045 for Windows 10 22H2). pub build: u16, - /// `Minor` — Windows minor version. + /// `Minor` - Windows minor version. pub minor: u8, - /// `Major` — Windows major version. + /// `Major` - Windows major version. pub major: u8, } @@ -62,7 +62,7 @@ impl WindowsVersion { } } -/// `nt_service_pack` — major / minor service pack number. +/// `nt_service_pack` - major / minor service pack number. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct ServicePack { /// `NTServicePack.Minor`. @@ -71,21 +71,21 @@ pub struct ServicePack { pub major: u8, } -/// `TSetupEntryBitness` — Inno Setup 6.7.0+ replacement for the +/// `TSetupEntryBitness` - Inno Setup 6.7.0+ replacement for the /// pre-6.7 `*32Bit` / `*64Bit` flag bits on file / registry / run /// entries. One byte on disk. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum Bitness { - /// `ebInstallDefault` — match the installer's default bitness. + /// `ebInstallDefault` - match the installer's default bitness. InstallDefault, /// `eb32Bit`. Bits32, /// `eb64Bit`. Bits64, - /// `ebNativeBit` — the native bitness of the running OS. + /// `ebNativeBit` - the native bitness of the running OS. Native, - /// `ebCurrentProcessBit` — the bitness of the currently-running + /// `ebCurrentProcessBit` - the bitness of the currently-running /// installer process. CurrentProcess, } @@ -115,7 +115,7 @@ impl Bitness { } } -/// Pascal `TSetupVersionData` — a target Windows version pinning the +/// Pascal `TSetupVersionData` - a target Windows version pinning the /// underlying kernel + NT version + service pack. 10 bytes on disk. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct TargetWindows { diff --git a/src/util/checksum.rs b/src/util/checksum.rs index 0105341..3ed2421 100644 --- a/src/util/checksum.rs +++ b/src/util/checksum.rs @@ -3,14 +3,14 @@ //! //! Inno Setup uses both CRC32 (most modern paths) and Adler32 (some //! legacy `exe_checksum` fields) over various small structures. This -//! module provides simple one-shot helpers — sufficient for the +//! module provides simple one-shot helpers - sufficient for the //! offset-table and block-header checks; per-chunk content checksums //! flow through the `crypto/` module instead. /// CRC32 of `data` using the IEEE 802.3 polynomial. /// /// Computed via a precomputed 256-entry table (built at compile time -/// inside this crate) — fast enough for the offset-table and block +/// inside this crate) - fast enough for the offset-table and block /// header / sub-chunk checks without pulling in an extra dependency. pub(crate) fn crc32(data: &[u8]) -> u32 { crc32_finalize(crc32_update(crc32_init(), data)) diff --git a/src/util/encoding.rs b/src/util/encoding.rs index 9947a15..c91d80b 100644 --- a/src/util/encoding.rs +++ b/src/util/encoding.rs @@ -12,8 +12,8 @@ //! determined elsewhere (per-installer or per-language). //! //! Modern Inno Setup is Unicode by default. The `(u)` / `(U)` suffix -//! on the version marker is best treated as a hint, not a directive -//! — see [`is_unicode_for_version`] for our decision logic. +//! on the version marker is best treated as a hint, not a directive - +//! see [`is_unicode_for_version`] for our decision logic. use crate::{error::Error, util::read::Reader, version::Version}; @@ -45,7 +45,7 @@ pub(crate) fn read_utf16_string( String::from_utf16(&units).map_err(|_| Error::InvalidUtf16 { what }) } -/// Reads a length-prefixed ANSI string (raw bytes — caller decodes +/// Reads a length-prefixed ANSI string (raw bytes - caller decodes /// via the appropriate codepage if a `&str` is needed). /// /// # Errors @@ -93,7 +93,7 @@ pub(crate) fn read_setup_string( /// The marker's `(u)` suffix is the most explicit signal but it is /// inconsistent in practice: HeidiSQL 6.4.0.1 ships an ANSI marker /// yet stores all strings as UTF-16LE. Inno Setup has been -/// Unicode-by-default since 5.6 — we treat that as the rule and +/// Unicode-by-default since 5.6 - we treat that as the rule and /// the marker `(u)` as an informational hint. pub(crate) fn is_unicode_for_version(version: &Version) -> bool { if version.is_unicode() { diff --git a/src/version.rs b/src/version.rs index 3167efe..b189adf 100644 --- a/src/version.rs +++ b/src/version.rs @@ -223,7 +223,7 @@ fn parse_modern(marker: &[u8; 64], prefix: &[u8], extra_flags: VersionFlags) -> } fn parse_dotted(s: &[u8]) -> Option<(u8, u8, u8, u8)> { - // Drop a trailing `a` qualifier — innoextract observes this on + // Drop a trailing `a` qualifier - innoextract observes this on // some pre-release Inno Setup builds (e.g. `5.5.7a`). It does not // affect binary layout, so we discard it. let core = match s.last().copied() { diff --git a/tests/integration.rs b/tests/integration.rs index a068849..2fbfad5 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -35,8 +35,8 @@ fn sample(name: &str) -> Option> { /// Lists every `*.exe` directly under `tests/samples//`. /// Returns `None` (with a skip message) if the directory is missing. -/// Returns `Some(empty)` callers should treat as "no samples present -/// — assert if your test requires at least one". +/// Returns `Some(empty)` callers should treat as "no samples present - +/// assert if your test requires at least one". fn samples_in(subdir: &str) -> Option> { let dir: PathBuf = format!("{}/tests/samples/{subdir}", env!("CARGO_MANIFEST_DIR")).into(); let entries = match std::fs::read_dir(&dir) { @@ -79,7 +79,7 @@ fn rejects_non_pe_input() { #[test] fn rejects_bare_pe_with_no_inno_payload() { - // Minimal valid-MZ buffer that is otherwise zeroed — no SetupLdr + // Minimal valid-MZ buffer that is otherwise zeroed - no SetupLdr // magic, no legacy 0x30 pointer. let mut buf = vec![0u8; 4096]; buf[0] = b'M'; @@ -108,14 +108,14 @@ fn heidisql_6_4_0_1_identifies() { v.marker_str() ); // HeidiSQL ships an ANSI-encoded installer despite the 6.4.0.1 - // age — the `(u)` suffix is absent in the marker. + // age - the `(u)` suffix is absent in the marker. assert!(!v.is_unicode(), "marker = {:?}", v.marker_str()); assert!(!v.is_isx()); assert!(!v.is_16bit()); assert_eq!(v.marker_str(), "Inno Setup Setup Data (6.4.0.1)"); assert_eq!(inst.variant(), Variant::Stock); assert_eq!(inst.setup_ldr_family(), SetupLdrFamily::V5_1_5); - // 6.4.0.1 predates the 6.5.2 v2 bump — record-version 1. + // 6.4.0.1 predates the 6.5.2 v2 bump - record-version 1. assert_eq!( inst.offset_table().source.generation, OffsetTableGeneration::V1, @@ -148,7 +148,7 @@ fn heidisql_6_4_0_1_identifies() { assert_eq!(header.app_version(), Some("12.17.0.7270")); assert_eq!(header.app_publisher(), Some("Ansgar Becker")); assert_eq!(header.default_dir_name(), Some("{autopf}\\HeidiSQL")); - // ChangesAssociations is a 6.0+ field — verify it parsed via the + // ChangesAssociations is a 6.0+ field - verify it parsed via the // generic accessor too. assert!(header.string(HeaderString::ChangesAssociations).is_some()); @@ -162,7 +162,7 @@ fn heidisql_6_4_0_1_identifies() { // 6.4.0.1 predates 6.5.0, so NumISSigKeyEntries is absent. assert_eq!(counts.iss_sig_keys, None); - // Embedded blobs — convenience accessors fold empty wire + // Embedded blobs - convenience accessors fold empty wire // strings to None. HeidiSQL has License + CompiledCode, no // info screens. let license = inst.license_text().expect("license_text present"); @@ -222,7 +222,7 @@ fn heidisql_6_4_0_1_identifies() { // Bytecode disassembly: every internal proc must decode // cleanly into at least one instruction, and the very last - // one must be Return (Cm_R = 9) — script-defined procs end + // one must be Return (Cm_R = 9) - script-defined procs end // with Ret. let mut total_instructions = 0usize; let mut external_returns_none = 0usize; @@ -260,7 +260,7 @@ fn heidisql_6_4_0_1_identifies() { "expected disassemble() to return None for external procs", ); - // Container summary line — triage-friendly one-liner. + // Container summary line - triage-friendly one-liner. let summary = format!("{}", cc.display_summary()); assert!(summary.starts_with("IFPS build ")); assert!(summary.contains("internal")); @@ -371,7 +371,7 @@ fn heidisql_6_4_0_1_identifies() { ); } - // CustomMessages: HeidiSQL ships the canonical Inno set — + // CustomMessages: HeidiSQL ships the canonical Inno set - // "NameAndVersion" is the first one and present in every Inno // Setup installer that uses the standard language files. Each // CustomMessage's `language` index must point at a valid @@ -398,7 +398,7 @@ fn heidisql_6_4_0_1_identifies() { // HeidiSQL has at least one task whose name string decodes via // the per-installer Unicode codepage. We don't need to check - // exact set — just that all 5 tasks parse cleanly with non-empty + // exact set - just that all 5 tasks parse cleanly with non-empty // names. for task in inst.tasks() { assert!( @@ -431,7 +431,7 @@ fn heidisql_6_4_0_1_identifies() { assert_eq!(main_exe.location_index, 0); // At least one registry entry must have a Subkey containing - // "HeidiSQL" — verifies the registry record stream parsed the + // "HeidiSQL" - verifies the registry record stream parsed the // installer's HKCU/HKLM writes. assert!( inst.registry_entries() @@ -527,7 +527,7 @@ fn heidisql_6_4_0_1_identifies() { // Solid LZMA proof: license.txt lives in the same chunk as // heidisql.exe but at a non-zero chunk_sub_offset. Both - // extractions must succeed — the second hits the OnceLock cache. + // extractions must succeed - the second hits the OnceLock cache. let license = inst .files() .iter() @@ -680,7 +680,7 @@ fn imagemagick_6_1_0_identifies() { "magick.exe missing from files", ); // At least one registry entry should be for the file-association - // class — verifies the registry record stream parsed. + // class - verifies the registry record stream parsed. assert!( inst.registry_entries() .iter() @@ -701,7 +701,7 @@ fn imagemagick_6_1_0_identifies() { innospect::DataChecksum::Sha1(_) )); - // Extract magick.exe (LZMA1 + 5309 BCJ on a pre-6.4 sample — + // Extract magick.exe (LZMA1 + 5309 BCJ on a pre-6.4 sample - // exercises a different code path than HeidiSQL's LZMA2). // Checksum verification is enforced internally; the mere fact // that extract_to_vec returns `Ok` proves the checksum matched. diff --git a/tests/samples/README.md b/tests/samples/README.md index df05df0..b789496 100644 --- a/tests/samples/README.md +++ b/tests/samples/README.md @@ -48,7 +48,7 @@ curl -Lo tests/samples/imagemagick-setup.exe \ Both have the post-5.1.5 `rDlPtS…` SetupLdr magic in the PE resource (`RESEARCH.md` §2.3). -## Synthetic — plain (`plain/`) +## Synthetic - plain (`plain/`) No encryption. Used by `plain_samples_parse_and_extract`, which asserts every sample parses cleanly, reports `is_encrypted() == @@ -68,7 +68,7 @@ and yields the canonical `payload.txt` via `extract_files()`. | `plain-tool6_3_0.exe` | 6.3.0 | Last pre-architectures-string release | | `plain-tool6_4_3.exe` | 6.4.3 | First XChaCha20-era release | | `plain-tool6_5_2.exe` | 6.5.2 | First standalone-encryption-header release | -| `plain-tool6_5_2-alt.exe` | 6.5.2 | Same script, second build — nondeterminism check | +| `plain-tool6_5_2-alt.exe` | 6.5.2 | Same script, second build - nondeterminism check | | `plain-tool6_6_1.exe` | 6.6.1 | Mid-range 6.x coverage | | `plain-tool7_0_0_1.exe` | 7.0.0-preview-3 | Buggy-PBKDF2 marker `(7,0,0,1)` regression sample | @@ -76,7 +76,7 @@ and yields the canonical `payload.txt` via `extract_files()`. produced encrypted variants. Rebuild with `-Tag is-6_7_0` (no `-WithFull` needed for the plain output) to fill it in. -## Synthetic — encrypted (`encrypted/`) +## Synthetic - encrypted (`encrypted/`) All share password **`test123`** and the canonical `payload.txt` (21 bytes, `Inno test payload v1\n`). Used by @@ -103,12 +103,12 @@ All share password **`test123`** and the canonical `payload.txt` | `enc-files-tool6_3_0.exe` | 6.3.0 | per-chunk | ARC4 + SHA-1 (last pre-6.4 ARC4 release) | | `enc-files-tool6_4_3.exe` | 6.4.3 | euFiles | XChaCha20 / inline `PasswordTest` (PBKDF2) | | `enc-files-tool6_5_2.exe` | 6.5.2 | euFiles | XChaCha20 / `TSetupEncryptionHeader` | -| `enc-files-tool6_5_2-alt.exe` | 6.5.2 | euFiles | XChaCha20 — nondeterminism rebuild | +| `enc-files-tool6_5_2-alt.exe` | 6.5.2 | euFiles | XChaCha20 - nondeterminism rebuild | | `enc-files-tool6_6_1.exe` | 6.6.1 | euFiles | XChaCha20 | | `enc-files-tool6_7_0.exe` | 6.7.0 | euFiles | XChaCha20 | | `enc-files-tool7_0_0_1.exe` | 7.0.0-preview-3 | euFiles | XChaCha20 with **buggy PBKDF2** (XOR'd `U_1`) | | `enc-full-tool6_5_2.exe` | 6.5.2 | euFull | XChaCha20 / `sccCompressedBlocks1/2` | -| `enc-full-tool6_5_2-alt.exe` | 6.5.2 | euFull | XChaCha20 — nondeterminism rebuild | +| `enc-full-tool6_5_2-alt.exe` | 6.5.2 | euFull | XChaCha20 - nondeterminism rebuild | | `enc-full-tool6_6_1.exe` | 6.6.1 | euFull | XChaCha20 | | `enc-full-tool6_7_0.exe` | 6.7.0 | euFull | XChaCha20 | | `enc-full-tool7_0_0_1.exe` | 7.0.0-preview-3 | euFull | XChaCha20 with buggy PBKDF2 | @@ -121,7 +121,7 @@ legacy indicator lives in the setup-header `Options` bitset: ## Quarantine (`quarantine/`) -Samples that successfully build but currently fail parse — each +Samples that successfully build but currently fail parse - each pinpoints a specific format-coverage gap and serves as a regression fixture for the eventual fix. These are **not** walked by `plain_samples_parse_and_extract` / @@ -129,7 +129,7 @@ by `plain_samples_parse_and_extract` / ToDo Stage 3 ladder lands, the matching pair moves back to `plain/` + `encrypted/`. -No samples currently quarantined — every produced sample pair parses +No samples currently quarantined - every produced sample pair parses and its `payload.txt` extracts cleanly. The pre-5.5 ladder (5.0.8..5.4.3) was promoted into `plain/` + `encrypted/` once the header parser learnt the per-version `String` / `AnsiString` field @@ -139,7 +139,7 @@ pre-5.5.0 `TSetupHeaderOption` bit table (mirroring innoextract's ## Filename convention -Outputs are named by `build-toolchain.ps1`'s slug — `Version` with +Outputs are named by `build-toolchain.ps1`'s slug - `Version` with `.` replaced by `_`, optional `-alt` for nondeterminism rebuilds, `_ansi` for explicit pre-5.3 ANSI builds: @@ -188,16 +188,16 @@ scp 'inno-build:inno-test/enc-full-tool*.exe' tests/samples/encrypted/ Versions and edge cases not yet in the matrix: -- **Plain 6.7.0** — see note above; trivial rebuild. -- **4.x** representative — needs older VC runtime on the host. -- **3.x / 2.x / 1.5** — pre-4.0.9 setup-loader paths. -- **16-bit 1.2.x** — pre-PE setup loader; needs a separate build +- **Plain 6.7.0** - see note above; trivial rebuild. +- **4.x** representative - needs older VC runtime on the host. +- **3.x / 2.x / 1.5** - pre-4.0.9 setup-loader paths. +- **16-bit 1.2.x** - pre-PE setup loader; needs a separate build script. Validates `BITS16` flag and `i1.2.10--16` legacy marker. -- **ISX (`My Inno Setup Extensions ≤ 3.0.6.1`)** — validates +- **ISX (`My Inno Setup Extensions ≤ 3.0.6.1`)** - validates `Variant::Isx` discrimination. -- **Multi-slice** — `DiskSpanning=yes` + small `DiskSliceSize`, +- **Multi-slice** - `DiskSpanning=yes` + small `DiskSliceSize`, unblocks the multi-slice extraction path. -- **7.0.0.3+** — once a fix-bearing 7.x ISCC ships, validates +- **7.0.0.3+** - once a fix-bearing 7.x ISCC ships, validates `CompiledCodeVersion` / `Bitness` claims and lets us replace the marker-keyed PBKDF2 gate with a `SetupBinVersion` check. diff --git a/tests/samples/build/encrypted-full.iss b/tests/samples/build/encrypted-full.iss index a125fb3..d9f1264 100644 --- a/tests/samples/build/encrypted-full.iss +++ b/tests/samples/build/encrypted-full.iss @@ -1,9 +1,9 @@ -; euFull sample — `Encryption=full` directive available 6.5.0+. +; euFull sample - `Encryption=full` directive available 6.5.0+. ; Produces an installer where the setup-0 stream itself is ; XChaCha20-encrypted (sccCompressedBlocks1/2 contexts), in ; addition to the per-chunk encryption. ; -; **Skip on Inno Setup < 6.5** — the directive doesn't exist +; **Skip on Inno Setup < 6.5** - the directive doesn't exist ; there and ISCC will reject the script. ; ; Build: ISCC.exe encrypted-full.iss @@ -28,7 +28,7 @@ Encryption=full [Files] Source: "payload.txt"; DestDir: "{app}"; Flags: ignoreversion -; See encrypted.iss for the rationale — every rebuild needs at least one +; See encrypted.iss for the rationale - every rebuild needs at least one ; [Run]/[UninstallRun] entry to exercise `RunEntry::read` (Run.OnLog, ; Run.Bitness cutoffs). [Run] diff --git a/tests/samples/build/encrypted.iss b/tests/samples/build/encrypted.iss index 1521e46..17cfc0d 100644 --- a/tests/samples/build/encrypted.iss +++ b/tests/samples/build/encrypted.iss @@ -30,7 +30,7 @@ Source: "payload.txt"; DestDir: "{app}"; Flags: ignoreversion ; Exercise `RunEntry::read` on the wire. The fields are format-affecting ; per version (`Run.OnLog` added at SetupBinVersion 7.0.0.1, `Run.Bitness` -; cutoff change) — having any [Run]/[UninstallRun] entry forces the +; cutoff change) - having any [Run]/[UninstallRun] entry forces the ; decoder to walk those bytes on every rebuilt sample. [Run] Filename: "{cmd}"; Parameters: "/c echo Inno test run"; Flags: runhidden diff --git a/tests/samples/build/plain.iss b/tests/samples/build/plain.iss index e9d170f..0140c4d 100644 --- a/tests/samples/build/plain.iss +++ b/tests/samples/build/plain.iss @@ -20,7 +20,7 @@ SolidCompression=yes [Files] Source: "payload.txt"; DestDir: "{app}"; Flags: ignoreversion -; See encrypted.iss for the rationale — every rebuild needs at least one +; See encrypted.iss for the rationale - every rebuild needs at least one ; [Run]/[UninstallRun] entry to exercise `RunEntry::read` (Run.OnLog, ; Run.Bitness cutoffs). [Run]