///|
/// Size of one segment in a big value, as stored by Windows.
let max_segment_size : Int32 = 16344
///|
/// Kind of data stored in a registry value.
pub(all) enum HiveValueType {
Nil
Sz
ExpandSz
Binary
Dword
DwordBigEndian
Link
MultiSz
ResourceList
FullResourceDescriptor
ResourceRequirementsList
Qword
Unknown(UInt32)
}
///|
/// Maps the numeric type code stored in a vk record to a value type.
pub fn HiveValueType::from_code(code : UInt32) -> HiveValueType {
match code.reinterpret_as_int() {
0 => Nil
1 => Sz
2 => ExpandSz
3 => Binary
4 => Dword
5 => DwordBigEndian
6 => Link
7 => MultiSz
8 => ResourceList
9 => FullResourceDescriptor
10 => ResourceRequirementsList
11 => Qword
_ => Unknown(code)
}
}
///|
/// Registry type name such as REG_SZ, as used by regedit and .reg files.
pub fn HiveValueType::label(self : HiveValueType) -> String {
match self {
Nil => "REG_NONE"
Sz => "REG_SZ"
ExpandSz => "REG_EXPAND_SZ"
Binary => "REG_BINARY"
Dword => "REG_DWORD"
DwordBigEndian => "REG_DWORD_BIG_ENDIAN"
Link => "REG_LINK"
MultiSz => "REG_MULTI_SZ"
ResourceList => "REG_RESOURCE_LIST"
FullResourceDescriptor => "REG_FULL_RESOURCE_DESCRIPTOR"
ResourceRequirementsList => "REG_RESOURCE_REQUIREMENTS_LIST"
Qword => "REG_QWORD"
Unknown(code) => "REG_UNKNOWN(0x" + code.to_string(radix=16) + ")"
}
}
///|
/// A registry value (vk record) together with its resolved data.
///
/// inline_data is true when the four bytes of data_offset hold the data
/// itself, which Windows does for payloads of at most four bytes.
pub(all) struct HiveValue {
offset : UInt32
name : String
data_type : HiveValueType
data_size : UInt32
data_offset : UInt32
inline_data : Bool
data : Bytes
}
///|
/// Copies count bytes starting at start into a fresh byte sequence.
fn copy_range(bytes : Bytes, start : Int32, count : Int32) -> Bytes {
let out : Array[Byte] = []
let mut index = 0
while index < count {
out.push(bytes[start + index])
index = index + 1
}
Bytes::from_array(out)
}
///|
/// Reads the value list stored in the cell at offset.
///
/// A value list cell is a plain array of 32-bit vk offsets; it carries no
/// signature of its own.
pub fn read_value_list(
bytes : Bytes,
offset : UInt32,
) -> Result[Array[UInt32], RegfError] {
let cell = match read_cell(bytes, offset) {
Err(error) => return Err(error)
Ok(entry) => entry
}
let relative = cell.offset.reinterpret_as_int()
let count = cell.payload_size / 4
let start = hive_bins_base + relative + 4
let offsets : Array[UInt32] = []
let mut index = 0
while index < count {
offsets.push(read_u32_le(bytes, start + index * 4))
index = index + 1
}
Ok(offsets)
}
///|
/// Reads the data of a value, following inline payloads and big data records.
fn read_value_data(
bytes : Bytes,
value : HiveValue,
) -> Result[Bytes, RegfError] {
let raw = value.data_size.reinterpret_as_int()
let size = raw & 0x7FFFFFFF
if size == 0 {
return Ok(Bytes::new(0))
}
if raw < 0 {
let packed = value.data_offset.reinterpret_as_int()
let out : Array[Byte] = []
let mut index = 0
while index < size && index < 4 {
out.push(((packed >> (index * 8)) & 0xFF).to_byte())
index = index + 1
}
return Ok(Bytes::from_array(out))
}
if size > max_segment_size {
return read_big_value_data(bytes, value.data_offset, size)
}
let cell = match read_cell(bytes, value.data_offset) {
Err(error) => return Err(error)
Ok(entry) => entry
}
let relative = cell.offset.reinterpret_as_int()
if cell.payload_size < size {
return Err(BadValueData(relative))
}
Ok(copy_range(bytes, hive_bins_base + relative + 4, size))
}
///|
/// Reads data stored in a db big data record, concatenating its segments.
fn read_big_value_data(
bytes : Bytes,
offset : UInt32,
size : Int32,
) -> Result[Bytes, RegfError] {
let cell = match read_cell(bytes, offset) {
Err(error) => return Err(error)
Ok(entry) => entry
}
let relative = cell.offset.reinterpret_as_int()
if cell.payload_size < 8 {
return Err(BadValueData(relative))
}
let start = hive_bins_base + relative + 4
let first = bytes[start].to_int()
let second = bytes[start + 1].to_int()
if first != 0x64 || second != 0x62 {
return Err(BadValueData(relative))
}
let segments = read_u16_le(bytes, start + 2).reinterpret_as_int()
if 4 + segments * 4 > cell.payload_size {
return Err(BadValueData(relative))
}
let joined : Array[Byte] = []
let mut index = 0
while index < segments && joined.length() < size {
let segment = match
read_cell(bytes, read_u32_le(bytes, start + 4 + index * 4)) {
Err(error) => return Err(error)
Ok(entry) => entry
}
let segment_start = hive_bins_base + segment.offset.reinterpret_as_int() + 4
let remaining = size - joined.length()
let available = if segment.payload_size < remaining {
segment.payload_size
} else {
remaining
}
// Windows stores at most 16344 data bytes per segment; the rest of the cell is 8-byte padding.
let take = if available < max_segment_size {
available
} else {
max_segment_size
}
let mut inner = 0
while inner < take {
joined.push(bytes[segment_start + inner])
inner = inner + 1
}
index = index + 1
}
if joined.length() < size {
return Err(BadValueData(relative))
}
Ok(Bytes::from_array(joined))
}
///|
/// Parses the value record stored in the cell at offset and resolves its data.
pub fn parse_value(
bytes : Bytes,
offset : UInt32,
) -> Result[HiveValue, RegfError] {
let cell = match read_cell(bytes, offset) {
Err(error) => return Err(error)
Ok(entry) => entry
}
let relative = cell.offset.reinterpret_as_int()
if cell.payload_size < 20 {
return Err(BadValue(relative))
}
let start = hive_bins_base + relative + 4
let first = bytes[start].to_int()
let second = bytes[start + 1].to_int()
if first != 0x76 || second != 0x6B {
return Err(BadValue(relative))
}
let name_length = read_u16_le(bytes, start + 2).reinterpret_as_int()
if 20 + name_length > cell.payload_size {
return Err(BadValue(relative))
}
let flags = read_u16_le(bytes, start + 16).reinterpret_as_int()
let compressed = (flags & 0x0001) != 0
let name = if compressed {
decode_single_byte(bytes, start + 20, name_length)
} else {
decode_utf16le(bytes, start + 20, name_length / 2)
}
let data_size = read_u32_le(bytes, start + 4)
let data_offset = read_u32_le(bytes, start + 8)
let data_type = HiveValueType::from_code(read_u32_le(bytes, start + 12))
let inline_data = data_size.reinterpret_as_int() < 0
let provisional : HiveValue = {
offset,
name,
data_type,
data_size,
data_offset,
inline_data,
data: Bytes::new(0),
}
let data = match read_value_data(bytes, provisional) {
Err(error) => return Err(error)
Ok(resolved) => resolved
}
let value : HiveValue = {
offset,
name,
data_type,
data_size,
data_offset,
inline_data,
data,
}
Ok(value)
}
///|
/// Reads the vk offsets listed by a key, without parsing the values.
///
/// The lenient counterpart of list_values: callers that must survive hives
/// referencing volatile or freed cells can parse each offset on their own.
pub fn value_offsets(
bytes : Bytes,
key : HiveKeyNode,
) -> Result[Array[UInt32], RegfError] {
let offsets : Array[UInt32] = []
let count = key.value_count.reinterpret_as_int()
if count <= 0 {
return Ok(offsets)
}
if key.value_list_offset.reinterpret_as_int() == -1 {
return Ok(offsets)
}
match read_value_list(bytes, key.value_list_offset) {
Err(error) => Err(error)
Ok(all) => {
// The nk value count is authoritative: the list cell is 8-byte aligned, so a stale tail can hold extra offsets.
let limited : Array[UInt32] = []
let mut index = 0
while index < count && index < all.length() {
limited.push(all[index])
index = index + 1
}
Ok(limited)
}
}
}
///|
/// Reads the values of a key in stored order.
///
/// This is the strict view: any unreadable value fails the whole call.
pub fn list_values(
bytes : Bytes,
key : HiveKeyNode,
) -> Result[Array[HiveValue], RegfError] {
let values : Array[HiveValue] = []
let offsets = match value_offsets(bytes, key) {
Err(error) => return Err(error)
Ok(list) => list
}
let mut index = 0
while index < offsets.length() {
match parse_value(bytes, offsets[index]) {
Err(error) => return Err(error)
Ok(value) => values.push(value)
}
index = index + 1
}
Ok(values)
}
///|
/// Interprets the data as a 32-bit integer when it holds at least four bytes.
pub fn HiveValue::as_u32(self : HiveValue) -> UInt32? {
if self.data.length() < 4 {
return None
}
Some(read_u32_le(self.data, 0))
}
///|
/// Decodes the data of a string typed value; the trailing NUL is dropped.
pub fn HiveValue::as_string(self : HiveValue) -> String? {
match self.data_type {
Sz => Some(decode_utf16le(self.data, 0, self.data.length() / 2))
ExpandSz => Some(decode_utf16le(self.data, 0, self.data.length() / 2))
Link => Some(decode_utf16le(self.data, 0, self.data.length() / 2))
_ => None
}
}
///|
/// Splits the data of a REG_MULTI_SZ value into its NUL separated entries.
pub fn HiveValue::as_multi_string(self : HiveValue) -> Array[String]? {
match self.data_type {
MultiSz => {
let units = self.data.length() / 2
let out : Array[String] = []
let mut start = 0
while start < units {
let mut end = start
while end < units {
let low = self.data[end * 2].to_int()
let high = self.data[end * 2 + 1].to_int()
if (low | (high << 8)) == 0 {
break
}
end = end + 1
}
if end > start {
out.push(decode_utf16le(self.data, start * 2, end - start))
}
start = end + 1
}
Some(out)
}
_ => None
}
}