use core::fmt;
use crate::{
decompress::{self, CompressionMode},
error::Error,
installer::NsisInstaller,
nsis::entry::{Entry, EntryIter},
opcode,
strings::NsisString,
};
pub fn hkey_name(root: i32) -> &'static str {
match root as u32 {
0x8000_0000 => "HKCR",
0x8000_0001 => "HKCU",
0x8000_0002 => "HKLM",
0x8000_0003 => "HKU",
0x8000_0005 => "HKCC",
_ => "UNKNOWN_HKEY",
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegValueType {
Str,
ExpandStr,
Bin,
Dword,
MultiStr,
Unknown(i32),
}
impl fmt::Display for RegValueType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RegValueType::Str => f.write_str("REG_SZ"),
RegValueType::ExpandStr => f.write_str("REG_EXPAND_SZ"),
RegValueType::Bin => f.write_str("REG_BINARY"),
RegValueType::Dword => f.write_str("REG_DWORD"),
RegValueType::MultiStr => f.write_str("REG_MULTI_SZ"),
RegValueType::Unknown(n) => write!(f, "REG_UNKNOWN({n})"),
}
}
}
impl RegValueType {
fn from_params(type_param: i32, flags_param: i32) -> Self {
match type_param {
1 => {
if flags_param == 2 {
RegValueType::ExpandStr
} else {
RegValueType::Str
}
}
2 => RegValueType::ExpandStr,
3 => {
if flags_param == 7 {
RegValueType::MultiStr
} else {
RegValueType::Bin
}
}
4 => RegValueType::Dword,
other => RegValueType::Unknown(other),
}
}
}
pub struct PluginCall<'a> {
installer: &'a NsisInstaller<'a>,
entry: Entry<'a>,
}
impl<'a> PluginCall<'a> {
pub fn dll(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(0))
}
pub fn function(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(1))
}
pub fn is_plugin_call(&self) -> bool {
self.entry.offset(2) == 0
}
pub fn no_unload(&self) -> bool {
self.entry.offset(3) == 1
}
pub fn entry(&self) -> &Entry<'a> {
&self.entry
}
}
pub enum ExecCommand<'a> {
Exec(ExecOp<'a>),
ShellExec(ShellExecOp<'a>),
}
pub struct ExecOp<'a> {
installer: &'a NsisInstaller<'a>,
entry: Entry<'a>,
}
impl<'a> ExecOp<'a> {
pub fn command_line(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(0))
}
pub fn is_wait(&self) -> bool {
self.entry.offset(2) != 0
}
pub fn entry(&self) -> &Entry<'a> {
&self.entry
}
}
pub struct ShellExecOp<'a> {
installer: &'a NsisInstaller<'a>,
entry: Entry<'a>,
}
impl<'a> ShellExecOp<'a> {
pub fn verb(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(0))
}
pub fn file(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(1))
}
pub fn params(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(2))
}
pub fn entry(&self) -> &Entry<'a> {
&self.entry
}
}
pub enum RegistryOp<'a> {
Write(RegWrite<'a>),
Delete(RegDelete<'a>),
Read(RegRead<'a>),
}
pub struct RegWrite<'a> {
installer: &'a NsisInstaller<'a>,
entry: Entry<'a>,
}
impl<'a> RegWrite<'a> {
pub fn root(&self) -> i32 {
self.entry.offset(0)
}
pub fn root_name(&self) -> &'static str {
hkey_name(self.entry.offset(0))
}
pub fn key(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(1))
}
pub fn value_name(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(2))
}
pub fn data(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(3))
}
pub fn reg_type(&self) -> RegValueType {
RegValueType::from_params(self.entry.offset(4), self.entry.offset(5))
}
pub fn entry(&self) -> &Entry<'a> {
&self.entry
}
}
pub struct RegDelete<'a> {
installer: &'a NsisInstaller<'a>,
entry: Entry<'a>,
}
impl<'a> RegDelete<'a> {
pub fn root(&self) -> i32 {
self.entry.offset(1)
}
pub fn root_name(&self) -> &'static str {
hkey_name(self.entry.offset(1))
}
pub fn key(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(2))
}
pub fn value_name(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(3))
}
pub fn entry(&self) -> &Entry<'a> {
&self.entry
}
}
pub struct RegRead<'a> {
installer: &'a NsisInstaller<'a>,
entry: Entry<'a>,
}
impl<'a> RegRead<'a> {
pub fn root(&self) -> i32 {
self.entry.offset(1)
}
pub fn root_name(&self) -> &'static str {
hkey_name(self.entry.offset(1))
}
pub fn key(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(2))
}
pub fn value_name(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(3))
}
pub fn entry(&self) -> &Entry<'a> {
&self.entry
}
}
pub struct Shortcut<'a> {
installer: &'a NsisInstaller<'a>,
entry: Entry<'a>,
}
impl<'a> Shortcut<'a> {
pub fn link_path(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(0))
}
pub fn target(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(1))
}
pub fn parameters(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(2))
}
pub fn entry(&self) -> &Entry<'a> {
&self.entry
}
}
pub struct Uninstaller<'a> {
installer: &'a NsisInstaller<'a>,
entry: Entry<'a>,
}
impl<'a> Uninstaller<'a> {
pub fn path(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(0))
}
pub fn data_offset(&self) -> i32 {
self.entry.offset(1)
}
pub fn icon_size(&self) -> i32 {
self.entry.offset(2)
}
pub fn data(&self) -> &[u8] {
let source = self.data_source();
let Some(overlay_offset) = self.overlay_offset() else {
return &[];
};
let Some(slice) = source.get(overlay_offset..) else {
return &[];
};
if slice.len() < 4 {
return &[];
}
let Ok((_, size)) = decompress::read_length_prefix(slice) else {
return &[];
};
let Some(start) = overlay_offset.checked_add(4) else {
return &[];
};
let Some(end) = start.checked_add(size as usize) else {
return &[];
};
source.get(start..end).unwrap_or(&[])
}
pub fn decompress(&self) -> Result<Vec<u8>, Error> {
let source = self.data_source();
let overlay_offset = self.overlay_offset().ok_or(Error::TooShort {
expected: 4,
actual: 0,
context: "uninstaller icon data length prefix",
})?;
let slice = source.get(overlay_offset..).ok_or(Error::TooShort {
expected: overlay_offset.saturating_add(4),
actual: source.len(),
context: "uninstaller overlay length prefix",
})?;
if slice.len() < 4 {
return Err(Error::TooShort {
expected: overlay_offset.saturating_add(4),
actual: source.len(),
context: "uninstaller overlay length prefix",
});
}
let (is_compressed, size) =
decompress::read_length_prefix(slice).map_err(|_| Error::TooShort {
expected: 4,
actual: 0,
context: "uninstaller overlay length prefix",
})?;
let start = overlay_offset.checked_add(4).ok_or(Error::TooShort {
expected: usize::MAX,
actual: source.len(),
context: "uninstaller overlay overflow",
})?;
let end = start.checked_add(size as usize).ok_or(Error::TooShort {
expected: usize::MAX,
actual: source.len(),
context: "uninstaller overlay overflow",
})?;
let payload = source.get(start..end).ok_or(Error::TooShort {
expected: end,
actual: source.len(),
context: "uninstaller overlay payload",
})?;
let overlay_data = if !is_compressed {
payload.to_vec()
} else {
decompress::decompress_block(
payload,
self.installer.compression(),
decompress::DecodeLimit::Capped(self.installer.max_decompressed_size()),
)?
};
let pe_stub_size = self.installer.first_header_file_offset();
let file_data = self.installer.file_data();
let pe_stub = file_data
.get(..pe_stub_size.min(file_data.len()))
.unwrap_or(&[]);
let mut result = Vec::with_capacity(pe_stub.len().saturating_add(overlay_data.len()));
result.extend_from_slice(pe_stub);
result.extend_from_slice(&overlay_data);
Ok(result)
}
pub fn entry(&self) -> &Entry<'a> {
&self.entry
}
fn overlay_offset(&self) -> Option<usize> {
let source = self.data_source();
let offset = self.source_offset();
let slice = source.get(offset..)?;
if slice.len() < 4 {
return None;
}
let (_, icon_size) = decompress::read_length_prefix(slice).ok()?;
let after_icon = offset.checked_add(4)?.checked_add(icon_size as usize)?;
let after_with_prefix = after_icon.checked_add(4)?;
if after_with_prefix <= source.len() {
Some(after_icon)
} else {
None
}
}
fn data_source(&self) -> &[u8] {
if self.installer.compression_mode() == CompressionMode::Solid {
self.installer.solid_data()
} else {
self.installer.file_data()
}
}
fn source_offset(&self) -> usize {
if self.installer.compression_mode() == CompressionMode::Solid {
self.data_offset().max(0) as usize
} else {
self.installer
.data_block_offset()
.saturating_add(self.data_offset().max(0) as usize)
}
}
}
pub struct PluginCallIter<'a> {
installer: &'a NsisInstaller<'a>,
entries: EntryIter<'a>,
}
impl<'a> PluginCallIter<'a> {
pub(crate) fn new(installer: &'a NsisInstaller<'a>, entries: EntryIter<'a>) -> Self {
Self { installer, entries }
}
}
impl<'a> Iterator for PluginCallIter<'a> {
type Item = Result<PluginCall<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let entry = match self.entries.next()? {
Ok(e) => e,
Err(e) => return Some(Err(e)),
};
if self.installer.normalize_opcode(entry.which()) == opcode::EW_REGISTERDLL {
return Some(Ok(PluginCall {
installer: self.installer,
entry,
}));
}
}
}
}
pub struct ExecIter<'a> {
installer: &'a NsisInstaller<'a>,
entries: EntryIter<'a>,
}
impl<'a> ExecIter<'a> {
pub(crate) fn new(installer: &'a NsisInstaller<'a>, entries: EntryIter<'a>) -> Self {
Self { installer, entries }
}
}
impl<'a> Iterator for ExecIter<'a> {
type Item = Result<ExecCommand<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let entry = match self.entries.next()? {
Ok(e) => e,
Err(e) => return Some(Err(e)),
};
match self.installer.normalize_opcode(entry.which()) {
opcode::EW_EXECUTE => {
return Some(Ok(ExecCommand::Exec(ExecOp {
installer: self.installer,
entry,
})));
}
opcode::EW_SHELLEXEC => {
return Some(Ok(ExecCommand::ShellExec(ShellExecOp {
installer: self.installer,
entry,
})));
}
_ => continue,
}
}
}
}
pub struct RegistryIter<'a> {
installer: &'a NsisInstaller<'a>,
entries: EntryIter<'a>,
}
impl<'a> RegistryIter<'a> {
pub(crate) fn new(installer: &'a NsisInstaller<'a>, entries: EntryIter<'a>) -> Self {
Self { installer, entries }
}
}
impl<'a> Iterator for RegistryIter<'a> {
type Item = Result<RegistryOp<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let entry = match self.entries.next()? {
Ok(e) => e,
Err(e) => return Some(Err(e)),
};
match self.installer.normalize_opcode(entry.which()) {
opcode::EW_WRITEREG => {
return Some(Ok(RegistryOp::Write(RegWrite {
installer: self.installer,
entry,
})));
}
opcode::EW_DELREG => {
return Some(Ok(RegistryOp::Delete(RegDelete {
installer: self.installer,
entry,
})));
}
opcode::EW_READREGSTR => {
return Some(Ok(RegistryOp::Read(RegRead {
installer: self.installer,
entry,
})));
}
_ => continue,
}
}
}
}
pub struct ShortcutIter<'a> {
installer: &'a NsisInstaller<'a>,
entries: EntryIter<'a>,
}
impl<'a> ShortcutIter<'a> {
pub(crate) fn new(installer: &'a NsisInstaller<'a>, entries: EntryIter<'a>) -> Self {
Self { installer, entries }
}
}
impl<'a> Iterator for ShortcutIter<'a> {
type Item = Result<Shortcut<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let entry = match self.entries.next()? {
Ok(e) => e,
Err(e) => return Some(Err(e)),
};
if self.installer.normalize_opcode(entry.which()) == opcode::EW_CREATESHORTCUT {
return Some(Ok(Shortcut {
installer: self.installer,
entry,
}));
}
}
}
}
pub struct UninstallerIter<'a> {
installer: &'a NsisInstaller<'a>,
entries: EntryIter<'a>,
}
impl<'a> UninstallerIter<'a> {
pub(crate) fn new(installer: &'a NsisInstaller<'a>, entries: EntryIter<'a>) -> Self {
Self { installer, entries }
}
}
impl<'a> Iterator for UninstallerIter<'a> {
type Item = Result<Uninstaller<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let entry = match self.entries.next()? {
Ok(e) => e,
Err(e) => return Some(Err(e)),
};
if self.installer.normalize_opcode(entry.which()) == opcode::EW_WRITEUNINSTALLER {
return Some(Ok(Uninstaller {
installer: self.installer,
entry,
}));
}
}
}
}