diff --git a/Cargo.lock b/Cargo.lock index b1f289ca..62f8aaea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,9 +159,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", + "js-sys", "num-integer", "num-traits", "serde", + "time", + "wasm-bindgen", "winapi", ] @@ -452,6 +455,7 @@ name = "framework_lib" version = "0.4.5" dependencies = [ "built", + "chrono", "clap", "clap-num", "clap-verbosity-flag", @@ -471,9 +475,11 @@ dependencies = [ "redox_hwio", "regex", "rusb", + "serde", "sha2", "smbios-lib", "spin 0.9.8", + "toml 0.8.22", "uefi", "uefi-services", "windows 0.59.0", @@ -1340,6 +1346,17 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi", + "winapi", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1530,6 +1547,12 @@ dependencies = [ "libc", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasm-bindgen" version = "0.2.84" diff --git a/README.md b/README.md index ab35c0b8..d3a6d39f 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ On UEFI and FreeBSD raw port I/O is used - on Linux this can also be used as a f - [x] HDMI Expansion Card (`--dp-hdmi-update`) - [x] DisplayPort Expansion Card (`--dp-hdmi-update`) - [ ] Audio Expansion Card + - [x] Get driver version for all devices (Windows only) (`--drivers`) ###### System Status diff --git a/framework_lib/Cargo.toml b/framework_lib/Cargo.toml index 4213b22c..ad901ef8 100644 --- a/framework_lib/Cargo.toml +++ b/framework_lib/Cargo.toml @@ -54,6 +54,9 @@ clap-verbosity-flag = { version = "2.2.1" } windows-version = "0.1.4" winreg = "0.55.0" nvml-wrapper = { version = "0.11.0", optional = true } +toml = "0.8" +serde = { version = "1.0", features = ["derive"] } +chrono = "0.4" [target.'cfg(unix)'.dependencies] libc = "0.2.155" diff --git a/framework_lib/src/baselines/framework12_intel_gen13.toml b/framework_lib/src/baselines/framework12_intel_gen13.toml new file mode 100644 index 00000000..60ff8117 --- /dev/null +++ b/framework_lib/src/baselines/framework12_intel_gen13.toml @@ -0,0 +1,26 @@ +# Driver baseline for Framework 12 Intel 13th Gen +# Last updated: 2026-01-14 +# NOTE: Placeholder versions - update with actual values + +[versions] +# Intel Drivers +"Intel Chipset" = "10.1.19899.8597" +"Intel Graphics" = "32.0.101.7026" +"Intel WiFi Driver" = "23.160.1.1" +"Intel Wireless Bluetooth" = "23.160.9.9" +"Intel Smart Sound Technology" = "10.29.0.10981" +"Intel Dynamic Tuning Technology" = "1.0.11700.1228" +"Intel Serial IO" = "30.100.2417.30" +"Intel Management Engine" = "2425.6.26.0" + +# Realtek Drivers +"Realtek Audio Driver" = "6.0.9859.1" + +# Framework +"Framework EC" = "0.0.0.6" +"Framework Sensors" = "0.0.7.0" + +# "Realtek Audio Console" 1.53.374.0 +# "ILITEK Touch Device" 10.0.19041.868 +# GNA 3.5.0.1578 +# Intel HSA 1.100.5688.0 diff --git a/framework_lib/src/baselines/framework13_amd_7080.toml b/framework_lib/src/baselines/framework13_amd_7080.toml new file mode 100644 index 00000000..1cf7c1b8 --- /dev/null +++ b/framework_lib/src/baselines/framework13_amd_7080.toml @@ -0,0 +1,22 @@ +# Driver baseline for Framework 13 AMD Ryzen 7080 Series +# Last updated: 2026-01-14 +# NOTE: Placeholder versions - update with actual values + +[versions] +# AMD Drivers +"AMD Chipset Driver" = "0.0.0.0" +"AMD Graphics Driver" = "0.0.0.0" + +# Realtek Drivers +"Realtek Audio Driver" = "0.0.0.0" +"Realtek Audio Console" = "0.0.0.0" + +# MediaTek Wireless +"RZ616 WiFi Driver" = "0.0.0.0" +"RZ616 Bluetooth Driver" = "0.0.0.0" + +# Goodix Fingerprint +"Goodix Fingerprint Driver" = "0.0.0.0" + +# Framework +"Framework EC" = "0.0.0.0" diff --git a/framework_lib/src/baselines/framework13_amd_ai300.toml b/framework_lib/src/baselines/framework13_amd_ai300.toml new file mode 100644 index 00000000..4b9e7957 --- /dev/null +++ b/framework_lib/src/baselines/framework13_amd_ai300.toml @@ -0,0 +1,29 @@ +# Driver baseline for Framework 13 AMD Ryzen AI 300 Series +# Last updated: 2026-01-14 +# NOTE: Placeholder versions - update with actual values + +[versions] +# AMD Drivers +"AMD Chipset Driver" = "7.06.02.123" +"AMD Graphics Driver" = "32.0.22021.1009" +"AMD MEP_ext Driver" = "32.2.0.1" +"AMD DRTM Driver" = "1.0.18.4" + +# Realtek Drivers +"Realtek Audio Driver" = "6.0.9859.1" +"Realtek Audio Console" = "1.53.374.0" +"Camera Driver" = "10.0.22000.20357" +"Realtek MEP Opt-in Driver" = "10.0.22000.10003" + +# MediaTek Wireless +"RZ717 WiFi Driver" = "5.6.0.4444" +"RZ717 Bluetooth Driver" = "1.1043.0.555" + +# Goodix Fingerprint +"Goodix Fingerprint Driver" = "3.12804.1.270" + +# Microsoft +"MEP Driver" = "2.0.16" + +# Framework +"Framework EC" = "0.0.0.6" diff --git a/framework_lib/src/baselines/framework16_amd_7080.toml b/framework_lib/src/baselines/framework16_amd_7080.toml new file mode 100644 index 00000000..06a860e2 --- /dev/null +++ b/framework_lib/src/baselines/framework16_amd_7080.toml @@ -0,0 +1,23 @@ +# Driver baseline for Framework 16 AMD Ryzen 7080 Series +# Last updated: 2026-01-14 +# NOTE: Placeholder versions - update with actual values + +[versions] +# AMD Drivers +"AMD Chipset Driver" = "0.0.0.0" +"AMD Graphics Driver" = "0.0.0.0" + +# Realtek Drivers +"Realtek Audio Driver" = "0.0.0.0" +"Realtek Audio Console" = "0.0.0.0" +"Camera Driver" = "0.0.0.0" + +# MediaTek Wireless +"RZ616 WiFi Driver" = "0.0.0.0" +"RZ616 Bluetooth Driver" = "0.0.0.0" + +# Goodix Fingerprint +"Goodix Fingerprint Driver" = "0.0.0.0" + +# Framework +"Framework EC" = "0.0.0.0" diff --git a/framework_lib/src/baselines/framework16_amd_ai300.toml b/framework_lib/src/baselines/framework16_amd_ai300.toml new file mode 100644 index 00000000..de1b70a5 --- /dev/null +++ b/framework_lib/src/baselines/framework16_amd_ai300.toml @@ -0,0 +1,29 @@ +# Driver baseline for Framework 16 AMD Ryzen AI 300 Series (Tulip) +# Last updated: 2026-01-14 + +[versions] +# AMD Drivers +"AMD Chipset Driver" = "7.06.02.123" +"AMD Graphics Driver" = "32.0.22021.1009" +"AMD DRTM Driver" = "1.0.18.4" + +# NVIDIA dGPU (optional) +"NVIDIA Graphics Driver" = "32.0.15.9144" + +# Realtek Drivers +"Realtek Audio Driver" = "6.0.9859.1" +"Realtek Audio Console" = "11.0.6000.374" +"Camera Driver" = "10.0.22000.20357" + +# MediaTek Wireless +"RZ717 WiFi Driver" = "5.6.0.4444" +"RZ717 Bluetooth Driver" = "1.1043.0.555" + +# Goodix Fingerprint +"Goodix Fingerprint Driver" = "3.12804.1.270" + +# Microsoft +"MEP Driver" = "2.0.16.0" + +# Framework +"Framework EC" = "0.0.0.6" diff --git a/framework_lib/src/baselines/framework_desktop_amd_ai_max300.toml b/framework_lib/src/baselines/framework_desktop_amd_ai_max300.toml new file mode 100644 index 00000000..db60d646 --- /dev/null +++ b/framework_lib/src/baselines/framework_desktop_amd_ai_max300.toml @@ -0,0 +1,14 @@ +# Driver baseline for Framework Desktop AMD Ryzen AI Max 300 +# Last updated: 2026-01-14 +# NOTE: Placeholder versions - update with actual values + +[versions] +# AMD Drivers +"AMD Chipset Driver" = "0.0.0.0" +"AMD Graphics Driver" = "0.0.0.0" + +# Realtek Drivers +"Realtek Audio Driver" = "0.0.0.0" + +# Framework +"Framework EC" = "0.0.0.0" diff --git a/framework_lib/src/baselines/intel_core_ultra1.toml b/framework_lib/src/baselines/intel_core_ultra1.toml new file mode 100644 index 00000000..e66e4a6d --- /dev/null +++ b/framework_lib/src/baselines/intel_core_ultra1.toml @@ -0,0 +1,25 @@ +# Driver baseline for Framework 13 Intel Core Ultra Series 1 (MeteorLake) +# Last updated: 2026-01-14 +# NOTE: Placeholder versions - update with actual values + +[versions] +# Intel Drivers +"Intel Chipset Driver" = "0.0.0.0" +"Intel Graphics" = "0.0.0.0" +"Intel WiFi Driver" = "0.0.0.0" +"Intel Wireless Bluetooth" = "0.0.0.0" +"Intel Smart Sound Technology" = "0.0.0.0" +"Intel Dynamic Tuning Technology" = "0.0.0.0" +"Intel Serial IO" = "0.0.0.0" +"Intel Management Engine" = "0.0.0.0" +"Intel NPU" = "0.0.0.0" +"Intel PMT Driver" = "0.0.0.0" + +# Realtek Drivers +"Realtek Audio Driver" = "0.0.0.0" + +# Goodix Fingerprint +"Goodix Fingerprint Driver" = "0.0.0.0" + +# Framework +"Framework EC" = "0.0.0.0" diff --git a/framework_lib/src/baselines/intel_gen11.toml b/framework_lib/src/baselines/intel_gen11.toml new file mode 100644 index 00000000..b424cac4 --- /dev/null +++ b/framework_lib/src/baselines/intel_gen11.toml @@ -0,0 +1,24 @@ +# Driver baseline for Framework 13 Intel 11th Gen (TigerLake) +# Last updated: 2026-01-14 +# NOTE: Placeholder versions - update with actual values + +[versions] +# Intel Drivers +"Intel Chipset Driver" = "0.0.0.0" +"Intel Graphics" = "0.0.0.0" +"Intel WiFi Driver" = "0.0.0.0" +"Intel Wireless Bluetooth" = "0.0.0.0" +"Intel Smart Sound Technology" = "0.0.0.0" +"Intel Dynamic Tuning Technology" = "0.0.0.0" +"Intel Serial IO" = "0.0.0.0" +"Intel Management Engine" = "0.0.0.0" +"Intel GNA Scoring Accelerator" = "0.0.0.0" + +# Realtek Drivers +"Realtek Audio Driver" = "0.0.0.0" + +# Goodix Fingerprint +"Goodix Fingerprint Driver" = "0.0.0.0" + +# Framework +"Framework EC" = "0.0.0.0" diff --git a/framework_lib/src/baselines/intel_gen12.toml b/framework_lib/src/baselines/intel_gen12.toml new file mode 100644 index 00000000..78a6b0b5 --- /dev/null +++ b/framework_lib/src/baselines/intel_gen12.toml @@ -0,0 +1,24 @@ +# Driver baseline for Framework 13 Intel 12th Gen (AlderLake) +# Last updated: 2026-01-14 +# NOTE: Placeholder versions - update with actual values + +[versions] +# Intel Drivers +"Intel Chipset Driver" = "0.0.0.0" +"Intel Graphics" = "0.0.0.0" +"Intel WiFi Driver" = "0.0.0.0" +"Intel Wireless Bluetooth" = "0.0.0.0" +"Intel Smart Sound Technology" = "0.0.0.0" +"Intel Dynamic Tuning Technology" = "0.0.0.0" +"Intel Serial IO" = "0.0.0.0" +"Intel Management Engine" = "0.0.0.0" +"Intel GNA Scoring Accelerator" = "0.0.0.0" + +# Realtek Drivers +"Realtek Audio Driver" = "0.0.0.0" + +# Goodix Fingerprint +"Goodix Fingerprint Driver" = "0.0.0.0" + +# Framework +"Framework EC" = "0.0.0.0" diff --git a/framework_lib/src/baselines/intel_gen13.toml b/framework_lib/src/baselines/intel_gen13.toml new file mode 100644 index 00000000..e19afadb --- /dev/null +++ b/framework_lib/src/baselines/intel_gen13.toml @@ -0,0 +1,25 @@ +# Driver baseline for Framework 13 Intel 13th Gen (RaptorLake) +# Last updated: 2026-01-14 +# NOTE: Placeholder versions - update with actual values + +[versions] +# Intel Drivers +"Intel Chipset Driver" = "0.0.0.0" +"Intel Graphics" = "0.0.0.0" +"Intel WiFi Driver" = "0.0.0.0" +"Intel Wireless Bluetooth" = "0.0.0.0" +"Intel Smart Sound Technology" = "0.0.0.0" +"Intel Dynamic Tuning Technology" = "0.0.0.0" +"Intel Serial IO" = "0.0.0.0" +"Intel Management Engine" = "0.0.0.0" +"Intel GNA Scoring Accelerator" = "0.0.0.0" +"Intel PMT Driver" = "0.0.0.0" + +# Realtek Drivers +"Realtek Audio Driver" = "0.0.0.0" + +# Goodix Fingerprint +"Goodix Fingerprint Driver" = "0.0.0.0" + +# Framework +"Framework EC" = "0.0.0.0" diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index f69e395e..3752a09b 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -227,6 +227,7 @@ impl Default for CrosEc { /// Find out which drivers are available /// /// Depending on the availability we choose the first one as default +#[allow(clippy::vec_init_then_push)] fn available_drivers() -> Vec { let mut drivers = vec![]; diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index a2727903..a730f83a 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -246,6 +246,14 @@ struct ClapCli { #[arg(long)] s0ix_counter: bool, + /// Display Windows Driver versions + #[arg(long)] + drivers: bool, + + /// Generate driver version baseline for current system + #[arg(long)] + drivers_baseline: bool, + /// Hash a file of arbitrary data #[arg(long)] hash: Option, @@ -466,6 +474,8 @@ pub fn parse(args: &[String]) -> Cli { ec_hib_delay: args.ec_hib_delay, uptimeinfo: args.uptimeinfo, s0ix_counter: args.s0ix_counter, + drivers: args.drivers, + drivers_baseline: args.drivers_baseline, hash: args.hash.map(|x| x.into_os_string().into_string().unwrap()), driver: args.driver, pd_addrs, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 98de02b5..057c0d1f 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -66,6 +66,8 @@ use crate::uefi::enable_page_break; #[cfg(feature = "rusb")] use crate::usbhub::check_usbhub_version; use crate::util::{self, Config, Platform, PlatformFamily}; +#[cfg(target_os = "windows")] +use crate::wmi; #[cfg(feature = "hidapi")] use hidapi::HidApi; use sha2::{Digest, Sha256, Sha384, Sha512}; @@ -217,6 +219,8 @@ pub struct Cli { pub uptimeinfo: bool, pub s0ix_counter: bool, pub hash: Option, + pub drivers: bool, + pub drivers_baseline: bool, pub pd_addrs: Option<(u16, u16, u16)>, pub pd_ports: Option<(u8, u8, u8)>, pub help: bool, @@ -1687,6 +1691,22 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } } else if let Some(ec_bin_path) = &args.flash_rw_ec { flash_ec(&ec, ec_bin_path, EcFlashType::Rw, args.dry_run); + } else if args.drivers { + #[cfg(target_os = "windows")] + { + let platform = smbios::get_platform(); + wmi::print_drivers_with_baseline(platform.as_ref()); + } + #[cfg(not(target_os = "windows"))] + println!("Driver Version only supported on Windows"); + } else if args.drivers_baseline { + #[cfg(target_os = "windows")] + { + let detected = wmi::collect_drivers(); + println!("{}", detected.to_toml()); + } + #[cfg(not(target_os = "windows"))] + println!("Driver baseline only supported on Windows"); } else if let Some(hash_file) = &args.hash { println!("Hashing file: {}", hash_file); #[cfg(feature = "uefi")] diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index cb2b4c5d..34df3764 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -80,6 +80,8 @@ pub fn parse(args: &[String]) -> Cli { ec_hib_delay: None, uptimeinfo: false, s0ix_counter: false, + drivers: false, + drivers_baseline: false, hash: None, // This is the only driver that works on UEFI driver: Some(CrosEcDriverType::Portio), diff --git a/framework_lib/src/drivers.toml b/framework_lib/src/drivers.toml new file mode 100644 index 00000000..b1d67483 --- /dev/null +++ b/framework_lib/src/drivers.toml @@ -0,0 +1,79 @@ +# Driver configuration for Framework systems +# This file defines which drivers to detect and their display names + +# PNP Drivers - detected via Win32_PnpSignedDriver +# Format: device_name = "display_alias" + +[pnp_drivers] +# Framework Drivers +"Framework EC" = "Framework EC" +"Framework Fingerprint Reader" = "Goodix Fingerprint Driver" +"Framework Sensors" = "Framework Sensors" +"Framework NE160QDM-NZ6" = "Framework Panel" + +# AMD Drivers (for AMD platforms) +"AMD Radeon(TM) 780M" = "AMD Graphics Driver" +"AMD Radeon(TM) 860M Graphics" = "AMD Graphics Driver" +"AMD Radeon(TM) RX 7700S" = "AMD Graphics Driver" +"AMD DRTM Boot Driver" = "AMD DRTM Driver" + +# NVIDIA Drivers (for dGPU) +"NVIDIA GeForce RTX 5070 Laptop GPU" = "NVIDIA Graphics Driver" + +# Realtek Drivers +"Realtek PC Camera Driver" = "Camera Driver" +"Realtek Hardware Support Application" = "Realtek Audio Console" + +# MediaTek Drivers +"RZ616 Bluetooth(R) Adapter" = "RZ616 Bluetooth Driver" +"RZ717 Bluetooth(R) Adapter" = "RZ717 Bluetooth Driver" + +# Microsoft Drivers +"Windows Camera Effects" = "MEP Driver" + +# Intel Drivers (for Intel platforms) +"Intel(R) LPC Controller - 519D" = "Intel Chipset" +"Intel(R) Wi-Fi 6E AX210 160MHz" = "Intel WiFi Driver" +"Intel(R) Wi-Fi 6E AX211 160MHz" = "Intel WiFi Driver" +"Intel(R) Platform Monitoring Technology (PMT) Driver" = "Intel PMT Driver" + +# Other +"ILITEK Wake On Touch Device" = "ILITEK Touch Device" + +# Products - detected via Win32_Product +# Format: product_name = "display_alias" + +[products] +"Intel(R) Chipset Device Software" = "Intel Chipset Driver" +"AMD_Chipset_Drivers" = "AMD Chipset Driver" + +# System Drivers - detected via Win32_SystemDriver +# Format: driver_name = "display_alias" + +[system_drivers] +# Intel Drivers (for Intel platforms) +ibtusb = "Intel Wireless Bluetooth" +IntcAzAudAddService = "Realtek Audio Driver" +IntcAudioBus = "Intel Smart Sound Technology" +ipf_acpi = "Intel Dynamic Tuning Technology" +npu = "Intel NPU" +igfxn = "Intel Graphics" +iaLPSS2_I2C_MTL = "Intel Serial IO" +MEIx64 = "Intel Management Engine" +IntelGNA = "Intel GNA Scoring Accelerator" + +# Framework Expansion Cards +rtux64w10 = "Framework Ethernet Expansion Card" +GeneStor = "Framework SD Expansion Card" + +# MediaTek WiFi Drivers +mtkwlex = "RZ616 WiFi Driver" +mtkwecx = "RZ717 WiFi Driver" + +# INF Drivers - detected via driver store INF file parsing +# These are boot-only drivers not visible via WMI at runtime +# Format: inf_name (without .inf) = "display_alias" + +[inf_drivers] +amddrtm = "AMD DRTM Driver" +rtextfw = "Realtek MEP Opt-in Driver" diff --git a/framework_lib/src/esrt/mod.rs b/framework_lib/src/esrt/mod.rs index 645c5669..0ccc8a39 100644 --- a/framework_lib/src/esrt/mod.rs +++ b/framework_lib/src/esrt/mod.rs @@ -438,7 +438,7 @@ pub fn get_esrt() -> Option { let short_guid = guid.trim_matches(|c| c == '{' || c == '}'); let esrt_entry = esrt.open_subkey(&guid).ok()?; let esrt = EsrtResourceEntry { - fw_class: GUID::parse(&short_guid).ok()?.into(), + fw_class: GUID::parse(short_guid).ok()?.into(), fw_type: esrt_entry.get_value("Type").ok()?, fw_version: esrt_entry.get_value("Version").ok()?, lowest_supported_fw_version: esrt_entry.get_value("LowestSupportedVersion").ok()?, diff --git a/framework_lib/src/lib.rs b/framework_lib/src/lib.rs index 1e76e724..c1d65819 100644 --- a/framework_lib/src/lib.rs +++ b/framework_lib/src/lib.rs @@ -49,6 +49,8 @@ pub mod smbios; #[cfg(feature = "uefi")] pub mod uefi; mod util; +#[cfg(target_os = "windows")] +pub mod wmi; pub mod built_info { // The file has been placed there by the build script. diff --git a/framework_lib/src/wmi.rs b/framework_lib/src/wmi.rs new file mode 100644 index 00000000..ca68fc10 --- /dev/null +++ b/framework_lib/src/wmi.rs @@ -0,0 +1,472 @@ +use crate::util::Platform; +use serde::Deserialize; +use std::collections::HashMap; +use std::fs; +use std::path::Path; +use wmi::*; + +/// Driver configuration loaded from TOML +#[derive(Debug, Deserialize)] +struct DriversConfig { + pnp_drivers: HashMap, + products: HashMap, + system_drivers: HashMap, + #[serde(default)] + inf_drivers: HashMap, +} + +/// Platform-specific baseline configuration +#[derive(Debug, Deserialize, Default)] +struct BaselineConfig { + versions: HashMap, +} + +/// Load driver configuration from embedded TOML +fn load_drivers_config() -> DriversConfig { + const CONFIG_STR: &str = include_str!("drivers.toml"); + toml::from_str(CONFIG_STR).expect("Failed to parse drivers.toml") +} + +/// Load baseline configuration for a specific platform +fn load_baseline_for_platform(platform: &Platform) -> BaselineConfig { + let config_str = match platform { + Platform::Framework12IntelGen13 => { + include_str!("baselines/framework12_intel_gen13.toml") + } + Platform::IntelGen11 => include_str!("baselines/intel_gen11.toml"), + Platform::IntelGen12 => include_str!("baselines/intel_gen12.toml"), + Platform::IntelGen13 => include_str!("baselines/intel_gen13.toml"), + Platform::IntelCoreUltra1 => include_str!("baselines/intel_core_ultra1.toml"), + Platform::Framework13Amd7080 => include_str!("baselines/framework13_amd_7080.toml"), + Platform::Framework13AmdAi300 => include_str!("baselines/framework13_amd_ai300.toml"), + Platform::Framework16Amd7080 => include_str!("baselines/framework16_amd_7080.toml"), + Platform::Framework16AmdAi300 => include_str!("baselines/framework16_amd_ai300.toml"), + Platform::FrameworkDesktopAmdAiMax300 => { + include_str!("baselines/framework_desktop_amd_ai_max300.toml") + } + Platform::GenericFramework(..) | Platform::UnknownSystem => { + return BaselineConfig::default(); + } + }; + toml::from_str(config_str).unwrap_or_default() +} + +const DRIVER_STORE_PATH: &str = r"C:\Windows\System32\DriverStore\FileRepository"; + +/// Find driver INF file in the driver store and extract version +fn find_inf_driver_version(inf_name: &str) -> Option { + let driver_store = Path::new(DRIVER_STORE_PATH); + if !driver_store.exists() { + return None; + } + + // Look for directories matching the INF name pattern (e.g., amddrtm.inf_amd64_*) + let pattern = format!("{}.inf_", inf_name); + let mut best_match: Option<(String, std::time::SystemTime)> = None; + + if let Ok(entries) = fs::read_dir(driver_store) { + for entry in entries.flatten() { + if let Ok(name) = entry.file_name().into_string() { + if name.starts_with(&pattern) { + // Only consider directories, not files like .ini + if let Ok(metadata) = entry.metadata() { + if !metadata.is_dir() { + continue; + } + // Get the modification time to find the newest version + if let Ok(modified) = metadata.modified() { + let should_update = match &best_match { + None => true, + Some((_, prev_time)) => modified > *prev_time, + }; + if should_update { + best_match = + Some((entry.path().to_string_lossy().to_string(), modified)); + } + } + } + } + } + } + } + + // If we found a matching directory, read the INF file and extract version + if let Some((dir_path, _)) = best_match { + let inf_file = Path::new(&dir_path).join(format!("{}.inf", inf_name)); + if inf_file.exists() { + return parse_inf_version(&inf_file); + } + } + + None +} + +/// Parse INF file and extract DriverVer version number +fn parse_inf_version(inf_path: &Path) -> Option { + // Try reading as UTF-8 first, then as UTF-16 LE (common for Windows INF files) + let content = match fs::read_to_string(inf_path) { + Ok(c) => c, + Err(_) => { + // Try reading as UTF-16 LE + let bytes = fs::read(inf_path).ok()?; + // Skip BOM if present and convert UTF-16 LE to String + let start = if bytes.len() >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE { + 2 + } else { + 0 + }; + let u16_chars: Vec = bytes[start..] + .chunks_exact(2) + .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) + .collect(); + String::from_utf16(&u16_chars).ok()? + } + }; + + // Look for DriverVer line, e.g.: DriverVer = 11/11/2024,1.0.18.4 + for line in content.lines() { + let trimmed = line.trim(); + if trimmed.to_lowercase().starts_with("driverver") { + // Extract version after the comma + if let Some(comma_pos) = trimmed.find(',') { + let version = trimmed[comma_pos + 1..].trim(); + return Some(version.to_string()); + } + } + } + + None +} + +/// Collected driver information for baseline updates +#[derive(Debug, Default)] +pub struct DetectedDrivers { + pub drivers: HashMap, +} + +impl DetectedDrivers { + /// Generate TOML baseline content from detected drivers + pub fn to_toml(&self) -> String { + let mut output = String::new(); + output.push_str("# Driver baseline - auto-generated\n"); + output.push_str("# Last updated: "); + output.push_str(&chrono::Local::now().format("%Y-%m-%d").to_string()); + output.push_str("\n\n[versions]\n"); + + let mut sorted: Vec<_> = self.drivers.iter().collect(); + sorted.sort_by_key(|(k, _)| k.as_str()); + + for (name, version) in sorted { + output.push_str(&format!("\"{}\" = \"{}\"\n", name, version)); + } + output + } +} + +// TODO: +// - [ ] Critical +// - [ ] Figure out why the CSME version is old +// - Looks like that depends on the CPU/CSME type +// - [x] Figure out how "Intel Chipset" shows up, I can't find any drivers +// - It's present in Win32_Product +// - [ ] Intel WiFi driver in Win32_SystemDriver is old, but in Win32_PnpSignedDriver is correct. Which one is used? +// - [x] Provide nice alias for driver names +// - [ ] Display drivers even when they're missing to make sure we find those that didn't install +// - [ ] Make more efficient by querying only the drivers we care about +// - [ ] Figure out why IGCC versios shows same as graphics driver +// - [ ] Figure out how to read HSA "Realtek Audio Console" version + +// Helpful commands +// Win32_SystemDriver +// get-wmiobject Win32_SystemDriver | select DisplayName,Name,@{n="version";e={(gi $_.pathname).VersionInfo.FileVersion}},PathName +// Get-WmiObject -query "SELECT * FROM CIM_Datafile WHERE Name = 'C:\Windows\system32\drivers\xinputhid.sys'" +// Get-WmiObject -query "SELECT * FROM Win32_PnPEntity WHERE ConfigManagerErrorCode <> 0" +// +// Win32_PnpSignedDriver +// get-wmiobject Win32_PnpSignedDriver | select Manufacturer,DeviceName,HardwareID,DriverVersion +// get-wmiobject Win32_PnpSignedDriver | where manufacturer -like 'Goodix' | select Manufacturer,DeviceName,HardwareID,DriverVersion +// get-wmiobject Win32_PnpSignedDriver | where manufacturer -like 'Intel' | select Manufacturer,DeviceName,HardwareID,DriverVersion +// +// Win32_Product +// Get-WmiObject -Class Win32_Product | select IdentifyingNumber, Name, Version +// +// Get-WmiObject -Class Win32_Product | select IdentifyingNumber, Name, Version | Format-Table | Out-String -width 9999 > product.txt +// get-wmiobject Win32_PnpSignedDriver | select Manufacturer,DeviceName,HardwareID,DriverVersion | Format-Table | Out-String -width 9999 > pnp.txt +// get-wmiobject Win32_SystemDriver | select DisplayName,Name,@{n="version";e={(gi $_.pathname).VersionInfo.FileVersion}},PathName | Format-Table | Out-String -width 9999 > system.txt + +pub fn print_yellow_bangs() { + println!("Devices with Yellow Bangs"); + debug!("Opening WMI"); + let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).unwrap(); + debug!("Querying WMI"); + let results: Vec> = wmi_con + .raw_query("SELECT * FROM Win32_PnPEntity WHERE ConfigManagerErrorCode <> 0") + .unwrap(); + + if results.is_empty() { + println!(" None"); + return; + } + + for bang in results.iter() { + // println!(" {:#?}", results); + // TODO: Unpack the Variant types + // TODO: Use serde + let description = if let Variant::String(s) = &bang["Description"] { + s.clone() + } else { + "".to_string() + }; + println!(" {}", description); + println!(" Compatible IDs: {:?}", &bang["CompatibleID"]); + println!(" Hardware IDs: {:?}", &bang["HardwareID"]); + println!(" DeviceID: {:?}", &bang["DeviceID"]); + println!(" PNPDeviceID: {:?}", &bang["PNPDeviceID"]); + println!( + " ConfigManagerErrorCode {:?}", + &bang["ConfigManagerErrorCode"] + ); + println!(" Status {:?}", &bang["Status"]); + // Other values that don't seem to have useful information + // ConfigManagerUserConfig: Bool (false) + // ErrorCleared: ? (Null) + // CreationClassName: String ("Win32_PnPEntity") + // Present: Bool (true) + // InstallDate: ? (Null) + // ErrorDescription: ? (Null) + // Caption: String ("Multimedia Audio Controller") + // LastErrorCode: ? (Null) + // Availability: ? (Null) + // PowerManagementCapabilities: Array ([]) + // PowerManagementSupported: ? (Null) + // PNPClass: ? (Null) + // StatusInfo: ? (Null) + } +} + +/// Print drivers with optional baseline comparison +pub fn print_drivers_with_baseline(platform: Option<&Platform>) { + print_yellow_bangs(); + + let config = load_drivers_config(); + let baseline = platform.map(load_baseline_for_platform).unwrap_or_default(); + + println!("Drivers"); + let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).unwrap(); + + // PNP Drivers + let results: Vec> = wmi_con + .raw_query( + "SELECT Manufacturer,DeviceName,HardwareID,DriverVersion FROM Win32_PnpSignedDriver", + ) + .unwrap(); + for val in results.iter() { + let device_name = if let Variant::String(s) = &val["DeviceName"] { + s.clone() + } else { + "".to_string() + }; + let version = if let Variant::String(s) = &val["DriverVersion"] { + s.clone() + } else { + "".to_string() + }; + + // Find matching driver entry + if let Some(alias) = config.pnp_drivers.get(&device_name) { + println!(" {}", alias); + print_version_with_baseline(&version, alias, &baseline); + } + } + + // Products + let results: Vec> = wmi_con + .raw_query("SELECT IdentifyingNumber, Name, Version FROM Win32_Product") + .unwrap(); + for val in results.iter() { + let name = if let Variant::String(s) = &val["Name"] { + s.clone() + } else { + "".to_string() + }; + let version = if let Variant::String(s) = &val["Version"] { + s.clone() + } else { + "".to_string() + }; + + // Find matching product entry + if let Some(alias) = config.products.get(&name) { + println!(" {}", alias); + print_version_with_baseline(&version, alias, &baseline); + } + } + + // System Drivers + let system_drivers = &config.system_drivers; + + let results: Vec> = wmi_con + .raw_query("SELECT DisplayName,Name,PathName FROM Win32_SystemDriver") + .unwrap(); + for val in results.iter() { + let display_name = if let Variant::String(s) = &val["DisplayName"] { + s.clone() + } else { + "".to_string() + }; + let name = if let Variant::String(s) = &val["Name"] { + s.clone() + } else { + "".to_string() + }; + let path_name = if let Variant::String(s) = &val["PathName"] { + s.clone() + } else { + "".to_string() + }; + + if !path_name.starts_with("C:") { + debug!("Skipping path_name: {:?}", path_name); + continue; + } + let query = format!( + "SELECT Version FROM CIM_Datafile WHERE Name = '{}'", + path_name.replace("\\", "\\\\") + ); + let results: Vec> = wmi_con.raw_query(query).unwrap(); + let version = if let Variant::String(s) = &results[0]["Version"] { + s.clone() + } else { + "".to_string() + }; + + if let Some(alias) = system_drivers.get(&name) { + println!(" {}", alias); + debug!(" Display: {}", display_name); + debug!(" Name: {}", name); + debug!(" Path: {}", path_name); + print_version_with_baseline(&version, alias, &baseline); + } + } + + // INF Drivers (boot-only drivers from driver store) + for (inf_name, alias) in &config.inf_drivers { + if let Some(version) = find_inf_driver_version(inf_name) { + println!(" {}", alias); + print_version_with_baseline(&version, alias, &baseline); + } + } +} + +/// Print version with baseline comparison +fn print_version_with_baseline(version: &str, alias: &str, baseline: &BaselineConfig) { + if let Some(expected) = baseline.versions.get(alias) { + if expected != "0.0.0.0" && version != expected { + println!(" Version: {} (expected: {})", version, expected); + } else { + println!(" Version: {}", version); + } + } else { + println!(" Version: {}", version); + } +} + +/// Print drivers without baseline comparison (backwards compatible) +pub fn print_drivers() { + print_drivers_with_baseline(None); +} + +/// Collect all detected drivers (for generating baselines) +pub fn collect_drivers() -> DetectedDrivers { + let config = load_drivers_config(); + let mut detected = DetectedDrivers::default(); + let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).unwrap(); + + // PNP Drivers + let results: Vec> = wmi_con + .raw_query( + "SELECT Manufacturer,DeviceName,HardwareID,DriverVersion FROM Win32_PnpSignedDriver", + ) + .unwrap(); + for val in results.iter() { + let device_name = if let Variant::String(s) = &val["DeviceName"] { + s.clone() + } else { + continue; + }; + let version = if let Variant::String(s) = &val["DriverVersion"] { + s.clone() + } else { + continue; + }; + + if let Some(alias) = config.pnp_drivers.get(&device_name) { + detected.drivers.insert(alias.clone(), version); + } + } + + // Products + let results: Vec> = wmi_con + .raw_query("SELECT IdentifyingNumber, Name, Version FROM Win32_Product") + .unwrap(); + for val in results.iter() { + let name = if let Variant::String(s) = &val["Name"] { + s.clone() + } else { + continue; + }; + let version = if let Variant::String(s) = &val["Version"] { + s.clone() + } else { + continue; + }; + + if let Some(alias) = config.products.get(&name) { + detected.drivers.insert(alias.clone(), version); + } + } + + // System Drivers + let system_drivers = &config.system_drivers; + let results: Vec> = wmi_con + .raw_query("SELECT DisplayName,Name,PathName FROM Win32_SystemDriver") + .unwrap(); + for val in results.iter() { + let name = if let Variant::String(s) = &val["Name"] { + s.clone() + } else { + continue; + }; + let path_name = if let Variant::String(s) = &val["PathName"] { + s.clone() + } else { + continue; + }; + + if !path_name.starts_with("C:") { + continue; + } + let query = format!( + "SELECT Version FROM CIM_Datafile WHERE Name = '{}'", + path_name.replace("\\", "\\\\") + ); + if let Ok(results) = wmi_con.raw_query::>(&query) { + if !results.is_empty() { + if let Variant::String(version) = &results[0]["Version"] { + if let Some(alias) = system_drivers.get(&name) { + detected.drivers.insert(alias.to_string(), version.clone()); + } + } + } + } + } + + // INF Drivers (boot-only drivers from driver store) + for (inf_name, alias) in &config.inf_drivers { + if let Some(version) = find_inf_driver_version(inf_name) { + detected.drivers.insert(alias.clone(), version); + } + } + + detected +}