use std::io::Cursor;
use winreg_core::hive::Hive;
use winreg_core::key::{filetime_to_datetime, Key};
#[derive(Debug, Clone, serde::Serialize)]
pub struct ShellbagEntry {
pub path: String,
pub key_path: String,
pub last_written: Option<String>,
pub mru_order: Vec<String>,
}
const BAGMRU_PATHS: &[&str] = &[
"Software\\Microsoft\\Windows\\Shell\\BagMRU",
"Software\\Microsoft\\Windows\\ShellNoRoam\\BagMRU",
"Local Settings\\Software\\Microsoft\\Windows\\Shell\\BagMRU",
];
pub fn parse(hive: &Hive<Cursor<Vec<u8>>>) -> Vec<ShellbagEntry> {
let mut entries = Vec::new();
for &path in BAGMRU_PATHS {
if let Ok(Some(root)) = hive.open_key(path) {
walk_key(&root, path, &mut entries);
}
}
entries
}
fn walk_key(key: &Key<'_>, key_path: &str, entries: &mut Vec<ShellbagEntry>) {
let last_written = filetime_to_datetime(key.last_written_raw())
.map(|dt| dt.format("%Y-%m-%dT%H:%M:%SZ").to_string());
let mru_order = decode_mrulistex(key);
let path = build_slot_path(key);
entries.push(ShellbagEntry {
path,
key_path: key_path.to_string(),
last_written,
mru_order,
});
if let Ok(subkeys) = key.subkeys() {
for subkey in subkeys {
let child_path = format!("{}\\{}", key_path, subkey.name());
walk_key(&subkey, &child_path, entries);
}
}
}
fn decode_mrulistex(key: &Key<'_>) -> Vec<String> {
let val = match key.value("MRUListEx") {
Ok(Some(v)) => v,
_ => return Vec::new(),
};
let raw = match val.raw_data() {
Ok(d) => d,
Err(_) => return Vec::new(),
};
let mut order = Vec::new();
let mut i = 0;
while i + 4 <= raw.len() {
let slot = u32::from_le_bytes([raw[i], raw[i + 1], raw[i + 2], raw[i + 3]]);
if slot == 0xFFFF_FFFF {
break;
}
order.push(slot.to_string());
i += 4;
}
order
}
fn build_slot_path(key: &Key<'_>) -> String {
let values = match key.values() {
Ok(v) => v,
Err(_) => return String::new(),
};
let mut parts: Vec<String> = Vec::new();
for val in values {
let name = val.name();
if name.chars().all(|c| c.is_ascii_digit()) {
parts.push(decode_slot(&name, &val));
}
}
parts.join("; ")
}
fn decode_slot(slot: &str, val: &winreg_core::value::Value<'_>) -> String {
if let Ok(raw) = val.raw_data() {
let items = shellitem::parse_idlist(&raw);
let path = shellitem::reconstruct_path(&items);
if !path.is_empty() {
return path;
}
}
let size = val.data_size() as usize;
format!("BagMRU[slot={slot}, size={size} bytes]")
}