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 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(1))
}
pub fn file(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(2))
}
pub fn params(&self) -> Result<NsisString, Error> {
self.installer.read_string(self.entry.offset(3))
}
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(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 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 offset = self.source_offset();
let Some((_, size)) = self.length_prefix() else {
return &[];
};
let start = offset + 4;
let end = start + size as usize;
if end <= source.len() {
&source[start..end]
} else {
&[]
}
}
pub fn decompress(&self) -> Result<Vec<u8>, Error> {
let source = self.data_source();
let offset = self.source_offset();
let Some((is_compressed, size)) = self.length_prefix() else {
return Err(Error::TooShort {
expected: 4,
actual: 0,
context: "uninstaller data length prefix",
});
};
let start = offset + 4;
let end = start + size as usize;
if end > source.len() {
return Err(Error::TooShort {
expected: end,
actual: source.len(),
context: "uninstaller data payload",
});
}
let payload = &source[start..end];
if !is_compressed {
return Ok(payload.to_vec());
}
let max_output = (size as usize * 10).max(64 * 1024 * 1024);
decompress::decompress_block(
payload,
self.installer.compression(),
max_output,
Some(max_output),
)
}
pub fn entry(&self) -> &Entry<'a> {
&self.entry
}
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() + self.data_offset().max(0) as usize
}
}
fn length_prefix(&self) -> Option<(bool, u32)> {
let source = self.data_source();
let offset = self.source_offset();
if offset + 4 > source.len() {
return None;
}
decompress::read_length_prefix(&source[offset..]).ok()
}
}
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 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 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 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 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 entry.which() == opcode::EW_WRITEUNINSTALLER {
return Some(Ok(Uninstaller {
installer: self.installer,
entry,
}));
}
}
}
}