use std::io::Cursor;
use winreg_core::hive::Hive;
use winreg_core::key::filetime_to_datetime;
const GUID_EXE: &str = "{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}";
const GUID_LNK: &str = "{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}";
const KNOWN_GUIDS: &[&str] = &[GUID_EXE, GUID_LNK];
const UA_DATA_SIZE: usize = 68;
#[derive(Debug, Clone, serde::Serialize)]
pub struct UserAssistEntry {
pub program: String,
pub run_count: u32,
pub focus_count: u32,
pub focus_duration_ms: u32,
pub last_run: Option<String>,
pub guid: String,
}
pub fn rot13_decode(s: &str) -> String {
s.chars()
.map(|c| match c {
'A'..='Z' => (b'A' + (c as u8 - b'A' + 13) % 26) as char,
'a'..='z' => (b'a' + (c as u8 - b'a' + 13) % 26) as char,
other => other,
})
.collect()
}
pub fn parse(hive: &Hive<Cursor<Vec<u8>>>) -> Vec<UserAssistEntry> {
let mut entries = Vec::new();
for &guid in KNOWN_GUIDS {
let count_path = format!(
"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{guid}\\Count"
);
let count_key = match hive.open_key(&count_path) {
Ok(Some(k)) => k,
_ => continue,
};
let values = match count_key.values() {
Ok(v) => v,
Err(_) => continue,
};
for val in values {
let raw = match val.raw_data() {
Ok(d) => d,
Err(_) => continue,
};
if raw.len() < UA_DATA_SIZE {
continue;
}
let run_count = winreg_core::bytes::le_u32(&raw[..], 4);
let focus_count = winreg_core::bytes::le_u32(&raw[..], 8);
let focus_duration_ms = winreg_core::bytes::le_u32(&raw[..], 12);
let filetime = winreg_core::bytes::le_u64(&raw[..], 60);
let last_run = filetime_to_datetime(filetime)
.map(|dt| dt.format("%Y-%m-%dT%H:%M:%SZ").to_string());
let program = rot13_decode(&val.name());
entries.push(UserAssistEntry {
program,
run_count,
focus_count,
focus_duration_ms,
last_run,
guid: guid.to_string(),
});
}
}
entries
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rot13_roundtrip_hello() {
let s = "Hello, World!";
assert_eq!(rot13_decode(&rot13_decode(s)), s);
}
#[test]
fn rot13_numbers_unchanged() {
assert_eq!(rot13_decode("12345"), "12345");
}
#[test]
fn rot13_special_chars_unchanged() {
assert_eq!(rot13_decode("\\:{}[]()"), "\\:{}[]()");
}
#[test]
fn rot13_uppercase() {
assert_eq!(rot13_decode("HELLO"), "URYYB");
}
#[test]
fn rot13_lowercase() {
assert_eq!(rot13_decode("hello"), "uryyb");
}
}