#![deny(unused_imports, unused_must_use)]
#![allow(clippy::missing_transmute_annotations)]
#![cfg_attr(not(test), no_std)]
#[cfg(not(windows))]
compile_error!("This crate only works for Windows targets.");
use core::ffi::{CStr, c_char, c_void};
use core::mem::{MaybeUninit, transmute};
use core::ptr::{null, null_mut};
use windows::Wdk::System::SystemInformation::{NtQuerySystemInformation, SYSTEM_INFORMATION_CLASS};
use windows::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress};
use windows::core::{s, w};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HostVersion {
sysname: &'static CStr,
release: &'static CStr,
}
impl HostVersion {
pub fn sysname(&self) -> &'static CStr {
self.sysname
}
pub fn sysname_str(&self) -> Option<&'static str> {
self.sysname.to_str().ok()
}
pub fn release(&self) -> &'static CStr {
self.release
}
pub fn release_str(&self) -> Option<&'static str> {
self.release.to_str().ok()
}
}
type GetVersionFn = unsafe extern "C" fn() -> *const c_char;
type GetBuildIdFn = unsafe extern "C" fn() -> *const c_char;
type GetHostVersionFn = unsafe extern "C" fn(*mut *const c_char, *mut *const c_char);
fn locate_wine_get_version() -> Option<GetVersionFn> {
unsafe {
let module = GetModuleHandleW(w!("ntdll.dll")).ok()?;
let proc = GetProcAddress(module, s!("wine_get_version"))?;
Some(transmute::<_, GetVersionFn>(proc))
}
}
pub fn is_wine() -> bool {
locate_wine_get_version().is_some()
}
pub fn get_version() -> Option<&'static CStr> {
Some(unsafe {
let proc = locate_wine_get_version()?;
CStr::from_ptr(proc())
})
}
pub fn get_version_str() -> Option<&'static str> {
get_version().and_then(|cstr| cstr.to_str().ok())
}
pub fn get_build() -> Option<&'static CStr> {
Some(unsafe {
let module = GetModuleHandleW(w!("ntdll.dll")).ok()?;
let proc = GetProcAddress(module, s!("wine_get_build_id"))?;
CStr::from_ptr(transmute::<_, GetBuildIdFn>(proc)())
})
}
pub fn get_build_str() -> Option<&'static str> {
get_build().and_then(|cstr| cstr.to_str().ok())
}
pub fn get_host_version() -> Option<HostVersion> {
let (sysname, release) = unsafe {
let module = GetModuleHandleW(w!("ntdll.dll")).ok()?;
let proc = GetProcAddress(module, s!("wine_get_host_version"))?;
let mut sysname_ptr: *const c_char = null();
let mut release_ptr: *const c_char = null();
transmute::<_, GetHostVersionFn>(proc)(&mut sysname_ptr, &mut release_ptr);
(CStr::from_ptr(sysname_ptr), CStr::from_ptr(release_ptr))
};
Some(HostVersion { sysname, release })
}
pub const SYSTEM_WINE_VERSION_INFORMATION: SYSTEM_INFORMATION_CLASS =
SYSTEM_INFORMATION_CLASS(1000i32);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Info {
buffer: [u8; Info::BUFFER_SIZE],
offsets: [usize; Info::SEGMENTS],
ends_at: usize,
}
impl Info {
const BUFFER_SIZE: usize = 256usize;
const SEGMENTS: usize = 4;
#[inline]
fn cstr_at<const N: usize>(&self) -> &CStr {
let start = self.offsets[N];
let end = self.offsets.get(N + 1).copied().unwrap_or(self.ends_at);
unsafe { CStr::from_bytes_with_nul_unchecked(&self.buffer[start..end]) }
}
pub fn into_inner(self) -> [u8; Info::BUFFER_SIZE] {
self.buffer
}
pub fn version(&self) -> &CStr {
self.cstr_at::<0>()
}
pub fn version_str(&self) -> Option<&str> {
self.version().to_str().ok()
}
pub fn build(&self) -> &CStr {
self.cstr_at::<1>()
}
pub fn build_str(&self) -> Option<&str> {
self.build().to_str().ok()
}
pub fn sysname(&self) -> &CStr {
self.cstr_at::<2>()
}
pub fn sysname_str(&self) -> Option<&str> {
self.sysname().to_str().ok()
}
pub fn release(&self) -> &CStr {
self.cstr_at::<3>()
}
pub fn release_str(&self) -> Option<&str> {
self.release().to_str().ok()
}
}
pub fn get_info() -> Option<Info> {
let mut buffer = [MaybeUninit::<u8>::uninit(); Info::BUFFER_SIZE];
unsafe {
NtQuerySystemInformation(
SYSTEM_WINE_VERSION_INFORMATION,
buffer.as_mut_ptr() as *mut c_void,
Info::BUFFER_SIZE as u32,
null_mut(),
)
.ok()
.ok()?;
}
let buffer: [u8; Info::BUFFER_SIZE] = unsafe { transmute(buffer) };
let mut offsets = [0usize; Info::SEGMENTS];
let mut ends_at = 0usize;
let iter = buffer.split_inclusive(|b| *b == 0).take(Info::SEGMENTS);
for (i, segment) in iter.enumerate() {
let last = offsets[i];
if let Some(offset) = offsets.get_mut(i + 1) {
*offset = last + segment.len();
} else {
ends_at = offsets.last().unwrap() + segment.len();
}
}
Some(Info {
buffer,
offsets,
ends_at,
})
}
#[test]
fn winer_is_not_wiener() {
is_wine();
get_version();
get_build();
get_host_version();
get_info();
}
#[test]
fn winer_quality_control() {
use std::env::var;
assert_eq!(is_wine(), var("WINER_TEST_VERSION").is_ok());
assert_eq!(get_version_str(), var("WINER_TEST_VERSION").ok().as_deref());
assert_eq!(get_build_str(), var("WINER_TEST_BUILD").ok().as_deref());
let host = get_host_version();
assert_eq!(
host.as_ref().and_then(|h| h.sysname_str()),
var("WINER_TEST_SYSNAME").ok().as_deref(),
);
assert_eq!(
host.as_ref().and_then(|h| h.release_str()),
var("WINER_TEST_RELEASE").ok().as_deref(),
);
}
#[test]
fn winer_syscall_extension() {
use std::env::var;
let is_wine = var("WINER_TEST_VERSION").is_ok();
if let Some(info) = get_info().as_ref() {
assert!(is_wine);
assert_eq!(
info.version_str(),
var("WINER_TEST_VERSION").ok().as_deref(),
);
assert_eq!(info.build_str(), var("WINER_TEST_BUILD").ok().as_deref(),);
assert_eq!(
info.sysname_str(),
var("WINER_TEST_SYSNAME").ok().as_deref(),
);
assert_eq!(
info.release_str(),
var("WINER_TEST_RELEASE").ok().as_deref(),
);
} else {
assert!(!is_wine);
}
}