#![doc(html_root_url = "https://docs.rs/inno")]
#![allow(dead_code)]
mod compression;
mod encryption;
pub mod entry;
pub mod error;
pub mod header;
mod loader;
mod lzma_stream_header;
mod pe;
mod read;
pub mod string;
pub mod version;
mod wizard;
use std::{
io,
io::{Read, Seek, SeekFrom},
};
use encoding_rs::{UTF_16LE, WINDOWS_1252};
use encryption::EncryptionHeader;
use entry::{
Component, DeleteEntry, Directory, File, FileLocation, ISSigKey, Icon, Ini, Language, Message,
MessageEntry, Permission, RegistryEntry, RunEntry, Task, Type,
};
use error::{HeaderStream, InnoError};
pub use header::Header;
use itertools::Itertools;
use loader::SetupLoader;
use read::{ReadBytesExt, stream::InnoStreamReader};
use version::{InnoVersion, windows_version::WindowsVersionRange};
pub use wizard::Wizard;
pub use zerocopy;
#[derive(Debug)]
pub struct Inno {
pub setup_loader: SetupLoader,
version: InnoVersion,
encryption_header: Option<EncryptionHeader>,
pub header: Header,
languages: Vec<Language>,
messages: Vec<MessageEntry>,
permissions: Vec<Permission>,
type_entries: Vec<Type>,
components: Vec<Component>,
tasks: Vec<Task>,
directories: Vec<Directory>,
is_sig_keys: Vec<ISSigKey>,
files: Vec<File>,
icons: Vec<Icon>,
ini_entries: Vec<Ini>,
registry_entries: Vec<RegistryEntry>,
delete_entries: Vec<DeleteEntry>,
uninstall_delete_entries: Vec<DeleteEntry>,
run_entries: Vec<RunEntry>,
uninstall_run_entries: Vec<RunEntry>,
wizard: Wizard,
file_locations: Vec<FileLocation>,
}
impl Inno {
pub const MAX_SUPPORTED_VERSION: InnoVersion = InnoVersion::new(6, 7, u8::MAX, u8::MAX);
pub fn new<R>(mut reader: R) -> Result<Self, InnoError>
where
R: Read + Seek,
{
let setup_loader =
SetupLoader::read_from(&mut reader).map_err(|_| InnoError::NotInnoFile)?;
reader.seek(SeekFrom::Start(setup_loader.header_offset().unsigned_abs()))?;
let mut inno_version = InnoVersion::read(&mut reader)?;
if inno_version > Self::MAX_SUPPORTED_VERSION {
return Err(InnoError::UnsupportedVersion(inno_version));
}
if let Some(mut versions_to_try) = inno_version.ambiguous_candidates().map(Vec::into_iter) {
let position = reader.stream_position()?;
loop {
match Self::read_stream(&mut reader, setup_loader, inno_version) {
Ok(inno) => break Ok(inno),
Err(err) => {
if let Some(next) = versions_to_try.next() {
inno_version = next;
reader.seek(SeekFrom::Start(position))?;
} else {
break Err(err);
}
}
}
}
} else {
Self::read_stream(&mut reader, setup_loader, inno_version)
}
}
fn read_stream<R: Read + Seek>(
mut reader: R,
setup_loader: SetupLoader,
inno_version: InnoVersion,
) -> Result<Self, InnoError> {
let encryption_header = if inno_version >= 6.5 {
Some(EncryptionHeader::read(&mut reader, inno_version)?)
} else {
None
};
let mut reader = InnoStreamReader::new(&mut reader, inno_version)?;
let mut header = Header::read(&mut reader, inno_version)?;
let languages = (0..header.language_count())
.map(|_| Language::read(&mut reader, inno_version))
.collect::<io::Result<Vec<_>>>()?;
let codepage = if inno_version.is_unicode() {
UTF_16LE
} else {
languages
.iter()
.map(Language::codepage)
.find_or_first(|&codepage| codepage == WINDOWS_1252)
.unwrap_or(WINDOWS_1252)
};
header.decode(codepage);
let mut wizard = if inno_version < 4 {
Wizard::read(&mut reader, &header, inno_version)?
} else {
Wizard::default()
};
let messages = (0..header.custom_message_count())
.map(|_| MessageEntry::read(&mut reader, &languages, codepage))
.collect::<io::Result<Vec<_>>>()?;
let permissions = (0..header.permission_count())
.map(|_| Permission::read(&mut reader))
.collect::<io::Result<Vec<_>>>()?;
let type_entries = (0..header.type_count())
.map(|_| Type::read(&mut reader, codepage, inno_version))
.collect::<io::Result<Vec<_>>>()?;
let components = (0..header.component_count())
.map(|_| Component::read(&mut reader, codepage, inno_version))
.collect::<io::Result<Vec<_>>>()?;
let tasks = (0..header.task_count())
.map(|_| Task::read(&mut reader, codepage, inno_version))
.collect::<io::Result<Vec<_>>>()?;
let directories = (0..header.directory_count())
.map(|_| Directory::read(&mut reader, codepage, inno_version))
.collect::<io::Result<Vec<_>>>()?;
let is_sig_keys = (0..header.is_sig_keys_count())
.map(|_| ISSigKey::read(&mut reader, codepage))
.collect::<io::Result<Vec<_>>>()?;
let files = (0..header.file_count())
.map(|_| File::read(&mut reader, codepage, inno_version))
.collect::<io::Result<Vec<_>>>()?;
let icons = (0..header.icon_count())
.map(|_| Icon::read(&mut reader, codepage, inno_version))
.collect::<io::Result<Vec<_>>>()?;
let ini_entries = (0..header.ini_entry_count())
.map(|_| Ini::read(&mut reader, codepage, inno_version))
.collect::<io::Result<Vec<_>>>()?;
let registry_entries = (0..header.registry_entry_count())
.map(|_| RegistryEntry::read(&mut reader, codepage, inno_version))
.collect::<io::Result<Vec<_>>>()?;
let delete_entries = (0..header.install_delete_entry_count())
.map(|_| DeleteEntry::read(&mut reader, codepage, inno_version))
.collect::<io::Result<Vec<_>>>()?;
let uninstall_delete_entries = (0..header.uninstall_delete_entry_count())
.map(|_| DeleteEntry::read(&mut reader, codepage, inno_version))
.collect::<io::Result<Vec<_>>>()?;
let run_entries = (0..header.run_entry_count())
.map(|_| RunEntry::read(&mut reader, codepage, inno_version))
.collect::<io::Result<Vec<_>>>()?;
let uninstall_run_entries = (0..header.uninstall_run_entry_count())
.map(|_| RunEntry::read(&mut reader, codepage, inno_version))
.collect::<io::Result<Vec<_>>>()?;
if inno_version >= 4 {
wizard = Wizard::read(&mut reader, &header, inno_version)?;
}
if !reader.is_end_of_stream() {
return Err(InnoError::UnexpectedExtraData(HeaderStream::Primary));
}
reader = reader.reset()?;
let file_locations = (0..header.file_location_entry_count())
.map(|_| FileLocation::read(&mut reader, &header, inno_version))
.collect::<io::Result<Vec<_>>>()?;
if !reader.is_end_of_stream() {
return Err(InnoError::UnexpectedExtraData(HeaderStream::Secondary));
}
Ok(Self {
setup_loader,
version: inno_version,
encryption_header,
header,
languages,
messages,
permissions,
type_entries,
components,
tasks,
directories,
is_sig_keys,
files,
icons,
ini_entries,
registry_entries,
delete_entries,
uninstall_delete_entries,
run_entries,
uninstall_run_entries,
wizard,
file_locations,
})
}
#[must_use]
#[inline]
pub const fn version(&self) -> InnoVersion {
self.version
}
#[must_use]
pub fn encryption_header(&self) -> Option<&EncryptionHeader> {
self.encryption_header
.as_ref()
.or_else(|| self.header.encryption_header())
}
#[must_use]
#[inline]
pub const fn primary_language(&self) -> Option<&Language> {
self.languages().first()
}
#[must_use]
#[inline]
pub const fn languages(&self) -> &[Language] {
self.languages.as_slice()
}
#[must_use]
#[inline]
pub const fn message_entries(&self) -> &[MessageEntry] {
self.messages.as_slice()
}
pub fn messages(&self) -> impl Iterator<Item = Message<'_, '_>> {
self.messages
.iter()
.map(|message| Message::new(message, self.languages()))
}
#[must_use]
#[inline]
pub const fn permissions(&self) -> &[Permission] {
self.permissions.as_slice()
}
#[must_use]
#[inline]
pub const fn type_entries(&self) -> &[Type] {
self.type_entries.as_slice()
}
#[must_use]
#[inline]
pub const fn components(&self) -> &[Component] {
self.components.as_slice()
}
#[must_use]
#[inline]
pub const fn tasks(&self) -> &[Task] {
self.tasks.as_slice()
}
#[must_use]
#[inline]
pub const fn directories(&self) -> &[Directory] {
self.directories.as_slice()
}
#[must_use]
#[inline]
pub const fn is_sig_keys(&self) -> &[ISSigKey] {
self.is_sig_keys.as_slice()
}
#[must_use]
#[inline]
pub const fn files(&self) -> &[File] {
self.files.as_slice()
}
#[must_use]
#[inline]
pub const fn icons(&self) -> &[Icon] {
self.icons.as_slice()
}
#[must_use]
#[inline]
pub const fn ini_entries(&self) -> &[Ini] {
self.ini_entries.as_slice()
}
#[must_use]
#[inline]
pub const fn registry_entries(&self) -> &[RegistryEntry] {
self.registry_entries.as_slice()
}
#[must_use]
#[inline]
pub const fn delete_entries(&self) -> &[DeleteEntry] {
self.delete_entries.as_slice()
}
#[must_use]
#[inline]
pub const fn uninstall_delete_entries(&self) -> &[DeleteEntry] {
self.uninstall_delete_entries.as_slice()
}
#[must_use]
#[inline]
pub const fn run_entries(&self) -> &[RunEntry] {
self.run_entries.as_slice()
}
#[must_use]
#[inline]
pub const fn uninstall_run_entries(&self) -> &[RunEntry] {
self.uninstall_run_entries.as_slice()
}
#[must_use]
#[inline]
pub const fn wizard(&self) -> &Wizard {
&self.wizard
}
#[must_use]
#[inline]
pub const fn file_locations(&self) -> &[FileLocation] {
self.file_locations.as_slice()
}
}