Skip to content

Commit

Permalink
Get Product Code from NSIS installers
Browse files Browse the repository at this point in the history
  • Loading branch information
russellbanks committed Jan 30, 2025
1 parent dfb4594 commit a5324ca
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 32 deletions.
35 changes: 25 additions & 10 deletions src/installers/nsis/entry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ pub enum Entry {
ini_file: I32,
} = 49u32.to_le(),
DeleteReg {
reserved: I32,
root: RegRoot,
key_name: I32,
value_name: I32,
Expand Down Expand Up @@ -323,28 +324,28 @@ pub enum Entry {

impl Entry {
#[expect(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
pub fn update_vars(&self, state: &mut NsisState) {
pub fn execute(&self, state: &mut NsisState) {
match self {
Self::GetFullPathname { output, input } => {
state.user_variables.insert(
state.variables.insert(
output.get().unsigned_abs() as usize,
state.get_string(input.get()),
);
}
Self::SearchPath { output, filename } => {
state.user_variables.insert(
state.variables.insert(
output.get().unsigned_abs() as usize,
state.get_string(filename.get()),
);
}
Self::GetTempFilename { output, base_dir } => {
state.user_variables.insert(
state.variables.insert(
output.get().unsigned_abs() as usize,
state.get_string(base_dir.get()),
);
}
Self::StrLen { output, input } => {
state.user_variables.insert(
state.variables.insert(
output.get().unsigned_abs() as usize,
Cow::Owned(state.get_string(input.get()).len().to_string()),
);
Expand All @@ -370,7 +371,7 @@ impl Entry {

let start = u32::try_from(start).unwrap_or_default();
if start < result.len() as u32 {
state.user_variables.insert(
state.variables.insert(
variable.get().unsigned_abs() as usize,
match result {
Cow::Borrowed(borrowed) => {
Expand All @@ -385,7 +386,7 @@ impl Entry {
}
} else {
state
.user_variables
.variables
.remove(&(variable.get().unsigned_abs() as usize));
}
}
Expand All @@ -394,7 +395,7 @@ impl Entry {
string_with_env_variables,
..
} => {
state.user_variables.insert(
state.variables.insert(
output.get().unsigned_abs() as usize,
state.get_string(string_with_env_variables.get()),
);
Expand Down Expand Up @@ -422,7 +423,7 @@ impl Entry {
13 => ((input1.get() as u32).wrapping_shr(input2.get() as u32)) as i32,
_ => input1.get(),
};
state.user_variables.insert(
state.variables.insert(
output.get().unsigned_abs() as usize,
Cow::Owned(result.to_string()),
);
Expand All @@ -440,13 +441,27 @@ impl Entry {
} else if *push_pop == PushPop::Pop {
if let Some(variable) = state.stack.pop() {
state
.user_variables
.variables
.insert(variable_or_string.get().unsigned_abs() as usize, variable);
}
} else if *push_pop == PushPop::Push {
state.stack.push(state.get_string(variable_or_string.get()));
}
}
Self::WriteReg {
root,
key_name,
value_name,
value,
..
} => {
state.registry.set_value(
*root,
state.get_string(key_name.get()),
state.get_string(value_name.get()),
state.get_string(value.get()),
);
}
_ => {}
}
}
Expand Down
30 changes: 12 additions & 18 deletions src/installers/nsis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod entry;
mod first_header;
mod header;
mod language;
mod registry;
mod state;
mod strings;
mod version;
Expand Down Expand Up @@ -102,23 +103,9 @@ impl Nsis {
let mut architecture =
Option::from(architecture).filter(|&architecture| architecture != Architecture::X86);

let mut display_name = None;
let mut display_version = None;
let mut display_publisher = None;
for entry in entries {
entry.update_vars(&mut state);
if let Entry::WriteReg {
value_name, value, ..
} = entry
{
let value = state.get_string(value.get());
match &*state.get_string(value_name.get()) {
"DisplayName" => display_name = Some(value),
"DisplayVersion" => display_version = Some(value),
"Publisher" => display_publisher = Some(value),
_ => {}
}
} else if let Entry::ExtractFile { name, .. } = entry {
entry.execute(&mut state);
if let Entry::ExtractFile { name, .. } = entry {
let name = state.get_string(name.get());
let file_stem = Utf8Path::new(&name).file_stem();
// If there is an app-64 file, the app is x64.
Expand Down Expand Up @@ -214,6 +201,11 @@ impl Nsis {
.map(Architecture::from_machine)
});

let display_name = state.registry.remove_value("DisplayName");
let display_version = state.registry.remove_value("DisplayVersion");
let publisher = state.registry.remove_value("Publisher");
let product_code = state.registry.get_product_code();

Ok(Self {
installer: Installer {
locale: Language::from_code(state.language_table.id.get())
Expand All @@ -223,14 +215,16 @@ impl Nsis {
architecture: architecture.unwrap_or(Architecture::X86),
r#type: Some(InstallerType::Nullsoft),
scope: install_dir.as_deref().and_then(Scope::from_install_dir),
apps_and_features_entries: [&display_name, &display_version, &display_publisher]
product_code: product_code.map(str::to_owned),
apps_and_features_entries: [&display_name, &display_version, &publisher]
.iter()
.any(|option| option.is_some())
.then(|| {
vec![AppsAndFeaturesEntry {
display_name: display_name.map(Cow::into_owned),
publisher: display_publisher.map(Cow::into_owned),
publisher: publisher.map(Cow::into_owned),
display_version: display_version.as_deref().map(Version::new),
product_code: product_code.map(str::to_owned),
..AppsAndFeaturesEntry::default()
}]
}),
Expand Down
52 changes: 52 additions & 0 deletions src/installers/nsis/registry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use crate::installers::utils::registry::RegRoot;
use std::borrow::Cow;
use std::collections::HashMap;

type Values<'data> = HashMap<Cow<'data, str>, Cow<'data, str>>;

type Keys<'data> = HashMap<Cow<'data, str>, Values<'data>>;

// Registry root -< Key name -< Value name - Value
#[derive(Debug)]
pub struct Registry<'data>(HashMap<RegRoot, Keys<'data>>);

const CURRENT_VERSION_UNINSTALL: &str = r"Software\Microsoft\Windows\CurrentVersion\Uninstall";

impl<'data> Registry<'data> {
pub fn new() -> Self {
Self(HashMap::new())
}

pub fn get_product_code(&self) -> Option<&str> {
// Find the first Software\Microsoft\Windows\CurrentVersion\Uninstall\{PRODUCT_CODE} key
// under any root and extract the product code from it
self.0.values().find_map(|keys| {
keys.keys().find_map(|key| {
key.rsplit_once('\\').and_then(|(parent, product_code)| {
(parent == CURRENT_VERSION_UNINSTALL).then_some(product_code)
})
})
})
}

pub fn set_value(
&mut self,
root: RegRoot,
key: Cow<'data, str>,
name: Cow<'data, str>,
value: Cow<'data, str>,
) {
self.0
.entry(root)
.or_default()
.entry(key)
.or_default()
.insert(name, value);
}

pub fn remove_value(&mut self, name: &str) -> Option<Cow<'data, str>> {
self.0
.values_mut()
.find_map(|keys| keys.values_mut().find_map(|values| values.remove(name)))
}
}
9 changes: 6 additions & 3 deletions src/installers/nsis/state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::installers::nsis::header::block::{BlockHeaders, BlockType};
use crate::installers::nsis::header::Header;
use crate::installers::nsis::language::table::LanguageTable;
use crate::installers::nsis::registry::Registry;
use crate::installers::nsis::strings::code::NsCode;
use crate::installers::nsis::strings::shell::Shell;
use crate::installers::nsis::strings::var::NsVar;
Expand All @@ -17,7 +18,8 @@ pub struct NsisState<'data> {
pub str_block: &'data [u8],
pub language_table: &'data LanguageTable,
pub stack: Vec<Cow<'data, str>>,
pub user_variables: HashMap<usize, Cow<'data, str>>,
pub variables: HashMap<usize, Cow<'data, str>>,
pub registry: Registry<'data>,
pub version: NsisVersion,
}

Expand All @@ -32,7 +34,8 @@ impl<'data> NsisState<'data> {
str_block: BlockType::Strings.get(data, blocks),
language_table: LanguageTable::get_main(data, header, blocks)?,
stack: Vec::new(),
user_variables: HashMap::new(),
variables: HashMap::new(),
registry: Registry::new(),
version: NsisVersion::default(),
};

Expand Down Expand Up @@ -123,7 +126,7 @@ impl<'data> NsisState<'data> {
} else {
let index = usize::from(decode_number_from_char(special_char));
if current == u16::from(NsCode::Var.get(self.version)) {
NsVar::resolve(&mut buf, index, &self.user_variables, self.version);
NsVar::resolve(&mut buf, index, &self.variables, self.version);
} else if current == u16::from(NsCode::Lang.get(self.version)) {
buf.push_str(
&self.get_string(self.language_table.string_offsets[index].get()),
Expand Down
4 changes: 3 additions & 1 deletion src/installers/utils/registry.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use zerocopy::{Immutable, KnownLayout, TryFromBytes};

#[expect(dead_code)]
#[derive(Debug, Default, PartialEq, Eq, TryFromBytes, KnownLayout, Immutable)]
#[derive(
Copy, Clone, Debug, Default, Hash, PartialEq, Eq, TryFromBytes, KnownLayout, Immutable,
)]
#[repr(u32)]
pub enum RegRoot {
#[default]
Expand Down

0 comments on commit a5324ca

Please sign in to comment.