#![doc = r#"# forward-dll-mini
[![Crates.io](https://img.shields.io/crates/v/forward-dll)](https://crates.io/crates/forward-dll)
[![GitHub](https://img.shields.io/badge/Github-forward--dll-blue)](https://github.com/hamflx/forward-dll)
[![Crates.io](https://img.shields.io/crates/v/forward-dll-mini)](https://crates.io/crates/forward-dll-mini)
[![GitHub](https://img.shields.io/badge/Github-forward--dll--mini-blue)](https://github.com/vSylva/forward-dll-mini)
```toml
// Cargo.toml
[lib]
name = "dinput8"
// name = "d3d11"
// Not only supports dinput8 and d3d11... test it yourself
crate-type = ["cdylib"]
[build-dependencies]
forward_dll_mini = { git = "https://github.com/vSylva/forward-dll-mini" }
```
```rust
// build.rs
use forward_dll_mini::forward_dll;
fn main() {
forward_dll("C:\\Windows\\System32\\dinput8.dll").unwrap();
// forward_dll("C:\\Windows\\System32\\d3d11.dll").unwrap();
}
```"#]
use std::{collections::HashMap, ffi::NulError, path::PathBuf};
use implib::{def::ModuleDef, Flavor, ImportLibrary, MachineType};
use object::read::pe::{PeFile32, PeFile64};
pub trait ForwardModule {
fn init(&self) -> ForwardResult<()>;
}
#[derive(Debug)]
pub enum ForwardError {
Win32Error(&'static str, u32),
StringError(NulError),
AlreadyInitialized,
}
impl std::fmt::Display for ForwardError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
ForwardError::Win32Error(func_name, err_code) => {
write!(f, "Win32Error: {} {}", func_name, err_code)
}
ForwardError::StringError(ref err) => write!(f, "StringError: {}", err),
ForwardError::AlreadyInitialized => write!(f, "AlreadyInitialized"),
}
}
}
pub type ForwardResult<T> = std::result::Result<T, ForwardError>;
struct ExportItem {
ordinal: u32,
name: Option<String>,
}
pub fn forward_dll(dll_path: &str) -> Result<(), String> {
forward_dll_with_dev_path(dll_path, dll_path)
}
pub fn forward_dll_with_dev_path(dll_path: &str, dev_dll_path: &str) -> Result<(), String> {
let exports = get_dll_export_names(dev_dll_path)?;
forward_dll_impl(dll_path, exports.as_slice())
}
pub fn forward_dll_with_exports(dll_path: &str, exports: &[(u32, &str)]) -> Result<(), String> {
forward_dll_impl(
dll_path,
exports
.iter()
.map(|(ord, name)| {
ExportItem {
ordinal: *ord,
name: Some(name.to_string()),
}
})
.collect::<Vec<_>>()
.as_slice(),
)
}
fn forward_dll_impl(dll_path: &str, exports: &[ExportItem]) -> Result<(), String> {
const SUFFIX: &str = ".dll";
let dll_path_without_ext = if dll_path.to_ascii_lowercase().ends_with(SUFFIX) {
&dll_path[..dll_path.len() - SUFFIX.len()]
} else {
dll_path
};
let out_dir = get_tmp_dir();
let mut anonymous_map = HashMap::new();
let mut anonymous_name_id = 0;
for ExportItem {
name,
ordinal,
} in exports
{
match name {
Some(name) => {
println!(
"cargo:rustc-link-arg=/EXPORT:{name}={dll_path_without_ext}.{name},@{ordinal}"
)
}
None => {
anonymous_name_id += 1;
let fn_name = format!("forward_dll_anonymous_{anonymous_name_id}");
println!(
"cargo:rustc-link-arg=/EXPORT:{fn_name}={dll_path_without_ext}.#{ordinal},@{ordinal},NONAME"
);
anonymous_map.insert(ordinal, fn_name);
}
};
}
let exports_def = String::from("LIBRARY version\nEXPORTS\n")
+ exports
.iter()
.map(
|ExportItem {
name,
ordinal,
}| {
match name {
Some(name) => format!(" {name} @{ordinal}\n"),
None => {
let fn_name = anonymous_map.get(ordinal).unwrap();
format!(" {fn_name} @{ordinal} NONAME\n")
}
}
},
)
.collect::<String>()
.as_str();
#[cfg(target_arch = "x86_64")]
let machine = MachineType::AMD64;
#[cfg(target_arch = "x86")]
let machine = MachineType::I386;
let mut def = ModuleDef::parse(&exports_def, machine)
.map_err(|err| format!("ImportLibrary::new error: {err}"))?;
for item in def.exports.iter_mut() {
item.symbol_name = item.name.trim_start_matches('_').to_string();
}
let lib = ImportLibrary::from_def(def, machine, Flavor::Msvc);
let version_lib_path = out_dir.join("version_proxy.lib");
let mut lib_file = std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(version_lib_path)
.map_err(|err| format!("OpenOptions::open error: {err}"))?;
lib.write_to(&mut lib_file)
.map_err(|err| format!("ImportLibrary::write_to error: {err}"))?;
println!("cargo:rustc-link-search={}", out_dir.display());
println!("cargo:rustc-link-lib=version_proxy");
Ok(())
}
fn get_tmp_dir() -> PathBuf {
std::env::var("OUT_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let dir = std::env::temp_dir().join("forward-dll-libs");
if !dir.exists() {
std::fs::create_dir_all(&dir).expect("Failed to create temp dir");
}
dir
})
}
fn get_dll_export_names(dll_path: &str) -> Result<Vec<ExportItem>, String> {
let dll_file = std::fs::read(dll_path).map_err(|err| format!("Failed to read file: {err}"))?;
let in_data = dll_file.as_slice();
let kind = object::FileKind::parse(in_data).map_err(|err| format!("Invalid file: {err}"))?;
let exports = match kind {
object::FileKind::Pe32 => {
PeFile32::parse(in_data)
.map_err(|err| format!("Invalid pe file: {err}"))?
.export_table()
.map_err(|err| format!("Invalid pe file: {err}"))?
.ok_or_else(|| "No export table".to_string())?
.exports()
}
object::FileKind::Pe64 => {
PeFile64::parse(in_data)
.map_err(|err| format!("Invalid pe file: {err}"))?
.export_table()
.map_err(|err| format!("Invalid pe file: {err}"))?
.ok_or_else(|| "No export table".to_string())?
.exports()
}
_ => return Err("Invalid file".to_string()),
}
.map_err(|err| format!("Invalid file: {err}"))?;
let mut export_list = Vec::new();
for export_item in exports {
let ordinal = export_item.ordinal;
let name = export_item
.name
.map(String::from_utf8_lossy)
.map(String::from);
let item = ExportItem {
name,
ordinal,
};
export_list.push(item);
}
Ok(export_list)
}