use std::{borrow::Cow, collections::HashSet};
use crate::{
error::Error,
records::{
item::ItemConditions,
windows::{Bitness, WindowsVersionRange},
},
util::{
encoding::{read_ansi_bytes, read_setup_string},
read::Reader,
},
version::Version,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum RegistryHive {
ClassesRoot,
CurrentUser,
LocalMachine,
Users,
PerformanceData,
CurrentConfig,
DynData,
Unknown(u32),
}
stable_name_enum!(RegistryHive, {
Self::ClassesRoot => "classes_root",
Self::CurrentUser => "current_user",
Self::LocalMachine => "local_machine",
Self::Users => "users",
Self::PerformanceData => "performance_data",
Self::CurrentConfig => "current_config",
Self::DynData => "dyn_data",
Self::Unknown(_) => "unknown",
});
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum RegistryValueType {
None,
String,
ExpandString,
DWord,
Binary,
MultiString,
QWord,
}
stable_name_enum!(RegistryValueType, {
Self::None => "none",
Self::String => "string",
Self::ExpandString => "expand_string",
Self::DWord => "dword",
Self::Binary => "binary",
Self::MultiString => "multi_string",
Self::QWord => "qword",
});
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum RegistryFlag {
CreateValueIfDoesntExist,
UninsDeleteValue,
UninsClearValue,
UninsDeleteEntireKey,
UninsDeleteEntireKeyIfEmpty,
PreserveStringType,
DeleteKey,
DeleteValue,
NoError,
DontCreateKey,
Bits32,
Bits64,
}
stable_flag_enum!(RegistryFlag, {
CreateValueIfDoesntExist => "create_value_if_doesnt_exist",
UninsDeleteValue => "unins_delete_value",
UninsClearValue => "unins_clear_value",
UninsDeleteEntireKey => "unins_delete_entire_key",
UninsDeleteEntireKeyIfEmpty => "unins_delete_entire_key_if_empty",
PreserveStringType => "preserve_string_type",
DeleteKey => "delete_key",
DeleteValue => "delete_value",
NoError => "no_error",
DontCreateKey => "dont_create_key",
Bits32 => "bits32",
Bits64 => "bits64",
});
#[derive(Clone, Debug)]
pub struct RegistryEntry {
pub subkey: String,
pub value_name: String,
pub value: Vec<u8>,
pub conditions: ItemConditions,
pub legacy_permissions: Vec<u8>,
pub winver: WindowsVersionRange,
pub hive: RegistryHive,
pub hive_raw: u32,
pub permission_index: i16,
pub value_type: Option<RegistryValueType>,
pub value_type_raw: u8,
pub bitness: Option<Bitness>,
pub bitness_raw: u8,
pub flags: HashSet<RegistryFlag>,
pub options_raw: Vec<u8>,
}
impl RegistryEntry {
#[must_use]
pub fn value_bytes(&self) -> &[u8] {
&self.value
}
#[must_use]
pub fn value_text(&self) -> Cow<'_, str> {
match self.value_type {
Some(RegistryValueType::DWord) => Cow::Owned(format_le_u32(&self.value)),
Some(RegistryValueType::QWord) => Cow::Owned(format_le_u64(&self.value)),
Some(RegistryValueType::String | RegistryValueType::ExpandString) => {
Cow::Owned(decode_utf16le_lossy(&self.value))
}
Some(RegistryValueType::MultiString) => {
Cow::Owned(decode_utf16le_lossy(&self.value).replace('\0', "\\0"))
}
Some(RegistryValueType::Binary) | Some(RegistryValueType::None) | None => {
Cow::Owned(format_hex(&self.value))
}
}
}
pub(crate) fn read(reader: &mut Reader<'_>, version: &Version) -> Result<Self, Error> {
let subkey = read_setup_string(reader, version, "Registry.Subkey")?;
let value_name = read_setup_string(reader, version, "Registry.ValueName")?;
let value = read_ansi_bytes(reader, "Registry.ValueData")?;
let conditions = ItemConditions::read(reader, version)?;
let legacy_permissions = if version.at_least(4, 0, 11) && !version.at_least(4, 1, 0) {
read_ansi_bytes(reader, "Registry.LegacyPermissions")?
} else {
Vec::new()
};
let winver = WindowsVersionRange::read(reader, version)?;
let raw_root = reader.u32_le("Registry.RootKey")?;
let hive_raw = raw_root & !0x8000_0000;
let hive = decode_hive(hive_raw);
let permission_index = if version.at_least(4, 1, 0) {
reader
.array::<2>("Registry.PermissionIndex")
.map(i16::from_le_bytes)?
} else {
-1
};
let value_type_raw = reader.u8("Registry.Typ")?;
let value_type = decode_value_type(value_type_raw, version);
let (bitness, bitness_raw) = if version.at_least_4(7, 0, 0, 3) {
let raw = reader.u8("Registry.Bitness")?;
(Bitness::decode(raw), raw)
} else {
(None, 0)
};
let table = registry_flag_table(version);
let raw = reader.set_bytes(table.len(), true, "Registry.Options")?;
let flags = super::decode_packed_flags(&raw, &table);
Ok(Self {
subkey,
value_name,
value,
conditions,
legacy_permissions,
winver,
hive,
hive_raw,
permission_index,
value_type,
value_type_raw,
bitness,
bitness_raw,
flags,
options_raw: raw,
})
}
}
fn decode_hive(raw: u32) -> RegistryHive {
match raw {
0 => RegistryHive::ClassesRoot,
1 => RegistryHive::CurrentUser,
2 => RegistryHive::LocalMachine,
3 => RegistryHive::Users,
4 => RegistryHive::PerformanceData,
5 => RegistryHive::CurrentConfig,
6 => RegistryHive::DynData,
n => RegistryHive::Unknown(n),
}
}
fn decode_value_type(b: u8, version: &Version) -> Option<RegistryValueType> {
let table: &[RegistryValueType] = if version.at_least(5, 2, 5) {
&[
RegistryValueType::None,
RegistryValueType::String,
RegistryValueType::ExpandString,
RegistryValueType::DWord,
RegistryValueType::Binary,
RegistryValueType::MultiString,
RegistryValueType::QWord,
]
} else {
&[
RegistryValueType::None,
RegistryValueType::String,
RegistryValueType::ExpandString,
RegistryValueType::DWord,
RegistryValueType::Binary,
RegistryValueType::MultiString,
]
};
table.get(usize::from(b)).copied()
}
fn format_le_u32(bytes: &[u8]) -> String {
let mut buf = [0_u8; 4];
for (idx, byte) in bytes.iter().take(4).enumerate() {
if let Some(slot) = buf.get_mut(idx) {
*slot = *byte;
}
}
u32::from_le_bytes(buf).to_string()
}
fn format_le_u64(bytes: &[u8]) -> String {
let mut buf = [0_u8; 8];
for (idx, byte) in bytes.iter().take(8).enumerate() {
if let Some(slot) = buf.get_mut(idx) {
*slot = *byte;
}
}
u64::from_le_bytes(buf).to_string()
}
fn decode_utf16le_lossy(bytes: &[u8]) -> String {
let units: Vec<u16> = bytes
.chunks_exact(2)
.filter_map(|chunk| <[u8; 2]>::try_from(chunk).ok().map(u16::from_le_bytes))
.collect();
String::from_utf16_lossy(&units)
}
fn format_hex(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(bytes.len().saturating_mul(2));
for byte in bytes {
if let Some(ch) = HEX.get(usize::from(byte >> 4)) {
out.push(char::from(*ch));
}
if let Some(ch) = HEX.get(usize::from(byte & 0x0f)) {
out.push(char::from(*ch));
}
}
out
}
fn registry_flag_table(version: &Version) -> Vec<RegistryFlag> {
let mut t = vec![
RegistryFlag::CreateValueIfDoesntExist,
RegistryFlag::UninsDeleteValue,
RegistryFlag::UninsClearValue,
RegistryFlag::UninsDeleteEntireKey,
RegistryFlag::UninsDeleteEntireKeyIfEmpty,
];
if version.at_least(1, 2, 6) {
t.push(RegistryFlag::PreserveStringType);
}
if version.at_least(1, 3, 9) {
t.push(RegistryFlag::DeleteKey);
t.push(RegistryFlag::DeleteValue);
}
if version.at_least(1, 3, 12) {
t.push(RegistryFlag::NoError);
}
if version.at_least(1, 3, 16) {
t.push(RegistryFlag::DontCreateKey);
}
if version.at_least(5, 1, 0) && !version.at_least_4(7, 0, 0, 3) {
t.push(RegistryFlag::Bits32);
t.push(RegistryFlag::Bits64);
}
t
}