use core::ffi::{CStr, c_char, c_void};
use core::mem::{MaybeUninit, transmute};
use core::ops::Deref;
use core::ptr::{NonNull, null, null_mut};
use core::str;
use windows::Wdk::System::SystemInformation::{NtQuerySystemInformation, SYSTEM_INFORMATION_CLASS};
use windows::Win32::Foundation::HMODULE;
use windows::Win32::System::Memory::{GetProcessHeap, HeapFree};
use spin::Once;
#[repr(transparent)]
#[derive(Clone, Copy)]
struct Module(HMODULE);
unsafe impl Send for Module {}
unsafe impl Sync for Module {}
impl From<HMODULE> for Module {
fn from(value: HMODULE) -> Self {
Self(value)
}
}
impl From<Module> for HMODULE {
fn from(value: Module) -> Self {
value.0
}
}
macro_rules! req_module {
($m:literal) => {
unsafe {
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::core::w;
GetModuleHandleW(w!($m))
}
};
($m:literal => $cache:ident) => {
*$cache.call_once(|| req_module!($m).unwrap().into())
};
}
macro_rules! import {
($hm:expr, $f:literal) => {
unsafe {
use windows::Win32::System::LibraryLoader::GetProcAddress;
use windows::core::s;
GetProcAddress($hm, s!($f)).map(|v| core::mem::transmute(v))
}
};
($hm:expr, $f:literal => $cache:ident) => {
*$cache.call_once(|| import!($hm, $f))
};
}
static NTDLL: Once<Module> = Once::new();
static KERNEL32: Once<Module> = Once::new();
pub type GetVersionFn = unsafe extern "C" fn() -> *const c_char;
pub type GetBuildIdFn = unsafe extern "C" fn() -> *const c_char;
pub type GetHostVersionFn = unsafe extern "C" fn(*mut *const c_char, *mut *const c_char);
static GET_VERSION: Once<Option<GetVersionFn>> = Once::new();
static GET_BUILD_ID: Once<Option<GetBuildIdFn>> = Once::new();
static GET_HOST_VERSION: Once<Option<GetHostVersionFn>> = Once::new();
pub fn ntdll() -> HMODULE {
req_module!("ntdll" => NTDLL).into()
}
pub fn kernel32() -> HMODULE {
req_module!("kernel32" => KERNEL32).into()
}
pub fn locate_get_version() -> Option<GetVersionFn> {
import!(ntdll(), "wine_get_version" => GET_VERSION)
}
pub fn locate_get_build_id() -> Option<GetBuildIdFn> {
import!(ntdll(), "wine_get_build_id" => GET_BUILD_ID)
}
pub fn locate_get_host_version() -> Option<GetHostVersionFn> {
import!(ntdll(), "wine_get_host_version" => GET_HOST_VERSION)
}
pub fn is_wine() -> bool {
locate_get_version().is_some()
}
#[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) -> Result<&'static str, str::Utf8Error> {
self.sysname.to_str()
}
pub fn release(&self) -> &'static CStr {
self.release
}
pub fn release_str(&self) -> Result<&'static str, str::Utf8Error> {
self.release.to_str()
}
}
pub fn runtime() -> Option<Runtime> {
Runtime::probe()
}
#[derive(Debug, Clone, Copy)]
pub struct Runtime;
impl Runtime {
fn probe() -> Option<Self> {
if self::is_wine() { Some(Self) } else { None }
}
#[inline]
pub fn version(&self) -> &'static CStr {
let proc = locate_get_version().expect("Runtime ensures that version API exists.");
unsafe { CStr::from_ptr(proc()) }
}
#[inline]
pub fn version_str(&self) -> Result<&'static str, str::Utf8Error> {
self.version().to_str()
}
#[inline]
pub fn build_id(&self) -> Option<&'static CStr> {
let proc = import!(ntdll(), "wine_get_build_id" => GET_BUILD_ID)?;
Some(unsafe { CStr::from_ptr(proc()) })
}
#[inline]
pub fn build_id_str(&self) -> Option<Result<&'static str, str::Utf8Error>> {
Some(self.build_id()?.to_str())
}
#[inline]
pub fn host_version(&self) -> Option<HostVersion> {
let proc = import!(ntdll(), "wine_get_host_version" => GET_HOST_VERSION)?;
let mut sysname_ptr: *const c_char = null();
let mut release_ptr: *const c_char = null();
unsafe {
proc(&mut sysname_ptr, &mut release_ptr);
Some(HostVersion {
sysname: CStr::from_ptr(sysname_ptr),
release: CStr::from_ptr(release_ptr),
})
}
}
#[inline]
pub fn host_sysname(&self) -> Option<&'static CStr> {
Some(self.host_version()?.sysname())
}
#[inline]
pub fn host_sysname_str(&self) -> Option<Result<&'static str, str::Utf8Error>> {
Some(self.host_version()?.sysname_str())
}
#[inline]
pub fn host_release(&self) -> Option<&'static CStr> {
Some(self.host_version()?.release())
}
#[inline]
pub fn host_release_str(&self) -> Option<Result<&'static str, str::Utf8Error>> {
Some(self.host_version()?.release_str())
}
pub fn info(&self) -> Result<Info, windows::core::Error> {
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()?
}
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();
}
}
Ok(Info {
buffer,
offsets,
ends_at,
})
}
}
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) -> Result<&str, str::Utf8Error> {
self.version().to_str()
}
pub fn build(&self) -> &CStr {
self.cstr_at::<1>()
}
pub fn build_str(&self) -> Result<&str, str::Utf8Error> {
self.build().to_str()
}
pub fn sysname(&self) -> &CStr {
self.cstr_at::<2>()
}
pub fn sysname_str(&self) -> Result<&str, str::Utf8Error> {
self.sysname().to_str()
}
pub fn release(&self) -> &CStr {
self.cstr_at::<3>()
}
pub fn release_str(&self) -> Result<&str, str::Utf8Error> {
self.release().to_str()
}
}
#[repr(transparent)]
pub struct Owned<T: ?Sized> {
pointer: NonNull<T>,
}
unsafe impl<T: ?Sized> Send for Owned<T> {}
unsafe impl<T: ?Sized> Sync for Owned<T> {}
impl<T: ?Sized> Owned<T> {
#[inline]
const fn new(ptr: *mut T) -> Option<Self> {
match NonNull::new(ptr) {
Some(pointer) => Some(Self { pointer }),
None => None,
}
}
#[inline]
pub const fn as_nonnull(&self) -> NonNull<T> {
self.pointer
}
#[inline]
pub const fn as_ptr(&self) -> *const T {
self.pointer.as_ptr() as *const _
}
#[inline]
pub const fn as_mut_ptr(&self) -> *mut T {
self.pointer.as_ptr()
}
}
impl<T: ?Sized> Deref for Owned<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { self.pointer.as_ref() }
}
}
impl<T: ?Sized> Drop for Owned<T> {
fn drop(&mut self) {
unsafe {
if let Ok(heap) = GetProcessHeap() {
let _ = HeapFree(heap, Default::default(), Some(self.as_ptr() as *const _));
}
}
}
}
pub mod path {
use super::Owned;
use super::kernel32;
use core::ffi::{CStr, c_char};
use windows::core::{PCWSTR, PWSTR};
use spin::Once;
#[cfg(feature = "wine-std")]
use std::ffi::{CString, OsString};
#[cfg(feature = "wine-std")]
use std::os::windows::ffi::{OsStrExt, OsStringExt};
#[cfg(feature = "wine-std")]
use std::path::{Path, PathBuf};
#[cfg(feature = "wine-std")]
pub use typed_path::{UnixPath, UnixPathBuf};
#[cfg(feature = "wine-std")]
use windows::Win32::Storage::FileSystem::{GetLongPathNameW, GetShortPathNameW};
pub type GetUnixFileNameFn = unsafe extern "C" fn(PCWSTR) -> *mut c_char;
pub type GetDosFileNameFn = unsafe extern "C" fn(*const c_char) -> PWSTR;
pub type U16CStr = widestring::U16CStr;
static GET_UNIX_FILE_NAME: Once<Option<GetUnixFileNameFn>> = Once::new();
static GET_DOS_FILE_NAME: Once<Option<GetDosFileNameFn>> = Once::new();
pub fn locate_get_unix_file_name() -> Option<GetUnixFileNameFn> {
import!(kernel32(), "wine_get_unix_file_name" => GET_UNIX_FILE_NAME)
}
pub fn locate_get_dos_file_name() -> Option<GetDosFileNameFn> {
import!(kernel32(), "wine_get_dos_file_name" => GET_DOS_FILE_NAME)
}
pub unsafe fn get_dos_file_name(path: *const c_char) -> Option<Owned<u16>> {
let proc = locate_get_dos_file_name()?;
Owned::new(unsafe { proc(path) }.0)
}
pub unsafe fn get_unix_file_name(path: *const u16) -> Option<Owned<c_char>> {
let proc = locate_get_unix_file_name()?;
Owned::new(unsafe { proc(PCWSTR::from_raw(path)) })
}
pub fn unix2dos_str(path: &CStr) -> Option<Owned<U16CStr>> {
let proc = locate_get_dos_file_name()?;
let pwstr = unsafe { proc(path.as_ptr()) };
let wstr = unsafe { U16CStr::from_ptr_str(pwstr.0 as *const _) };
Owned::new(wstr as *const _ as *mut _)
}
pub fn dos2unix_str(path: &U16CStr) -> Option<Owned<CStr>> {
let proc = locate_get_unix_file_name()?;
let cstr = unsafe { CStr::from_ptr(proc(PCWSTR::from_raw(path.as_ptr()))) };
Owned::new(cstr as *const _ as *mut _)
}
#[cfg(feature = "wine-std")]
pub fn unix2dos(path: &UnixPath) -> Option<PathBuf> {
use widestring::U16CStr;
let path_cstr = CString::new(path.as_bytes()).ok()?;
let result = unsafe { get_dos_file_name(path_cstr.as_ptr()) }?;
let wstr = unsafe { U16CStr::from_ptr_str(result.as_ptr() as *const _) };
Some(OsString::from_wide(wstr.as_slice()).into())
}
#[cfg(feature = "wine-std")]
pub fn dos2unix(path: impl AsRef<Path>) -> Option<UnixPathBuf> {
use core::iter::once;
let buf = path
.as_ref()
.as_os_str()
.encode_wide()
.chain(once(0))
.collect::<Vec<_>>();
let result = unsafe { get_unix_file_name(buf.as_ptr()) }?;
let cstr = unsafe { CStr::from_ptr(result.as_ptr()) };
Some(UnixPath::new(cstr.to_bytes()).to_path_buf())
}
#[cfg(feature = "wine-std")]
pub trait PathExt {
fn to_short_path(&self) -> windows::core::Result<PathBuf>;
fn to_long_path(&self) -> windows::core::Result<PathBuf>;
}
#[cfg(feature = "wine-std")]
type GetPathNameFn = unsafe fn(PWSTR, Option<&mut [u16]>) -> u32;
#[cfg(feature = "wine-std")]
fn short_long_convert(proc: GetPathNameFn, path: &Path) -> windows::core::Result<PathBuf> {
use core::iter::once;
use windows::Win32::Foundation::GetLastError;
let mut encoded = path
.as_os_str()
.encode_wide()
.chain(once(0))
.collect::<Vec<_>>();
let pwstr = PWSTR::from_raw(encoded.as_mut_ptr());
let cap = unsafe { proc(pwstr, None) } as usize;
if cap == 0 {
Err(unsafe { GetLastError() }.into())
} else {
let mut buf = vec![0; cap];
let len = unsafe { proc(pwstr, Some(buf.as_mut_slice())) } as usize;
debug_assert_eq!(len + 1, buf.len());
Ok(OsString::from_wide(&buf[..len]).into())
}
}
#[cfg(feature = "wine-std")]
impl PathExt for Path {
fn to_short_path(&self) -> windows::core::Result<PathBuf> {
short_long_convert(GetShortPathNameW, self)
}
fn to_long_path(&self) -> windows::core::Result<PathBuf> {
short_long_convert(GetLongPathNameW, self)
}
}
}