Skip to main content

dump/
dump.rs

1//! Inno Setup installer dump tool.
2//!
3//! Usage:
4//!   `cargo run --example dump -- <setup.exe>`
5
6use std::{env, fs, process};
7
8fn main() {
9    let args: Vec<String> = env::args().collect();
10    let Some(path) = args.get(1) else {
11        eprintln!("usage: dump <setup.exe>");
12        process::exit(1);
13    };
14
15    let data = fs::read(path).unwrap_or_else(|e| {
16        eprintln!("error reading {path}: {e}");
17        process::exit(1);
18    });
19
20    let installer = innospect::InnoInstaller::from_bytes(&data).unwrap_or_else(|e| {
21        eprintln!("error parsing Inno Setup installer: {e}");
22        process::exit(1);
23    });
24
25    let v = installer.version();
26    println!("== Identification ==");
27    println!("  marker:        {:?}", v.marker_str());
28    println!("  version:       {}.{}.{}.{}", v.a, v.b, v.c, v.d);
29    println!("  flags:         {:?}", v.flags);
30    println!("  variant:       {:?}", installer.variant());
31    println!(
32        "  setupldr:      {:?} (locator: {:?})",
33        installer.setup_ldr_family(),
34        installer.pe_locator_mode(),
35    );
36
37    let ot = installer.offset_table();
38    println!();
39    println!("== Offset table ==");
40    println!("  generation:    {:?}", ot.source.generation);
41    println!("  version_id:    {}", ot.version_id);
42    println!("  start:         {:#x}", ot.source.start);
43    println!("  Offset0:       {:#x}", ot.offset_setup0);
44    println!("  Offset1:       {:#x}", ot.offset_setup1);
45    println!("  OffsetEXE:     {:#x}", ot.offset_exe);
46    println!("  TotalSize:     {} bytes", ot.total_size,);
47
48    println!();
49    println!("== Setup-0 ==");
50    println!("  compression:   {:?}", installer.compression());
51    println!(
52        "  encryption:    {:?}",
53        installer.encryption().map(|e| e.mode)
54    );
55    let setup0 = installer.decompressed_setup0();
56    println!("  decompressed:  {} bytes", setup0.len());
57    if !setup0.is_empty() {
58        let preview_len = 128.min(setup0.len());
59        let preview: Vec<u8> = setup0
60            .iter()
61            .take(preview_len)
62            .map(|&b| if (32..127).contains(&b) { b } else { b'.' })
63            .collect();
64        println!("  preview:       {:?}", String::from_utf8_lossy(&preview),);
65    }
66
67    if let Some(header) = installer.header() {
68        println!();
69        println!("== Setup header ==");
70        println!("  AppName:        {:?}", header.app_name().unwrap_or(""));
71        println!("  AppId:          {:?}", header.app_id().unwrap_or(""));
72        println!("  AppVersion:     {:?}", header.app_version().unwrap_or(""));
73        println!(
74            "  AppPublisher:   {:?}",
75            header.app_publisher().unwrap_or(""),
76        );
77        println!(
78            "  DefaultDirName: {:?}",
79            header.default_dir_name().unwrap_or(""),
80        );
81
82        let counts = header.counts();
83        println!();
84        println!("== Entry counts ==");
85        println!("  languages:        {}", counts.languages);
86        println!("  custom_messages:  {}", counts.custom_messages);
87        println!("  permissions:      {}", counts.permissions);
88        println!("  types:            {}", counts.types);
89        println!("  components:       {}", counts.components);
90        println!("  tasks:            {}", counts.tasks);
91        println!("  directories:      {}", counts.directories);
92        if let Some(n) = counts.iss_sig_keys {
93            println!("  iss_sig_keys:     {n}");
94        }
95        println!("  files:            {}", counts.files);
96        println!("  file_locations:   {}", counts.file_locations);
97        println!("  icons:            {}", counts.icons);
98        println!("  ini_entries:      {}", counts.ini_entries);
99        println!("  registry:         {}", counts.registry);
100        println!("  install_deletes:  {}", counts.install_deletes);
101        println!("  uninstall_deletes:{}", counts.uninstall_deletes,);
102        println!("  run:              {}", counts.run);
103        println!("  uninstall_run:    {}", counts.uninstall_run);
104
105        let license_len = installer
106            .header()
107            .and_then(|h| h.ansi(innospect::HeaderAnsi::LicenseText))
108            .map_or(0, <[u8]>::len);
109        let info_before_len = installer
110            .header()
111            .and_then(|h| h.ansi(innospect::HeaderAnsi::InfoBeforeText))
112            .map_or(0, <[u8]>::len);
113        let info_after_len = installer
114            .header()
115            .and_then(|h| h.ansi(innospect::HeaderAnsi::InfoAfterText))
116            .map_or(0, <[u8]>::len);
117        let compiled_len = installer
118            .header()
119            .and_then(|h| h.ansi(innospect::HeaderAnsi::CompiledCodeText))
120            .map_or(0, <[u8]>::len);
121        println!();
122        println!("== Embedded blobs ==");
123        println!("  license_text:        {license_len} bytes");
124        println!("  info_before:         {info_before_len} bytes");
125        println!("  info_after:          {info_after_len} bytes");
126        println!("  compiled_code_text:  {compiled_len} bytes");
127
128        let tail = header.tail();
129        let tail_size = header
130            .records_offset()
131            .saturating_sub(header.tail_start_offset());
132        println!();
133        println!("== Fixed numeric tail ({tail_size} bytes) ==");
134        println!(
135            "  MinVersion (Win):       {}.{} build {}",
136            tail.windows_version_range.min.windows.major,
137            tail.windows_version_range.min.windows.minor,
138            tail.windows_version_range.min.windows.build,
139        );
140        println!(
141            "  OnlyBelowVersion (Win): {}.{} build {}",
142            tail.windows_version_range.only_below.windows.major,
143            tail.windows_version_range.only_below.windows.minor,
144            tail.windows_version_range.only_below.windows.build,
145        );
146        println!("  WizardStyle:            {:?}", tail.wizard_style);
147        println!(
148            "  WizardSizePercent:      ({}, {})",
149            tail.wizard_size_percent_x, tail.wizard_size_percent_y,
150        );
151        println!("  PrivilegesRequired:     {:?}", tail.privileges_required);
152        println!("  CompressMethod:         {:?}", tail.compress_method);
153        println!(
154            "  ExtraDiskSpaceRequired: {} bytes",
155            tail.extra_disk_space_required,
156        );
157        println!(
158            "  UninstallDisplaySize:   {} bytes",
159            tail.uninstall_display_size,
160        );
161        println!("  Options ({} set):", tail.options.len());
162        let mut sorted: Vec<_> = tail.options.iter().collect();
163        sorted.sort_by_key(|o| format!("{o:?}"));
164        for opt in sorted {
165            println!("    - {opt:?}");
166        }
167    }
168
169    println!();
170    println!("== Data block (file-location records) ==");
171    let data = installer.data_block();
172    println!("  decompressed:  {} bytes", data.len());
173
174    println!();
175    println!(
176        "== Records (3c lightweight) — languages={} messages={} permissions={} types={} components={} tasks={} ==",
177        installer.languages().len(),
178        installer.messages().len(),
179        installer.permissions().len(),
180        installer.types().len(),
181        installer.components().len(),
182        installer.tasks().len(),
183    );
184    for (i, l) in installer.languages().iter().enumerate().take(5) {
185        let name = l.name_string().unwrap_or_default();
186        let pretty = l.language_name_string().unwrap_or_default();
187        println!(
188            "  language[{i}]: id={:#06x} cp={:?} name={:?} ({:?})",
189            l.language_id, l.codepage, name, pretty,
190        );
191    }
192    if installer.languages().len() > 5 {
193        println!(
194            "    … {} more",
195            installer.languages().len().saturating_sub(5)
196        );
197    }
198    for (i, t) in installer.tasks().iter().enumerate().take(8) {
199        println!(
200            "  task[{i}]: name={:?} flags={:?} level={}",
201            t.name, t.flags, t.level,
202        );
203    }
204
205    println!();
206    println!(
207        "== Records (3d heavy) — dirs={} files={} icons={} ini={} reg={} ins_del={} unins_del={} run={} unins_run={} file_loc={} ==",
208        installer.directories().len(),
209        installer.files().len(),
210        installer.icons().len(),
211        installer.ini_entries().len(),
212        installer.registry_entries().len(),
213        installer.install_deletes().len(),
214        installer.uninstall_deletes().len(),
215        installer.run_entries().len(),
216        installer.uninstall_runs().len(),
217        installer.file_locations().len(),
218    );
219    for (i, f) in installer.files().iter().enumerate().take(4) {
220        println!(
221            "  file[{i}]: src={:?} dst={:?} loc={} ext_size={} flags={}",
222            f.source,
223            f.destination,
224            f.location_index,
225            f.external_size,
226            f.flags.len(),
227        );
228    }
229    if installer.files().len() > 4 {
230        println!("    … {} more", installer.files().len().saturating_sub(4));
231    }
232    for (i, r) in installer.registry_entries().iter().enumerate() {
233        println!(
234            "  reg[{i}]: hive={:?} subkey={:?} name={:?} type={:?}",
235            r.hive, r.subkey, r.value_name, r.value_type,
236        );
237    }
238    for (i, ic) in installer.icons().iter().enumerate().take(4) {
239        println!(
240            "  icon[{i}]: name={:?} target={:?} args={:?}",
241            ic.name, ic.filename, ic.parameters,
242        );
243    }
244    for (i, r) in installer.run_entries().iter().enumerate().take(4) {
245        println!(
246            "  run[{i}]: cmd={:?} args={:?} wait={:?} flags={:?}",
247            r.name, r.parameters, r.wait, r.flags,
248        );
249    }
250    for (i, d) in installer.file_locations().iter().enumerate().take(2) {
251        println!(
252            "  file_loc[{i}]: orig_size={} compressed={} chunk_offset={} flags={:?}",
253            d.original_size, d.chunk_compressed_size, d.chunk_sub_offset, d.flags,
254        );
255    }
256
257    println!();
258    println!("== Extraction ==");
259    let mut extracted = 0usize;
260    let mut total_bytes: u64 = 0;
261    let mut errors = 0usize;
262    for f in installer.files() {
263        if f.location_index == u32::MAX {
264            continue;
265        }
266        match installer.extract_to_vec(f) {
267            Ok(bytes) => {
268                total_bytes = total_bytes.saturating_add(bytes.len() as u64);
269                extracted = extracted.saturating_add(1);
270            }
271            Err(e) => {
272                errors = errors.saturating_add(1);
273                if errors <= 3 {
274                    println!("  ! {:?}: {e}", f.destination);
275                }
276            }
277        }
278    }
279    println!("  extracted: {extracted} files / {total_bytes} bytes / {errors} errors");
280}