use alloc::{borrow::ToOwned, format, string::String, vec::Vec};
use log::warn;
use nt_hive::{Hive, KeyNode};
use thiserror::Error;
use uefi::{CStr16, Handle, Status, cstr16};
use crate::{
BootResult,
config::{
Config,
builder::ConfigBuilder,
parsers::{ConfigParser, Parsers},
},
system::{
fs::{FsError, UefiFileSystem},
helper::get_path_cstr,
},
};
const WIN_PREFIX: &CStr16 = cstr16!("\\EFI\\Microsoft\\Boot");
const WIN_SUFFIX: &str = ".efi";
const DISPLAYORDER_PATH: &str =
"Objects\\{9dea862c-5cdd-4e70-acc1-f32b344d4795}\\Elements\\24000001";
#[derive(Error, Debug)]
pub enum WinError {
#[error("Hive Parse Error: {0}")]
Hive(#[from] nt_hive::NtHiveError),
#[error("BCD missing key: \"{0}\"")]
BcdMissingKey(&'static str),
#[error("BCD missing Element value in key: \"{0}\"")]
BcdMissingElement(&'static str),
}
pub struct WinConfig {
title: String,
}
impl WinConfig {
pub fn new(content: &[u8]) -> Result<Self, WinError> {
let mut config = Self::default();
let hive = Hive::new(content)?;
let root_key_node = hive.root_key_node()?;
let displayorder =
Self::get_values_of_key(DISPLAYORDER_PATH, "displayorder", &root_key_node)?;
let displayorder_len = displayorder.len();
if let Some(guid) = displayorder.into_iter().next()
&& displayorder_len == 1
{
let path = format!("Objects\\{guid}\\Elements\\12000004");
let description = Self::get_value_of_key(&path, "description", &root_key_node)?;
config.title = description;
}
Ok(config)
}
fn get_value_of_key(
path: &str,
key_name: &'static str,
root_key_node: &KeyNode<'_, &[u8]>,
) -> Result<String, WinError> {
let key = root_key_node
.subpath(path)
.ok_or(WinError::BcdMissingKey(key_name))??;
let value = key
.value("Element")
.ok_or(WinError::BcdMissingElement(key_name))??
.string_data()?;
Ok(value)
}
fn get_values_of_key(
path: &str,
key_name: &'static str,
root_key_node: &KeyNode<'_, &[u8]>,
) -> Result<Vec<String>, WinError> {
let key = root_key_node
.subpath(path)
.ok_or(WinError::BcdMissingKey(key_name))??;
Ok(key
.value("Element")
.ok_or(WinError::BcdMissingElement(key_name))??
.multi_string_data()?
.filter_map(Result::ok)
.collect())
}
}
impl Default for WinConfig {
fn default() -> Self {
Self {
title: "Windows".to_owned(),
}
}
}
impl ConfigParser for WinConfig {
fn parse_configs(fs: &mut UefiFileSystem, handle: Handle, configs: &mut Vec<Config>) {
let Ok(path) = get_path_cstr(WIN_PREFIX, cstr16!("BCD")) else {
return;
};
if fs.exists(&path) {
match get_win_config(fs, handle) {
Ok(Some(config)) => configs.push(config),
Err(e) => warn!("{e}"),
_ => (),
}
}
}
}
fn get_win_config(fs: &mut UefiFileSystem, handle: Handle) -> BootResult<Option<Config>> {
let content = match fs.read(&get_path_cstr(WIN_PREFIX, cstr16!("BCD"))?) {
Ok(content) => content,
Err(FsError::OpenErr(Status::NOT_FOUND)) => return Ok(None),
Err(e) => return Err(e.into()),
};
let win_config = WinConfig::new(&content)?;
let efi_path = format!("{WIN_PREFIX}\\bootmgfw.efi");
let config = ConfigBuilder::new("bootmgfw.efi", WIN_SUFFIX)
.efi_path(efi_path)
.title(win_config.title)
.sort_key("windows")
.fs_handle(handle)
.origin(Parsers::Windows);
Ok(Some(config.build()))
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn doesnt_panic(x in any::<Vec<u8>>()) {
let _ = WinConfig::new(&x);
}
}
}