Skip to main content

innospect/
lib.rs

1//! Parse and inspect Inno Setup installer binaries.
2//!
3//! This crate provides typed access to the internal structures of an
4//! [Inno Setup](https://jrsoftware.org/isinfo.php) installer executable,
5//! from the PE loader stub through the compressed setup header down to
6//! individual records and embedded files.
7//!
8//! Format research is tracked in `RESEARCH.md` at the crate root and is
9//! the source of truth for what each layer of the parser implements.
10//!
11//! # Stable Identifiers
12//!
13//! Public discriminator enums expose stable [`core::fmt::Display`]
14//! strings, and most also expose `as_str()`. Consumers may persist
15//! those strings in databases and compare them across crate releases.
16//! New variants on `#[non_exhaustive]` enums are added in minor-version
17//! releases; once a variant's display string is published, that string
18//! is a compatibility surface and will not be renamed.
19//!
20//! # Architecture
21//!
22//! The crate is organized in layers that mirror the on-disk format:
23//!
24//! - **PE overlay detection** ([`overlay`]): Locates the Inno Setup loader
25//!   table (`SetupLdr`) and the setup payload appended after the PE
26//!   sections.
27//! - **Decompression** ([`decompress`]): Handles `zlib`, LZMA, and LZMA2
28//!   decompression of the setup header and data streams.
29//! - **Low-level structures** ([`header`], [`records`]): View types for
30//!   each structure in the setup header (`TSetupHeader`, file/registry/
31//!   ini/run records, language tables, Pascal script, and the file
32//!   location table).
33//! - **High-level API** ([`InnoInstaller`]): Ties everything together
34//!   into a convenient exploration interface for analysis use cases.
35
36// The `missing_docs`, `unsafe_code`, `clippy::unwrap_used`,
37// `clippy::expect_used`, `clippy::panic`,
38// `clippy::arithmetic_side_effects`, and `clippy::indexing_slicing` lints
39// are declared in `Cargo.toml` under `[lints]` so they enforce on every
40// build regardless of the consuming workspace. innospect is used in
41// malware-analysis pipelines where every input byte is adversarial and
42// the parser must not panic.
43#![cfg_attr(
44    test,
45    allow(
46        clippy::unwrap_used,
47        clippy::expect_used,
48        clippy::panic,
49        clippy::arithmetic_side_effects,
50        clippy::indexing_slicing
51    )
52)]
53
54macro_rules! stable_name_enum {
55    ($ty:ty, { $($pat:pat => $name:literal),+ $(,)? }) => {
56        impl $ty {
57            /// Returns this value's stable identifier.
58            #[must_use]
59            pub fn as_str(&self) -> &'static str {
60                match self {
61                    $($pat => $name,)+
62                }
63            }
64        }
65
66        impl core::fmt::Display for $ty {
67            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68                f.write_str(self.as_str())
69            }
70        }
71    };
72}
73
74macro_rules! stable_flag_enum {
75    ($ty:ty, { $($variant:ident => $name:literal),+ $(,)? }) => {
76        stable_name_enum!($ty, { $(Self::$variant => $name,)+ });
77
78        impl $ty {
79            /// Canonical storage order for this flag enum.
80            pub const ORDER: &'static [Self] = &[$(Self::$variant,)+];
81
82            /// Canonical stable-name order for this flag enum.
83            pub const NAME_ORDER: &'static [&'static str] = &[$($name,)+];
84
85            /// Returns this flag's canonical bit position.
86            #[must_use]
87            pub fn bit(self) -> u64 {
88                let shift = Self::ORDER
89                    .iter()
90                    .position(|flag| *flag == self)
91                    .and_then(|idx| u32::try_from(idx).ok());
92                shift.and_then(|idx| 1_u64.checked_shl(idx)).unwrap_or(0)
93            }
94
95            /// Converts a set of flags into the canonical bitmask.
96            #[must_use]
97            pub fn set_to_bits(set: &std::collections::HashSet<Self>) -> u64 {
98                set.iter().fold(0_u64, |acc, flag| acc | flag.bit())
99            }
100        }
101    };
102}
103
104pub mod analysis;
105pub mod decompress;
106pub mod error;
107pub mod extract;
108pub mod header;
109pub mod installer;
110pub mod overlay;
111pub mod records;
112pub mod version;
113
114/// Re-export of the `pascalscript` crate so existing
115/// `innospect::pascalscript::*` paths resolve through `innospect`. The
116/// parser itself is the standalone
117/// [`pascalscript`](https://github.com/BinFlip/pascalscript-rs)
118/// crate; this re-export is for caller convenience.
119pub use ::pascalscript;
120
121mod crypto;
122mod util;
123
124pub use error::Error;
125pub use extract::FileReader;
126pub use header::{
127    Architecture, AutoNoYes, CompressMethod, EntryCounts, HeaderAnsi, HeaderOption, HeaderString,
128    HeaderTail, ImageAlphaFormat, LanguageDetectionMethod, PrivilegesRequired,
129    PrivilegesRequiredOverride, SetupHeader, UninstallLogMode, WizardStyle, YesNoAuto,
130};
131pub use installer::{Compression, EncryptionInfo, EncryptionMode, InnoInstaller};
132pub use overlay::{
133    OffsetTable, OffsetTableSource, SetupLdrFamily, pe::LocatorMode as PeLocatorMode,
134};
135pub use records::{
136    component::{ComponentEntry, ComponentFlag},
137    dataentry::{DataChecksum, DataEntry, DataFlag, SignMode},
138    delete::{DeleteEntry, DeleteTargetType},
139    directory::{DirectoryEntry, DirectoryFlag},
140    file::{FileEntry, FileEntryType, FileFlag, FileVerification, FileVerificationKind},
141    icon::{CloseOnExit, IconEntry, IconFlag},
142    ini::{IniEntry, IniFlag},
143    isssigkey::ISSigKeyEntry,
144    language::{LanguageCodepage, LanguageEntry},
145    message::MessageEntry,
146    permission::PermissionEntry,
147    registry::{RegistryEntry, RegistryFlag, RegistryHive, RegistryValueType},
148    run::{RunEntry, RunFlag, RunWait},
149    task::{TaskEntry, TaskFlag},
150    type_::{SetupTypeKind, TypeEntry},
151    windows::Bitness,
152};
153pub use version::{Variant, Version, VersionFlags};
154
155// Thread-safety guarantee: InnoInstaller borrows from a `&[u8]` input
156// buffer, holds no interior mutability, and contains no raw pointers
157// or `Cell`/`RefCell`. It is therefore both `Send` and `Sync`. This
158// static assertion makes the guarantee a compile-time invariant: a
159// future change that adds a non-Send/non-Sync field will break the
160// build here, not silently at a downstream `.await` point.
161const _: fn() = || {
162    fn assert_send_sync<T: Send + Sync>() {}
163    assert_send_sync::<InnoInstaller<'static>>();
164};