diff --git a/Cargo.lock b/Cargo.lock index fb7a39c..c71c4a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,6 +137,26 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + [[package]] name = "bitflags" version = "2.6.0" @@ -191,22 +211,21 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "clang" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c044c781163c001b913cd018fc95a628c50d0d2dfea8bca77dad71edb16e37" -dependencies = [ - "clang-sys", - "libc", -] - [[package]] name = "clang-sys" version = "1.8.1" @@ -215,6 +234,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", + "libloading", ] [[package]] @@ -763,6 +783,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -815,6 +844,16 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets", +] + [[package]] name = "libm" version = "0.2.8" @@ -845,6 +884,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -875,6 +920,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -1089,6 +1144,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -1477,18 +1542,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -1497,9 +1562,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -1642,9 +1707,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.75" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -1985,11 +2050,13 @@ dependencies = [ name = "vapore-abigen" version = "0.1.0" dependencies = [ - "clang", + "bindgen", "color-eyre", "env_logger", "log", "regex", + "serde", + "serde_json", ] [[package]] diff --git a/abigen/Cargo.toml b/abigen/Cargo.toml index 81b8124..cff0d73 100644 --- a/abigen/Cargo.toml +++ b/abigen/Cargo.toml @@ -4,8 +4,10 @@ edition = "2021" version.workspace = true [dependencies] -clang = { version = "2.0.0", features = ["clang_10_0"] } +bindgen = "0.70.1" color-eyre.workspace = true env_logger.workspace = true log.workspace = true regex = "1.10.6" +serde = { version = "1.0.209", features = ["derive"] } +serde_json = "1.0.128" diff --git a/abigen/src/apidesc.rs b/abigen/src/apidesc.rs new file mode 100644 index 0000000..0858b54 --- /dev/null +++ b/abigen/src/apidesc.rs @@ -0,0 +1,79 @@ +use color_eyre::eyre; +use std::{fs, path::Path}; + +#[derive(Debug, serde::Deserialize)] +pub struct ApiDescription { + pub callback_structs: Vec, + pub consts: Vec, + pub enums: Vec, + pub interfaces: Vec, + pub structs: Vec, + pub typedefs: Vec, +} + +#[derive(Debug, serde::Deserialize)] +pub struct CallbackStruct { + pub callback_id: u64, + #[serde(rename = "struct")] + pub _struct: String, + pub fields: Vec, +} + +#[derive(Debug, serde::Deserialize)] +pub struct StructField { + #[serde(rename = "fieldname")] + pub field_name: String, + #[serde(rename = "fieldtype")] + pub field_type: String, +} + +#[derive(Debug, serde::Deserialize)] +pub struct Const { + #[serde(rename = "constname")] + pub const_name: String, + #[serde(rename = "consttype")] + pub const_type: String, + #[serde(rename = "constval")] + pub const_val: String, +} + +#[derive(Debug, serde::Deserialize)] +pub struct Enum { + #[serde(rename = "enumname")] + pub enum_name: String, +} + +#[derive(Debug, serde::Deserialize)] +pub struct Interface { + #[serde(rename = "classname")] + pub class_name: String, + pub version_string: Option, +} + +#[derive(Debug, serde::Deserialize)] +pub struct Struct { + #[serde(rename = "struct")] + pub _struct: String, +} +#[derive(Debug, serde::Deserialize)] +pub struct Typedef { + pub typedef: String, +} + +impl ApiDescription { + pub fn read(sdk_path: &Path) -> eyre::Result { + let content = fs::read(sdk_path.join("steam_api.json"))?; + Ok(serde_json::from_slice(&content)?) + } + + pub fn non_interface_names(&self) -> Vec<&str> { + self.callback_structs + .iter() + .map(|item| item._struct.as_str()) + .chain(self.consts.iter().map(|item| item.const_name.as_str())) + .chain(self.enums.iter().map(|item| item.enum_name.as_str())) + .chain(self.structs.iter().map(|item| item._struct.as_str())) + .chain(self.typedefs.iter().map(|item| item.typedef.as_str())) + .collect() + } +} diff --git a/abigen/src/main.rs b/abigen/src/main.rs index 4322095..55bd87e 100644 --- a/abigen/src/main.rs +++ b/abigen/src/main.rs @@ -6,9 +6,12 @@ use std::{ path::{Path, PathBuf}, }; -use color_eyre::eyre; +use color_eyre::eyre::{self, OptionExt}; use regex::Regex; +mod apidesc; +mod rename; + fn main() -> eyre::Result<()> { env_logger::init(); color_eyre::install()?; @@ -17,23 +20,70 @@ fn main() -> eyre::Result<()> { PathBuf::from(env::var_os("PROTON_SOURCE").unwrap_or_else(|| "proton".into())) .join("lsteamclient"); - let mut versions = BTreeMap::new(); + let mut interface_versions = BTreeMap::new(); - for maybe_sdk in fs::read_dir(lsteamclient_path)? { + for maybe_sdk in fs::read_dir(&lsteamclient_path)? { let sdk = maybe_sdk?; if !sdk.file_type()?.is_dir() { continue; } - let this_versions = interface_versions(&sdk.path())?; - versions.insert(sdk.file_name(), this_versions); + let this_versions = parse_interface_versions(&sdk.path())?; + + interface_versions.insert(sdk.file_name(), this_versions); } - println!("{:#?}", versions); + let greatest_sdk_version = interface_versions + .keys() + .next_back() + .ok_or_eyre("No SDKs found")? + .clone(); + log::info!("Last SDK found is {:?}", greatest_sdk_version); + + let mut sdk_by_iface = BTreeMap::new(); + + for (sdk, sdk_versions) in interface_versions.iter() { + for iface_version in sdk_versions.values() { + sdk_by_iface.insert(iface_version.to_string(), sdk.clone()); + } + } + + println!("{:#?}", sdk_by_iface); + + let api_desc = apidesc::ApiDescription::read(&lsteamclient_path.join(&greatest_sdk_version))?; + + let mut latest_builder = bindgen::builder() + .header( + lsteamclient_path + .join(&greatest_sdk_version) + .join("steam_api.h") + .to_str() + .ok_or_eyre("Invalid path")?, + ) + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .parse_callbacks(Box::new(rename::RenameEnumCallbacks)) + .ignore_methods() + .ignore_functions() + .clang_args(["-x", "c++"]) + .default_enum_style(bindgen::EnumVariation::NewType { + is_bitfield: false, + is_global: false, + }) + .bitfield_enum(".*Flags") + .vtable_generation(true); + + // We only need the public API, steam_api.h implementation details aren't super useful + for item in api_desc.non_interface_names() { + latest_builder = latest_builder.allowlist_item(item); + log::trace!("Allowing item `{}`", item); + } + + let latest_bindings = latest_builder.generate()?; + latest_bindings.write_to_file("target/src/deps.rs")?; Ok(()) } -fn interface_versions(path: &Path) -> eyre::Result> { +fn parse_interface_versions(path: &Path) -> eyre::Result> { let re = Regex::new(r#"#define\s*(STEAM[A-Z]*)_INTERFACE_VERSION\s*"([a-zA-Z0-9_]*)""#)?; let mut versions = BTreeMap::new(); diff --git a/abigen/src/rename.rs b/abigen/src/rename.rs new file mode 100644 index 0000000..a29d023 --- /dev/null +++ b/abigen/src/rename.rs @@ -0,0 +1,56 @@ +use std::collections::BTreeMap; + +use bindgen::callbacks::ParseCallbacks; +use regex::Regex; + +#[derive(Debug)] +pub struct RenameEnumCallbacks; + +impl ParseCallbacks for RenameEnumCallbacks { + fn enum_variant_name( + &self, + enum_name: Option<&str>, + original_variant_name: &str, + _variant_value: bindgen::callbacks::EnumVariantValue, + ) -> Option { + let base_name = enum_name?; + let name = if base_name.ends_with("Flags") { + base_name.split_at(base_name.len() - 1).0 + } else { + base_name + }; + let re = Regex::new(&format!("^k_{}_?(.*)$", regex::escape(name))).unwrap(); + let c = re.captures(original_variant_name)?; + let new_name = c.get(1)?.as_str(); + if new_name.chars().next()?.is_ascii_digit() { + Some(format!("_{}", new_name)) + } else { + Some(new_name.to_string()) + } + } +} + +#[derive(Debug)] +pub struct RenameInterfaceCallbacks { + interface_versions: BTreeMap, + re: Regex, +} + +impl RenameInterfaceCallbacks { + pub fn new(interface_versions: BTreeMap) -> Self { + Self { + interface_versions, + re: Regex::new("^I(Steam[a-zA-Z]+)$").unwrap(), + } + } +} + +impl ParseCallbacks for RenameInterfaceCallbacks { + fn item_name(&self, original_item_name: &str) -> Option { + let c = self.re.captures(original_item_name)?; + let full_version = self.interface_versions.get(c.get(1)?.as_str())?; + let (_, short_version) = full_version.split_at(full_version.len() - 3); + + Some(format!("{}{}", original_item_name, short_version)) + } +} diff --git a/flake.nix b/flake.nix index 8bd7005..db3e8ac 100644 --- a/flake.nix +++ b/flake.nix @@ -30,7 +30,7 @@ LIBCLANG_PATH = "${llvmPackages.libclang.lib}/lib"; RUST_SRC_PATH = "${rustPackages.rustPlatform.rustLibSrc}"; - RUST_LOG = "debug,vapore=trace,vapore-client=trace"; + RUST_LOG = "debug,vapore=trace,vapore-client=trace,bindgen=error"; RUST_BACKTRACE = "1"; };