use std::collections::HashSet;
use crate::{
error::Error,
records::windows::WindowsVersionRange,
util::{encoding::read_setup_string, read::Reader},
version::Version,
};
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ComponentFlag {
Fixed,
Restart,
DisableNoUninstallWarning,
Exclusive,
DontInheritCheck,
}
#[derive(Clone, Debug)]
pub struct ComponentEntry {
pub name: String,
pub description: String,
pub types: String,
pub languages: String,
pub check: String,
pub extra_disk_space_required: i64,
pub level: i32,
pub used: bool,
pub winver: WindowsVersionRange,
pub flags: HashSet<ComponentFlag>,
pub options_raw: u8,
pub size: u64,
}
impl ComponentEntry {
pub(crate) fn read(reader: &mut Reader<'_>, version: &Version) -> Result<Self, Error> {
let name = read_setup_string(reader, version, "Component.Name")?;
let description = read_setup_string(reader, version, "Component.Description")?;
let types = read_setup_string(reader, version, "Component.Types")?;
let languages = if version.at_least(4, 0, 1) {
read_setup_string(reader, version, "Component.Languages")?
} else {
String::new()
};
let check = if version.at_least(4, 0, 0) || (version.is_isx() && version.at_least(1, 3, 24))
{
read_setup_string(reader, version, "Component.Check")?
} else {
String::new()
};
let extra_disk_space_required = if version.at_least(4, 0, 0) {
reader.i64_le("Component.ExtraDiskSpaceRequired")?
} else {
i64::from(reader.i32_le("Component.ExtraDiskSpaceRequired")?)
};
let level = if version.at_least(6, 7, 0) {
i32::from(reader.u8("Component.Level")?)
} else if version.at_least(4, 0, 0) || (version.is_isx() && version.at_least(3, 0, 3)) {
reader.i32_le("Component.Level")?
} else {
0
};
let used = if version.at_least(4, 0, 0) || (version.is_isx() && version.at_least(3, 0, 4)) {
reader.u8("Component.Used")? != 0
} else {
true
};
let winver = WindowsVersionRange::read(reader, version)?;
let options_raw = reader.u8("Component.Options")?;
let flags = decode_component_flags(options_raw, version);
let size = if version.at_least(4, 0, 0) {
reader.u64_le("Component.Size")?
} else if version.at_least(2, 0, 0) || (version.is_isx() && version.at_least(1, 3, 24)) {
u64::from(reader.u32_le("Component.Size")?)
} else {
0
};
Ok(Self {
name,
description,
types,
languages,
check,
extra_disk_space_required,
level,
used,
winver,
flags,
options_raw,
size,
})
}
}
fn decode_component_flags(raw: u8, version: &Version) -> HashSet<ComponentFlag> {
let table: &[ComponentFlag] = if version.at_least(4, 2, 3) {
&[
ComponentFlag::Fixed,
ComponentFlag::Restart,
ComponentFlag::DisableNoUninstallWarning,
ComponentFlag::Exclusive,
ComponentFlag::DontInheritCheck,
]
} else if version.at_least(3, 0, 8) || (version.is_isx() && version.at_least_4(3, 0, 6, 1)) {
&[
ComponentFlag::Fixed,
ComponentFlag::Restart,
ComponentFlag::DisableNoUninstallWarning,
ComponentFlag::Exclusive,
]
} else {
&[
ComponentFlag::Fixed,
ComponentFlag::Restart,
ComponentFlag::DisableNoUninstallWarning,
]
};
super::decode_packed_flags(&[raw], table)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::version::{Version, VersionFlags};
fn v6_4() -> Version {
Version {
a: 6,
b: 4,
c: 0,
d: 0,
flags: VersionFlags::UNICODE,
raw_marker: [0u8; 64],
}
}
fn put_str(buf: &mut Vec<u8>, s: &str) {
let utf16: Vec<u16> = s.encode_utf16().collect();
let byte_len = u32::try_from(utf16.len() * 2).unwrap();
buf.extend_from_slice(&byte_len.to_le_bytes());
for u in utf16 {
buf.extend_from_slice(&u.to_le_bytes());
}
}
#[test]
fn parses_minimal_component() {
let v = v6_4();
let mut bytes = Vec::new();
put_str(&mut bytes, "core");
put_str(&mut bytes, "Core program files");
put_str(&mut bytes, ""); put_str(&mut bytes, ""); put_str(&mut bytes, ""); bytes.extend_from_slice(&0i64.to_le_bytes()); bytes.extend_from_slice(&1i32.to_le_bytes()); bytes.push(1); bytes.extend_from_slice(&[0u8; 20]); bytes.push(0b0_0001); bytes.extend_from_slice(&999u64.to_le_bytes());
let mut r = Reader::new(&bytes);
let c = ComponentEntry::read(&mut r, &v).unwrap();
assert_eq!(c.name, "core");
assert_eq!(c.level, 1);
assert!(c.used);
assert_eq!(c.size, 999);
assert!(c.flags.contains(&ComponentFlag::Fixed));
assert!(!c.flags.contains(&ComponentFlag::Exclusive));
assert_eq!(r.pos(), bytes.len());
}
}