1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Helper code for finding PCAT binaries.

#![warn(missing_docs)]
#![forbid(unsafe_code)]

use anyhow::Context;
use mesh::MeshPayload;
use std::fs::File;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;

mod resource_dll_parser;

#[derive(Debug, MeshPayload)]
/// The location of discovered ROM data.
pub struct RomFileLocation {
    /// The opened file containing the data.
    pub file: File,
    /// The starting byte offset of the data.
    pub start: u64,
    /// The length of the data.
    pub len: usize,
}

/// Returns path to the "Windows\System32" directory.
fn system32_path() -> String {
    // Other approaches could be using the WinDir env. variable,
    // or the unsafe `GetWindowsDirectoryW` function from the `windows` crate.
    let windows_dir = std::env::var("SystemRoot").unwrap_or(String::from(r"C:\Windows"));
    format!("{windows_dir}\\System32")
}

/// Attempt to automatically find and open the PCAT BIOS. Will always prefer
/// the more recently updated vmfirmwarepcat.dll over vmfirmware.dll.
pub fn find_pcat_bios(command_line_path: Option<&Path>) -> anyhow::Result<RomFileLocation> {
    const NAME: &str = "pcat_firmware";
    const BIOS_DESCRIPTOR: DllResourceDescriptor = DllResourceDescriptor::new(b"VMFW", 13500);
    const EXPECTED_BIOS_SIZE: usize = 256 * 1024;

    if let Some(p) = command_line_path {
        parse_rom_file(p, NAME, BIOS_DESCRIPTOR, EXPECTED_BIOS_SIZE)
    } else {
        let system32_path = system32_path();
        // Newer windows hosts have a specific file for pcat firmware.
        let default_pcat_firmware_file = format!(r"{system32_path}\vmfirmwarepcat.dll");

        let result = match parse_rom_file(
            &translate_path(Path::new(default_pcat_firmware_file.as_str()))?,
            NAME,
            BIOS_DESCRIPTOR,
            EXPECTED_BIOS_SIZE,
        ) {
            Ok(r) => r,
            Err(_) => {
                // Older hosts have a single file for both pcat and uefi.
                let legacy_pcat_firmware_file = format!(r"{system32_path}\vmfirmware.dll");

                parse_rom_file(
                    &translate_path(Path::new(legacy_pcat_firmware_file.as_str()))?,
                    NAME,
                    BIOS_DESCRIPTOR,
                    EXPECTED_BIOS_SIZE,
                )?
            }
        };
        Ok(result)
    }
}

/// Attempt to automatically find and open the SVGA video device BIOS from
/// vmemulateddevices.dll.
pub fn find_svga_bios(command_line_path: Option<&Path>) -> anyhow::Result<RomFileLocation> {
    const SVGA_BIOS_DESCRIPTOR: DllResourceDescriptor = DllResourceDescriptor::new(b"BIOS", 13501);
    const NAME: &str = "vga_firmware";
    const EXPECTED_SIZE: usize = 48 * 1024;

    if let Some(p) = command_line_path {
        parse_rom_file(p, NAME, SVGA_BIOS_DESCRIPTOR, EXPECTED_SIZE)
    } else {
        let system32_path = system32_path();
        // TODO: Also load the splash screen from the same dll?
        let default_svga_firmware_file = format!(r"{system32_path}\vmemulateddevices.dll");

        parse_rom_file(
            &translate_path(Path::new(default_svga_firmware_file.as_str()))?,
            NAME,
            SVGA_BIOS_DESCRIPTOR,
            EXPECTED_SIZE,
        )
    }
}

/// Translate a Windows path to an OS-appropriate path.
fn translate_path(path: &Path) -> anyhow::Result<PathBuf> {
    let file_path = if cfg!(windows) {
        path.into()
    } else if cfg!(target_os = "linux") {
        // WSL
        let output = Command::new("wslpath")
            .arg(path)
            .output()
            .context("Failed to translate path to windows. Are you not on WSL?")?;

        String::from_utf8_lossy(&output.stdout).trim().into()
    } else {
        anyhow::bail!("No path specified for firmware and no default is configured for this OS.")
    };

    Ok(file_path)
}

/// Reads the ROM data from the given file and returns it.
/// Autodetects if the file is a dll or not, and attempts to parse appropriately.
fn parse_rom_file(
    file_path: &Path,
    firmware_name: &str,
    descriptor: DllResourceDescriptor,
    expected_len: usize,
) -> anyhow::Result<RomFileLocation> {
    tracing::debug!(
        ?file_path,
        ?firmware_name,
        "Attempting to load firmware file."
    );

    let file = fs_err::File::open(file_path)?;

    let (start, len) = if let Some(maybe_resource) =
        resource_dll_parser::try_find_resource_from_dll(&file, &descriptor)?
    {
        maybe_resource
    } else {
        (0, file.metadata()?.len() as usize)
    };

    if len != expected_len {
        tracing::warn!(
            firmware_name,
            len,
            expected_len,
            "ROM data length does not match expected length, trying anyways",
        );
    }

    Ok(RomFileLocation {
        file: file.into(),
        start,
        len,
    })
}

pub(crate) struct DllResourceDescriptor {
    /// 4 characters encoded in LE UTF-16
    resource_type: [u8; 8],
    id: u32,
}

impl DllResourceDescriptor {
    const fn new(resource_type: &[u8; 4], id: u32) -> Self {
        Self {
            id,
            // Convert to LE UTF-16, only support ASCII names today
            resource_type: [
                resource_type[0],
                0,
                resource_type[1],
                0,
                resource_type[2],
                0,
                resource_type[3],
                0,
            ],
        }
    }
}