mod checksum;
mod offset;
mod signature;
use std::{
io,
io::{Read, Seek, SeekFrom},
};
pub use checksum::Checksum;
use offset::SetupLoaderOffset;
use signature::SetupLoaderSignature;
use zerocopy::LE;
use super::{
InnoError, ReadBytesExt,
pe::{
CoffHeader, DosHeader, OptionalHeader, SectionTable, Signature,
resource::{ImageResourceDataEntry, ResourceDirectory, SectionReader},
},
read::crc32::Crc32Reader,
version::InnoVersion,
};
#[derive(Copy, Clone, Debug)]
pub struct SetupLoader {
#[doc(alias = "ID")]
signature: SetupLoaderSignature,
version: InnoVersion,
#[doc(alias = "SetupLdrOffsetTableVersion")]
revision: u32,
#[doc(alias = "TotalSize")]
minimum_setup_exe_size: i64,
#[doc(alias = "OffsetEXE")]
exe_offset: i64,
#[doc(alias = "CompressedSizeEXE")]
exe_compressed_size: u32,
#[doc(alias = "UncompressedSizeEXE")]
exe_uncompressed_size: u32,
#[doc(alias = "CRCEXE")]
exe_checksum: Checksum,
message_offset: u32,
#[doc(alias = "Offset0")]
header_offset: i64,
#[doc(alias = "Offset1")]
data_offset: i64,
#[doc(alias = "ReservedPadding")]
reserved_padding: u32,
}
impl SetupLoader {
const EXE_MODE_OFFSET: u32 = 0x30;
const TABLE_RESOURCE_ID: u32 = 11111;
pub fn read_from<R>(mut src: R) -> Result<Self, InnoError>
where
R: Read + Seek,
{
Self::read_legacy(&mut src).or_else(|_| Self::read_from_resource(&mut src))
}
fn read_legacy<R>(mut reader: R) -> Result<Self, InnoError>
where
R: Read + Seek,
{
reader.seek(SeekFrom::Start(Self::EXE_MODE_OFFSET.into()))?;
let setup_loader_offset = SetupLoaderOffset::try_read(&mut reader)?;
reader.seek(SeekFrom::Start(setup_loader_offset.table_offset().into()))?;
Self::new(reader)
}
fn read_from_resource<R>(mut reader: R) -> Result<Self, InnoError>
where
R: Read + Seek,
{
reader.seek(SeekFrom::Start(0))?;
let dos_header = DosHeader::try_read_from_io(&mut reader)?;
reader.seek(SeekFrom::Start(dos_header.pe_pointer().into()))?;
let _signature = Signature::try_read_from_io(&mut reader)?;
let coff_header = reader.read_t::<CoffHeader>()?;
let optional_header = OptionalHeader::read_from(&mut reader)?;
let section_table = SectionTable::read_from(&mut reader, coff_header)?;
let resource_table = optional_header
.data_directories
.resource_table()
.ok_or_else(|| {
InnoError::Io(io::Error::new(
io::ErrorKind::InvalidData,
"No resource table found in executable",
))
})?;
let resource_directory_offset = resource_table.file_offset(§ion_table)?;
let section_reader = SectionReader::new(
&mut reader,
resource_directory_offset.into(),
resource_table.size().into(),
)?;
let mut resource_directory = ResourceDirectory::new(section_reader)?;
let _rc_data = resource_directory.find_rc_data()?;
let loader_directory_table =
resource_directory.find_directory_table_by_id(Self::TABLE_RESOURCE_ID)?;
let loader_directory = loader_directory_table.entries().next().ok_or_else(|| {
InnoError::Io(io::Error::new(
io::ErrorKind::InvalidData,
"No loader data entry found in loader directory",
))
})?;
let loader_data_entry_offset = loader_directory.file_offset(resource_directory_offset);
reader.seek(SeekFrom::Start(loader_data_entry_offset.into()))?;
let loader_data_entry = reader.read_t::<ImageResourceDataEntry>()?;
let setup_loader_offset =
section_table.to_file_offset(loader_data_entry.offset_to_data())?;
reader.seek(SeekFrom::Start(setup_loader_offset.into()))?;
Self::new(reader.take(loader_data_entry.size().into()))
}
fn new<R>(reader: R) -> Result<Self, InnoError>
where
R: Read,
{
let mut checksum = Crc32Reader::new(reader);
let signature = SetupLoaderSignature::read_from(&mut checksum)?;
let loader_version = signature
.version()
.ok_or(InnoError::UnknownLoaderSignature(signature.as_array()))?;
let revision = if loader_version >= (5, 1, 5) {
checksum.read_u32::<LE>()?
} else {
0
};
let minimum_setup_exe_size = if revision >= 2 {
checksum.read_i64::<LE>()?
} else {
checksum.read_u32::<LE>()?.into()
};
let exe_offset = if revision >= 2 {
checksum.read_i64::<LE>()?
} else {
checksum.read_u32::<LE>()?.into()
};
let exe_compressed_size = if loader_version >= (4, 1, 6) {
0
} else {
checksum.read_u32::<LE>()?
};
let exe_uncompressed_size = checksum.read_u32::<LE>()?;
let exe_checksum = if loader_version >= (4, 0, 3) {
Checksum::Crc32(checksum.read_u32::<LE>()?)
} else {
Checksum::Adler32(checksum.read_u32::<LE>()?)
};
let message_offset = if loader_version >= 4 {
0
} else {
checksum.get_mut().read_u32::<LE>()?
};
let header_offset = if revision >= 2 {
checksum.read_i64::<LE>()?
} else {
checksum.read_u32::<LE>()?.into()
};
let data_offset = if revision >= 2 {
checksum.read_i64::<LE>()?
} else {
checksum.read_u32::<LE>()?.into()
};
let reserved_padding = if revision >= 2 {
checksum.read_u32::<LE>()?
} else {
0
};
if loader_version >= (4, 0, 10) {
let expected_checksum = checksum.get_mut().read_u32::<LE>()?;
let actual_checksum = checksum.finalize();
if actual_checksum != expected_checksum {
return Err(InnoError::CrcChecksumMismatch {
location: "Setup Loader",
actual: actual_checksum,
expected: expected_checksum,
});
}
}
Ok(Self {
signature,
version: loader_version,
revision,
minimum_setup_exe_size,
exe_offset,
exe_compressed_size,
exe_uncompressed_size,
exe_checksum,
message_offset,
header_offset,
data_offset,
reserved_padding,
})
}
#[must_use]
#[inline]
pub const fn version(&self) -> InnoVersion {
self.version
}
#[doc(alias = "SetupLdrOffsetTableVersion")]
#[must_use]
#[inline]
pub const fn revision(&self) -> u32 {
self.revision
}
#[doc(alias = "ID")]
#[must_use]
#[inline]
pub const fn signature(&self) -> SetupLoaderSignature {
self.signature
}
#[doc(alias = "TotalSize")]
#[must_use]
#[inline]
pub const fn minimum_setup_exe_size(&self) -> i64 {
self.minimum_setup_exe_size
}
#[doc(alias = "OffsetEXE")]
#[must_use]
#[inline]
pub const fn exe_offset(&self) -> i64 {
self.exe_offset
}
#[doc(alias = "CompressedSizeEXE")]
#[must_use]
#[inline]
pub const fn exe_compressed_size(&self) -> u32 {
self.exe_compressed_size
}
#[doc(alias = "UncompressedSizeEXE")]
#[must_use]
#[inline]
pub const fn exe_uncompressed_size(&self) -> u32 {
self.exe_uncompressed_size
}
#[doc(alias = "CRCEXE")]
#[must_use]
#[inline]
pub const fn exe_checksum(&self) -> Checksum {
self.exe_checksum
}
#[must_use]
#[inline]
pub const fn message_offset(&self) -> u32 {
self.message_offset
}
#[doc(alias = "Offset0")]
#[must_use]
#[inline]
pub const fn header_offset(&self) -> i64 {
self.header_offset
}
#[doc(alias = "Offset1")]
#[must_use]
#[inline]
pub const fn data_offset(&self) -> i64 {
self.data_offset
}
}