use std::ffi::c_void;
use std::ptr;
use std::mem;
use std::env;
use std::iter;
use std::thread;
use std::str::FromStr;
use std::time::Duration;
use std::io::Error as IoError;
use std::ffi::{OsStr, OsString, CString};
use std::os::windows::ffi::OsStrExt;
use color_eyre::eyre::Result;
use libc::{intptr_t, c_int, c_uint, c_long, c_char, FILE};
use log::{trace, debug, info, warn, error};
use bstr::ByteSlice;
use lazy_static::lazy_static;
use winreg::enums::*;
use winreg::RegKey;
use winapi::um::wincon::{FreeConsole, AttachConsole};
#[allow(unused_imports)]
use winapi::um::winbase::{STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE};
use winapi::um::consoleapi::AllocConsole;
use deelevate::{Token, PrivilegeLevel};
const _O_TEXT: c_int = 0x4000;
#[allow(dead_code)]
const STDIN_FILENO: c_int = 0;
#[allow(dead_code)]
const STDOUT_FILENO: c_int = 1;
#[allow(dead_code)]
const STDERR_FILENO: c_int = 2;
unsafe extern "C"
{
pub fn _open_osfhandle(osfhandle: intptr_t, flags: c_int) -> c_int;
pub fn _fdopen(fd: c_int, mode: *const c_char) -> *mut FILE;
pub fn _dup2(fd1: c_int, fd2: c_int) -> c_int;
pub fn __acrt_iob_func(_Ix: c_uint) -> *mut FILE;
}
#[allow(dead_code)]
pub fn stdinf() -> *mut FILE
{
unsafe { __acrt_iob_func(0) }
}
pub fn stdoutf() -> *mut FILE
{
unsafe { __acrt_iob_func(1) }
}
pub fn stderrf() -> *mut FILE
{
unsafe { __acrt_iob_func(2) }
}
#[allow(unused_macros)]
macro_rules! winapi_bool
{
($e:expr) => {
match $e {
0 => Err::<(), _>(IoError::last_os_error()),
_ => Ok(()),
}
}
}
#[allow(unused_macros)]
macro_rules! winapi_handle
{
($e:expr) => {
match $e {
winapi::um::handleapi::INVALID_HANDLE_VALUE => Err::<winapi::um::winnt::HANDLE, _>(IoError::last_os_error()),
handle => Ok(handle),
}
}
}
#[allow(unused_macros)]
macro_rules! winapi_neg
{
($e:expr) => {
match $e {
-1 => Err(IoError::last_os_error()),
other => Ok(other),
}
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct UcrtStdioStreamData
{
_ptr: *mut FILE,
_base: *mut i8,
_cnt: c_int,
_flags: c_long,
_file: c_long,
_charbuf: c_int,
_bufsiz: c_int,
_tmpfname: *mut i8,
_lock: *mut c_void,
}
pub fn restore_cstdio(parent_pid: u32) -> Result<()>
{
unsafe {
FreeConsole();
if let Err(_e) = winapi_bool!(AttachConsole(parent_pid)) {
AllocConsole();
}
}
let res = unsafe { libc::freopen(b"CONIN$\0".as_ptr() as *const i8, b"w\0".as_ptr() as *const i8, stdinf()) };
if res.is_null() {
Err::<(), _>(IoError::last_os_error())
.expect("Failed to resynchronize stdin");
}
let res = unsafe { libc::freopen(b"CONOUT$\0".as_ptr() as *const i8, b"wt\0".as_ptr() as *const i8, stdoutf()) };
if res.is_null() {
Err::<(), _>(IoError::last_os_error())
.expect("Failed to resynchronize stdout");
}
let out = stdoutf() as *mut UcrtStdioStreamData;
let err = stderrf() as *mut UcrtStdioStreamData;
unsafe {
*err = *out;
}
Ok(())
}
fn os_str_to_null_terminated_vec(s: &OsStr) -> Vec<u16>
{
s.encode_wide().chain(iter::once(0)).collect()
}
fn admin_install_drivers(devices: &mut [wdi::DeviceInfo])
{
for dev in devices.into_iter() {
let hwid_str = dev.hardware_id
.as_ref()
.expect("BMP WDI DeviceInfo always have hardware_id set")
.to_str_lossy()
.to_string();
println!("Installing for {}", &hwid_str);
thread::sleep(Duration::from_secs(1));
println!("Preparing driver for installation...");
wdi::prepare_driver(dev, "usb_driver", "usb_device.inf", &mut Default::default())
.unwrap();
println!("Driver prepared.");
println!("About to install driver. This may take multiple minutes and there will be NO PROGRESS REPORTING!");
println!("Installing driver...");
thread::sleep(Duration::from_secs(1));
wdi::install_driver(dev, "usb_driver", "usb_device.inf", &mut Default::default())
.unwrap();
println!("Driver successfully installed for {}", &hwid_str);
}
}
lazy_static! {
pub static ref APP_MODE_WDI_INFO: wdi::DeviceInfo = wdi::DeviceInfo {
vid: 0x1d50,
pid: 0x6018,
is_composite: true,
mi: 4,
desc: CString::new("Black Magic DFU (Interface 4)").unwrap().into_bytes_with_nul().to_vec(),
driver: None,
device_id: None,
hardware_id: Some(CString::new(r"USB\VID_1D50&PID_6018&REV_0100&MI_04").unwrap().to_bytes_with_nul().to_vec()),
compatible_id: Some(CString::new(r"USB\Class_fe&SubClass_01&Prot_01").unwrap().to_bytes_with_nul().to_vec()),
upper_filter: None,
driver_version: 0,
};
pub static ref DFU_MODE_WDI_INFO: wdi::DeviceInfo = wdi::DeviceInfo {
vid: 0x1d50,
pid: 0x6017,
is_composite: false,
mi: 0,
desc: CString::new("Black Magic Probe DFU").unwrap().to_bytes_with_nul().to_vec(),
driver: None,
device_id: None,
hardware_id: Some(CString::new(r"USB\VID_1D50&PID_6017&REV_0100").unwrap().to_bytes_with_nul().to_vec()),
compatible_id: Some(CString::new(r"USB\Cass_FE&SubClass_01&Prot_02").unwrap().to_bytes_with_nul().to_vec()),
upper_filter: None,
driver_version: 0,
};
}
pub fn hwid_bound_to_driver(hardware_id: &str, enumerator: &str) -> Result<Vec<String>>
{
debug!("Checking what drivers device {} under enumerator {} is bound to", hardware_id, enumerator);
let mut driver_names: Vec<String> = Vec::new();
let hwid_lower = hardware_id.to_lowercase();
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
let mut driver_db_subkey_name = String::from(r"SYSTEM\DriverDatabase\DeviceIds\");
driver_db_subkey_name.push_str(enumerator);
trace!(r"Opening HKLM:\{}", &driver_db_subkey_name);
let driver_db_for_enum = hklm.open_subkey(&driver_db_subkey_name)
.map_err(|e| {
error!("Error opening USB driver database in registry: {}", &e);
e
})?;
for dev_key_name in driver_db_for_enum.enum_keys() {
let dev_key_name: String = dev_key_name
.map_err(|e| {
warn!("Error enumerating registry subkeys of {:?}: {}", &driver_db_for_enum, &e);
e
})?;
let dev_key_lower = dev_key_name.to_lowercase();
if dev_key_lower == hwid_lower {
trace!(r"Opening HKLM:\{}\{}", &driver_db_subkey_name, &dev_key_name);
let dev_key = driver_db_for_enum.open_subkey(&dev_key_name)
.map_err(|e| {
warn!("Error opening know-to-exist subkey {:?} when checking bound drivers: {}", &dev_key_name, &e);
e
})?;
for driver_regval in dev_key.enum_values() {
let (driver_name, _driver_value) = driver_regval
.map_err(|e| {
warn!(
"Error enumerating values of key {:?} when checking bound drivers: {}",
&dev_key,
&e,
);
e
})?;
driver_names.push(driver_name);
}
}
}
Ok(driver_names)
}
pub fn ensure_access(parent_pid: Option<u32>, explicitly_requested: bool, force: bool)
{
debug!("Checking Windows registry driver database to determine if WinUSB is bound to BMP device nodes");
let mut devices_needing_driver: Vec<wdi::DeviceInfo> = Vec::with_capacity(2);
if force {
info!("Force installing WinUSB driver for app mode and DFU mode BMP devices...");
devices_needing_driver.push(APP_MODE_WDI_INFO.clone());
devices_needing_driver.push(DFU_MODE_WDI_INFO.clone());
} else {
match hwid_bound_to_driver("VID_1D50&PID_6018&MI_04", "USB") {
Ok(driver_names) if driver_names.len() == 0 => {
devices_needing_driver.push(APP_MODE_WDI_INFO.clone());
info!("Scheduling WinUSB driver installation for app mode BMP device...");
}
Err(_e) => {
devices_needing_driver.push(APP_MODE_WDI_INFO.clone());
info!("Scheduling WinUSB driver installation for app mode BMP device...");
}
Ok(driver_names) => {
trace!("App mode BMP bound to drivers: {:?}", driver_names);
},
}
match hwid_bound_to_driver("VID_1D50&PID_6017", "USB") {
Ok(driver_names) if driver_names.len() == 0 => {
devices_needing_driver.push(DFU_MODE_WDI_INFO.clone());
info!("Scheduling WinUSB driver installation for DFU mode BMP device...");
}
Err(_e) => {
devices_needing_driver.push(DFU_MODE_WDI_INFO.clone());
info!("Scheduling WinUSB driver installation for DFU mode BMP device...");
}
Ok(driver_names) => {
trace!("DFU mode BMP bound to drivers: {:?}", driver_names);
},
}
}
if devices_needing_driver.len() == 0 {
if explicitly_requested {
println!("Drivers are already installed for BMP devices; nothing to do.");
}
return;
}
println!("The WinUSB driver needs to be installed for the Black Magic Probe device before continuing. Standby...");
thread::sleep(Duration::from_secs(1));
let token = Token::with_current_process()
.expect("Unable to determine the current process's privilege level");
let level = token.privilege_level()
.expect("Unable to determine the current process's privilege level");
let need_to_elevate = match level {
PrivilegeLevel::NotPrivileged | PrivilegeLevel::HighIntegrityAdmin => true,
_ => {
false
},
};
if !need_to_elevate {
if let Some(pid) = parent_pid {
match restore_cstdio(pid) {
Ok(_) => (),
Err(_e) => {
todo!("Create a log file!");
}
}
}
admin_install_drivers(&mut devices_needing_driver);
println!("Successfully installed drivers for {} USB interfaces.", devices_needing_driver.len());
std::process::exit(0);
}
let mut args: Vec<OsString> = Vec::with_capacity(env::args_os().len() + 1);
args.extend(env::args_os().map(|s| s.to_owned()));
args.push(OsString::from_str("--windows-wdi-install-mode").unwrap());
use winapi::um::winbase;
use winapi::um::winuser;
use winapi::um::shellapi;
use winapi::um::shellapi::SHELLEXECUTEINFOW;
use winapi::um::shellapi::ShellExecuteExW;
use winapi::um::synchapi;
let verb: Vec<u16> = OsStr::new("runas").encode_wide().chain(iter::once(0)).collect();
let mut args: Vec<OsString> = env::args_os().map(|s| s.to_owned()).collect();
let _ = args.remove(0);
args.push(OsStr::new(&format!("--windows-wdi-install-mode={}", std::process::id())).to_owned());
let file = os_str_to_null_terminated_vec(env::current_exe().unwrap().as_os_str());
let parameters: OsString = args
.join(OsStr::new(" "));
let parameters = os_str_to_null_terminated_vec(¶meters);
let cwd = os_str_to_null_terminated_vec(
env::current_dir()
.expect("Unable to get current working directory")
.as_os_str()
);
let mut info = SHELLEXECUTEINFOW {
cbSize: mem::size_of::<SHELLEXECUTEINFOW>() as u32,
fMask: shellapi::SEE_MASK_NOCLOSEPROCESS,
hwnd: ptr::null_mut(),
lpVerb: verb.as_ptr(),
lpFile: file.as_ptr(),
lpParameters: parameters.as_ptr(),
lpDirectory: cwd.as_ptr(),
nShow: winuser::SW_HIDE,
hInstApp: ptr::null_mut(),
lpIDList: ptr::null_mut(),
lpClass: ptr::null_mut(),
hkeyClass: ptr::null_mut(),
hMonitor: ptr::null_mut(),
dwHotKey: 0,
hProcess: ptr::null_mut(),
};
let res = unsafe { ShellExecuteExW(&mut info) };
if res == winapi::shared::minwindef::FALSE {
Err::<(), _>(IoError::last_os_error())
.expect("Error calling ShellExecuteExW()");
}
let hproc = info.hProcess;
let ret = unsafe { synchapi::WaitForSingleObject(hproc, winbase::INFINITE) };
if ret == winbase::WAIT_FAILED {
Err::<(), _>(IoError::last_os_error())
.expect("Error calling WaitForSingleObject()");
}
std::thread::sleep(std::time::Duration::from_secs(5));
let mut exit_code = 0;
if unsafe { winapi::um::processthreadsapi::GetExitCodeProcess(hproc, &mut exit_code) } != 0 {
if exit_code != 0 {
error!("Elevated process exited with {}; driver installation probably failed", exit_code);
std::process::exit(exit_code as i32);
} else {
info!("Exiting parent process. Elevated process exited successfully.");
}
} else {
Err::<(), _>(IoError::last_os_error())
.expect("Error calling GetExitCodeProcess()");
}
println!(
"Driver installation should be complete. \
You may need to unplug the device and plug it back in before things work."
);
}