summaryrefslogtreecommitdiff
path: root/plugins/bntl_utils/src/winmd
diff options
context:
space:
mode:
authorMason Reed <mason@vector35.com>2026-02-11 18:04:07 -0800
committerMason Reed <35282038+emesare@users.noreply.github.com>2026-02-23 00:09:44 -0800
commit37008b7fa16837d04c1658868646cad681cbe035 (patch)
tree577c2b62ee47c78a5d31d11f2aa610e441f5c808 /plugins/bntl_utils/src/winmd
parent837f8590be80b7c98162e70e4f0c1814b83e9d7b (diff)
Add BNTL utility plugin
Allow users to easily create, diff, dump and validate type libraries Supports the following formats: - C header files (via core type parsers) - Binary files (collects exported and imported functions) - WinMD files (via `windows-metadata` crate) - Existing type library files (for easy fixups) - Apiset files (to resolve through forwarded windows dlls) Can be invoked as a regular plugin via UI commands or via CLI. Processing of type libraries inherently requires external linking, processing will automatically merge and deduplicate colliding type libraries so prefer to use inside a project or a directory and process all information (for a given platform) at once, rather than smaller invocations.
Diffstat (limited to 'plugins/bntl_utils/src/winmd')
-rw-r--r--plugins/bntl_utils/src/winmd/info.rs430
-rw-r--r--plugins/bntl_utils/src/winmd/translate.rs654
2 files changed, 1084 insertions, 0 deletions
diff --git a/plugins/bntl_utils/src/winmd/info.rs b/plugins/bntl_utils/src/winmd/info.rs
new file mode 100644
index 00000000..4db30c7a
--- /dev/null
+++ b/plugins/bntl_utils/src/winmd/info.rs
@@ -0,0 +1,430 @@
+//! Metadata information extracted from Windows metadata files.
+//!
+//! While we could use the direct representation, this is easier to work with.
+
+use std::collections::{HashMap, HashSet};
+
+#[derive(Debug, Default, Clone)]
+pub struct MetadataInfo {
+ pub types: Vec<MetadataTypeInfo>,
+ pub functions: Vec<MetadataFunctionInfo>,
+ pub constants: Vec<MetadataConstantInfo>,
+}
+
+impl MetadataInfo {
+ /// Partitions the metadata into a map of libraries, where each library contains types and functions
+ /// that belong to that library. This is used when mapping metadata info to type libraries.
+ pub fn partitioned(&self) -> PartitionedMetadataInfo {
+ let mut result_map: HashMap<LibraryName, LibraryInfo> = HashMap::new();
+
+ // Map of namespace to module names that use it.
+ let mut namespace_dependencies: HashMap<String, HashSet<String>> = HashMap::new();
+ for func in &self.functions {
+ if let Some(import) = &func.import_info {
+ namespace_dependencies
+ .entry(func.namespace.clone())
+ .or_default()
+ .insert(import.module.name.clone());
+ }
+ }
+
+ let namespace_to_library_name = |ns: &str| -> LibraryName {
+ match namespace_dependencies.get(ns) {
+ Some(modules) if modules.len() == 1 => {
+ LibraryName::Module(modules.iter().next().unwrap().clone())
+ }
+ _ => LibraryName::Namespace(ns.to_string()),
+ }
+ };
+
+ for func in &self.functions {
+ let dest_lib = match &func.import_info {
+ Some(info) => LibraryName::Module(info.module.name.clone()),
+ None => LibraryName::Namespace(func.namespace.clone()),
+ };
+ let entry = result_map.entry(dest_lib.clone()).or_default();
+ func.ty.visit_references(&mut |ns, name| {
+ let library_name = namespace_to_library_name(ns);
+ if dest_lib != library_name {
+ entry
+ .external_references
+ .insert(name.to_string(), library_name);
+ }
+ });
+ entry.metadata.functions.push(func.clone());
+ }
+
+ for ty in &self.types {
+ let dest_lib = namespace_to_library_name(&ty.namespace);
+ let entry = result_map.entry(dest_lib.clone()).or_default();
+ ty.kind.visit_references(&mut |ns, name| {
+ let library_name = namespace_to_library_name(ns);
+ if dest_lib != library_name {
+ entry
+ .external_references
+ .insert(name.to_string(), library_name);
+ }
+ });
+ entry.metadata.types.push(ty.clone());
+ }
+
+ for constant in &self.constants {
+ let dest_lib = namespace_to_library_name(&constant.namespace);
+ let entry = result_map.entry(dest_lib.clone()).or_default();
+ constant.ty.visit_references(&mut |ns, name| {
+ let library_name = namespace_to_library_name(ns);
+ if dest_lib != library_name {
+ entry
+ .external_references
+ .insert(name.to_string(), library_name);
+ }
+ });
+ entry.metadata.constants.push(constant.clone());
+ }
+
+ PartitionedMetadataInfo {
+ libraries: result_map,
+ }
+ }
+
+ pub fn create_constant_enums(&self) -> Vec<MetadataTypeInfo> {
+ // Group constants by their type, if there are multiple constants with the same type, we
+ // will make an enum out of them, once that is done, we will take overlapping constants
+ // and prioritize certain namespaces over others.
+ // TODO: Add some more structured types here, this is a crazy map.
+ let mut grouped_constants: HashMap<
+ (String, String),
+ HashMap<u64, Vec<MetadataConstantInfo>>,
+ > = HashMap::new();
+ for constant in &self.constants {
+ let MetadataTypeKind::Reference { name, namespace } = &constant.ty else {
+ // TODO: We should optionally provide a way to group constants like these into an enumeration.
+ // Skipping constant `WDS_MC_TRACE_VERBOSE` with non-reference type `Integer { size: Some(4), is_signed: false }`
+ // Skipping constant `WDS_MC_TRACE_INFO` with non-reference type `Integer { size: Some(4), is_signed: false }`
+ // Skipping constant `WDS_MC_TRACE_WARNING` with non-reference type `Integer { size: Some(4), is_signed: false }`
+ // Skipping constant `WDS_MC_TRACE_ERROR` with non-reference type `Integer { size: Some(4), is_signed: false }`
+ // Skipping constant `WDS_MC_TRACE_FATAL` with non-reference type `Integer { size: Some(4), is_signed: false }`
+ tracing::debug!(
+ "Skipping constant `{}` with non-reference type `{:?}`",
+ constant.name,
+ constant.ty
+ );
+ continue;
+ };
+ grouped_constants
+ .entry((namespace.clone(), name.clone()))
+ .or_default()
+ .entry(constant.value)
+ .or_default()
+ .push(constant.clone());
+ }
+
+ let mut enums = Vec::new();
+ for ((enum_namespace, enum_name), mapped_values) in grouped_constants {
+ let mut variants = Vec::new();
+ for (_, group_variants) in mapped_values {
+ let sorted_group_variants =
+ sort_metadata_constants_by_proximity(&enum_namespace, group_variants);
+ let enum_variants: Vec<_> = sorted_group_variants
+ .iter()
+ .map(|info| (info.name.clone(), info.value))
+ .collect();
+ variants.extend(enum_variants);
+ }
+
+ let enum_kind = MetadataTypeKind::Enum {
+ ty: Box::new(MetadataTypeKind::Void),
+ variants,
+ };
+
+ enums.push(MetadataTypeInfo {
+ name: enum_name,
+ kind: enum_kind,
+ namespace: enum_namespace,
+ });
+ }
+ enums
+ }
+
+ #[allow(dead_code)]
+ fn update_stale_references(&mut self) {
+ let mut valid_type_map = HashMap::new();
+ for ty in self.types.iter() {
+ valid_type_map.insert(ty.name.clone(), ty.clone());
+ }
+
+ for ty in self.types.iter_mut() {
+ ty.kind.visit_references_mut(&mut |node| {
+ let MetadataTypeKind::Reference { name, namespace } = node else {
+ tracing::error!(
+ "`visit_references_mut` did not return a reference! {:?}",
+ node
+ );
+ return;
+ };
+ if let Some(survivor) = valid_type_map.get(name) {
+ if namespace != &survivor.namespace {
+ tracing::debug!(
+ "Updating stale namespace reference `{}` to `{}` for `{}`",
+ namespace,
+ survivor.namespace,
+ name
+ );
+ *namespace = survivor.namespace.clone();
+ }
+ }
+ });
+ }
+ }
+}
+
+#[derive(Debug, Clone, Eq, Hash, PartialEq)]
+pub enum LibraryName {
+ /// A synthetic library with no associated module name.
+ ///
+ /// The shared library is "synthetic" in the sense that a binary view cannot reference it directly.
+ Namespace(String),
+ /// A real module with a name (e.g. "info.dll"), these libraries can be referenced directly by a binary view.
+ Module(String),
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct LibraryInfo {
+ pub metadata: MetadataInfo,
+ /// A map of externally referenced names to their library names.
+ ///
+ /// This is required when resolving type references to other libraries.
+ pub external_references: HashMap<String, LibraryName>,
+}
+
+#[derive(Debug, Default)]
+pub struct PartitionedMetadataInfo {
+ pub libraries: HashMap<LibraryName, LibraryInfo>,
+}
+
+// TODO: ModuleRef (computable from ModuleInfo and the underlying core module)
+// TODO: Put a ModuleRef in all places where a module is associated.
+#[derive(Debug, Clone)]
+pub struct MetadataModuleInfo {
+ /// The modules name on disk, this is used to determine the imported
+ /// function name when loading type information from a type library.
+ pub name: String,
+}
+
+#[derive(Debug, Clone)]
+pub struct MetadataTypeInfo {
+ pub name: String,
+ pub kind: MetadataTypeKind,
+ /// The namespace of the type, e.x. "Windows.Win32.Foundation"
+ ///
+ /// This is used to help determine what library this information belongs to. When we go to import
+ /// this information (along with others), we will build a tree of information where each node
+ /// corresponds to the namespace, and each child node corresponds to a sub-namespace. Then import
+ /// info will be enumerated to determine if the type can only ever belong to a single import module
+ /// if the type is only used in a single module, we will place it in that type library. If the namespace
+ /// can reference more than one module, we will place it in a common type library named after
+ /// the namespace itself, it can only ever be referenced by another type library and as such should
+ /// only contain types and no functions.
+ ///
+ /// For more information see [`PartitionedMetadataInfo`].
+ pub namespace: String,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum MetadataTypeKind {
+ Void,
+ Bool {
+ // NOTE: Weird optional, if None we actually default the size to integer size!
+ size: Option<usize>,
+ },
+ Integer {
+ size: Option<usize>,
+ is_signed: bool,
+ },
+ Character {
+ size: usize,
+ },
+ Float {
+ size: usize,
+ },
+ Pointer {
+ is_const: bool,
+ is_pointee_const: bool,
+ target: Box<MetadataTypeKind>,
+ },
+ Array {
+ element: Box<MetadataTypeKind>,
+ count: usize,
+ },
+ Struct {
+ fields: Vec<MetadataFieldInfo>,
+ is_packed: bool,
+ },
+ Union {
+ fields: Vec<MetadataFieldInfo>,
+ },
+ Enum {
+ ty: Box<MetadataTypeKind>,
+ variants: Vec<(String, u64)>,
+ },
+ Function {
+ params: Vec<MetadataParameterInfo>,
+ return_type: Box<MetadataTypeKind>,
+ is_vararg: bool,
+ },
+ Reference {
+ // TODO: Generics may also be passed here.
+ /// The namespace of the referenced type, e.x. "Windows.Win32.Foundation"
+ namespace: String,
+ /// The referenced type name, e.x. "BOOL"
+ name: String,
+ },
+}
+
+impl MetadataTypeKind {
+ pub(crate) fn visit_references<F>(&self, callback: &mut F)
+ where
+ F: FnMut(&str, &str),
+ {
+ match self {
+ MetadataTypeKind::Reference { namespace, name } => {
+ callback(namespace, name);
+ }
+ MetadataTypeKind::Pointer { target, .. } => {
+ target.visit_references(callback);
+ }
+ MetadataTypeKind::Array { element, .. } => {
+ element.visit_references(callback);
+ }
+ MetadataTypeKind::Struct { fields, .. } => {
+ for field in fields {
+ field.ty.visit_references(callback);
+ }
+ }
+ MetadataTypeKind::Enum { ty, .. } => {
+ ty.visit_references(callback);
+ }
+ MetadataTypeKind::Function {
+ params,
+ return_type,
+ ..
+ } => {
+ for param in params {
+ param.ty.visit_references(callback);
+ }
+ return_type.visit_references(callback);
+ }
+ _ => {}
+ }
+ }
+
+ #[allow(dead_code)]
+ pub(crate) fn visit_references_mut<F>(&mut self, callback: &mut F)
+ where
+ F: FnMut(&mut MetadataTypeKind),
+ {
+ match self {
+ MetadataTypeKind::Reference { .. } => {
+ callback(self);
+ }
+ MetadataTypeKind::Pointer { target, .. } => {
+ target.visit_references_mut(callback);
+ }
+ MetadataTypeKind::Array { element, .. } => {
+ element.visit_references_mut(callback);
+ }
+ MetadataTypeKind::Struct { fields, .. } | MetadataTypeKind::Union { fields, .. } => {
+ for field in fields {
+ field.ty.visit_references_mut(callback);
+ }
+ }
+ MetadataTypeKind::Enum { ty, .. } => {
+ ty.visit_references_mut(callback);
+ }
+ MetadataTypeKind::Function {
+ params,
+ return_type,
+ ..
+ } => {
+ for param in params {
+ param.ty.visit_references_mut(callback);
+ }
+ return_type.visit_references_mut(callback);
+ }
+ _ => {}
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct MetadataFieldInfo {
+ pub name: String,
+ pub ty: MetadataTypeKind,
+ pub is_const: bool,
+ /// This is only set for bitfields, The first value is the bit position within the associated byte,
+ /// and the second is the bit width.
+ ///
+ /// NOTE: The bit position can never be greater than `7`.
+ pub bitfield: Option<(u8, u8)>,
+ // TODO: Attributes ( virtual, static, etc...)
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct MetadataParameterInfo {
+ pub name: String,
+ pub ty: MetadataTypeKind,
+ // TODO: Attributes (in, out, etc...)
+}
+
+#[allow(dead_code)]
+#[derive(Debug, Clone)]
+pub enum MetadataImportMethod {
+ ByName(String),
+ ByOrdinal(u32),
+}
+
+#[derive(Debug, Clone)]
+pub struct MetadataImportInfo {
+ #[allow(dead_code)]
+ pub method: MetadataImportMethod,
+ pub module: MetadataModuleInfo,
+}
+
+#[derive(Debug, Clone)]
+pub struct MetadataFunctionInfo {
+ pub name: String,
+ /// This will only ever be [`MetadataTypeKind::Function`].
+ pub ty: MetadataTypeKind,
+ pub namespace: String,
+ pub import_info: Option<MetadataImportInfo>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct MetadataConstantInfo {
+ pub name: String,
+ pub namespace: String,
+ pub ty: MetadataTypeKind,
+ pub value: u64,
+}
+
+pub fn sort_metadata_constants_by_proximity(
+ reference: &str,
+ mut candidates: Vec<MetadataConstantInfo>,
+) -> Vec<MetadataConstantInfo> {
+ let ref_parts: Vec<&str> = reference.split('.').collect();
+ candidates.sort_by_cached_key(|info| {
+ // Extract the namespace string from the metadata info
+ let ns = &info.namespace;
+ let cand_parts = ns.split('.');
+
+ let score = ref_parts
+ .iter()
+ .zip(cand_parts)
+ .take_while(|(a, b)| *a == b)
+ .count();
+
+ // Sort by highest score first, then alphabetically by namespace
+ (std::cmp::Reverse(score), ns.clone())
+ });
+ candidates
+}
diff --git a/plugins/bntl_utils/src/winmd/translate.rs b/plugins/bntl_utils/src/winmd/translate.rs
new file mode 100644
index 00000000..01ea54a5
--- /dev/null
+++ b/plugins/bntl_utils/src/winmd/translate.rs
@@ -0,0 +1,654 @@
+//! Translate windows metadata into a self-contained structure, for later use.
+
+use super::info::{
+ MetadataConstantInfo, MetadataFieldInfo, MetadataFunctionInfo, MetadataImportInfo,
+ MetadataImportMethod, MetadataInfo, MetadataModuleInfo, MetadataParameterInfo,
+ MetadataTypeInfo, MetadataTypeKind,
+};
+use std::collections::{HashMap, HashSet};
+use thiserror::Error;
+use windows_metadata::reader::TypeCategory;
+use windows_metadata::{
+ AsRow, FieldAttributes, HasAttributes, MethodCallAttributes, Type, TypeAttributes, Value,
+};
+
+pub const BITFIELD_ATTR: &str = "NativeBitfieldAttribute";
+pub const CONST_ATTR: &str = "ConstAttribute";
+pub const FNPTR_ATTR: &str = "UnmanagedFunctionPointerAttribute";
+pub const _STRUCT_SIZE_ATTR: &str = "StructSizeFieldAttribute";
+pub const API_CONTRACT_ATTR: &str = "ApiContractAttribute";
+
+#[derive(Error, Debug)]
+pub enum TranslationError {
+ #[error("no files were provided")]
+ NoFiles,
+ #[error("the type name '{0}' is not handled")]
+ UnhandledType(String),
+ #[error("the attribute '{0}' is not supported")]
+ UnsupportedAttribute(String),
+}
+
+pub struct WindowsMetadataTranslator {
+ // TODO: Allow this to be customized by user.
+ /// Replace references to a given name with a different one.
+ ///
+ /// This allows you to move types to a different namespace or rename them and be certain all
+ /// references to that type are updated.
+ remapped_references: HashMap<(&'static str, &'static str), (&'static str, &'static str)>,
+}
+
+impl WindowsMetadataTranslator {
+ pub fn new() -> Self {
+ // TODO: Move this to a static array.
+ let mut remapped_references = HashMap::new();
+ remapped_references.insert(("System", "Guid"), ("Windows.Win32.Foundation", "Guid"));
+ Self {
+ remapped_references,
+ }
+ }
+
+ pub fn translate(
+ &self,
+ files: Vec<windows_metadata::reader::File>,
+ ) -> Result<MetadataInfo, TranslationError> {
+ if files.is_empty() {
+ return Err(TranslationError::NoFiles);
+ }
+ let index = windows_metadata::reader::TypeIndex::new(files);
+ self.translate_index(&index)
+ }
+
+ pub fn translate_index(
+ &self,
+ index: &windows_metadata::reader::TypeIndex,
+ ) -> Result<MetadataInfo, TranslationError> {
+ let mut functions = Vec::new();
+ let mut types = Vec::new();
+ let mut constants = Vec::new();
+
+ // TODO: Move this somewhere else?
+ // Add synthetic types here.
+ types.extend([
+ MetadataTypeInfo {
+ name: "Guid".to_string(),
+ kind: MetadataTypeKind::Struct {
+ fields: vec![
+ MetadataFieldInfo {
+ name: "Data1".to_string(),
+ ty: MetadataTypeKind::Integer {
+ size: Some(4),
+ is_signed: false,
+ },
+ is_const: false,
+ bitfield: None,
+ },
+ MetadataFieldInfo {
+ name: "Data2".to_string(),
+ ty: MetadataTypeKind::Integer {
+ size: Some(2),
+ is_signed: false,
+ },
+ is_const: false,
+ bitfield: None,
+ },
+ MetadataFieldInfo {
+ name: "Data3".to_string(),
+ ty: MetadataTypeKind::Integer {
+ size: Some(2),
+ is_signed: false,
+ },
+ is_const: false,
+ bitfield: None,
+ },
+ MetadataFieldInfo {
+ name: "Data4".to_string(),
+ ty: MetadataTypeKind::Array {
+ element: Box::new(MetadataTypeKind::Integer {
+ size: Some(1),
+ is_signed: false,
+ }),
+ count: 8,
+ },
+ is_const: false,
+ bitfield: None,
+ },
+ ],
+ is_packed: false,
+ },
+ namespace: "Windows.Win32.Foundation".to_string(),
+ },
+ MetadataTypeInfo {
+ name: "HANDLE".to_string(),
+ kind: MetadataTypeKind::Pointer {
+ is_const: false,
+ is_pointee_const: false,
+ target: Box::new(MetadataTypeKind::Void),
+ },
+ namespace: "Windows.Win32.Foundation".to_string(),
+ },
+ MetadataTypeInfo {
+ name: "HINSTANCE".to_string(),
+ kind: MetadataTypeKind::Pointer {
+ is_const: false,
+ is_pointee_const: false,
+ target: Box::new(MetadataTypeKind::Void),
+ },
+ namespace: "Windows.Win32.Foundation".to_string(),
+ },
+ MetadataTypeInfo {
+ name: "HMODULE".to_string(),
+ kind: MetadataTypeKind::Pointer {
+ is_const: false,
+ is_pointee_const: false,
+ target: Box::new(MetadataTypeKind::Void),
+ },
+ namespace: "Windows.Win32.Foundation".to_string(),
+ },
+ MetadataTypeInfo {
+ name: "PCSTR".to_string(),
+ kind: MetadataTypeKind::Pointer {
+ is_const: true,
+ is_pointee_const: false,
+ target: Box::new(MetadataTypeKind::Character { size: 1 }),
+ },
+ namespace: "Windows.Win32.Foundation".to_string(),
+ },
+ MetadataTypeInfo {
+ name: "PCWSTR".to_string(),
+ kind: MetadataTypeKind::Pointer {
+ is_const: true,
+ is_pointee_const: false,
+ target: Box::new(MetadataTypeKind::Character { size: 2 }),
+ },
+ namespace: "Windows.Win32.Foundation".to_string(),
+ },
+ MetadataTypeInfo {
+ name: "PSTR".to_string(),
+ kind: MetadataTypeKind::Pointer {
+ is_const: false,
+ is_pointee_const: false,
+ target: Box::new(MetadataTypeKind::Character { size: 1 }),
+ },
+ namespace: "Windows.Win32.Foundation".to_string(),
+ },
+ MetadataTypeInfo {
+ name: "PWSTR".to_string(),
+ kind: MetadataTypeKind::Pointer {
+ is_const: false,
+ is_pointee_const: false,
+ target: Box::new(MetadataTypeKind::Character { size: 2 }),
+ },
+ namespace: "Windows.Win32.Foundation".to_string(),
+ },
+ MetadataTypeInfo {
+ name: "UNICODE_STRING".to_string(),
+ kind: MetadataTypeKind::Struct {
+ fields: vec![
+ MetadataFieldInfo {
+ name: "Length".to_string(),
+ ty: MetadataTypeKind::Integer {
+ size: Some(2),
+ is_signed: false,
+ },
+ is_const: false,
+ bitfield: None,
+ },
+ MetadataFieldInfo {
+ name: "MaximumLength".to_string(),
+ ty: MetadataTypeKind::Integer {
+ size: Some(2),
+ is_signed: false,
+ },
+ is_const: false,
+ bitfield: None,
+ },
+ MetadataFieldInfo {
+ name: "Buffer".to_string(),
+ ty: MetadataTypeKind::Reference {
+ namespace: "Windows.Win32.Foundation".to_string(),
+ name: "PWSTR".to_string(),
+ },
+ is_const: false,
+ bitfield: None,
+ },
+ ],
+ is_packed: false,
+ },
+ namespace: "Windows.Win32.Foundation".to_string(),
+ },
+ MetadataTypeInfo {
+ name: "BOOLEAN".to_string(),
+ kind: MetadataTypeKind::Bool { size: Some(1) },
+ namespace: "Windows.Win32.Security".to_string(),
+ },
+ MetadataTypeInfo {
+ name: "BOOL".to_string(),
+ // BOOL is integer sized, not char sized like a typical bool value.
+ kind: MetadataTypeKind::Bool { size: None },
+ namespace: "Windows.Win32.Security".to_string(),
+ },
+ ]);
+
+ for entry in index.types() {
+ match entry.category() {
+ TypeCategory::Interface => {
+ let (interface_ty, interface_vtable_ty) = self.translate_interface(&entry)?;
+ types.push(interface_ty);
+ types.push(interface_vtable_ty);
+ }
+ TypeCategory::Class => {
+ let (cls_functions, cls_constants) = self.translate_class(&entry)?;
+ functions.extend(cls_functions);
+ constants.extend(cls_constants);
+ }
+ TypeCategory::Enum => {
+ types.push(self.translate_enum(&entry)?);
+ }
+ TypeCategory::Struct => {
+ // Skip marker type structures.
+ if entry.has_attribute(API_CONTRACT_ATTR) {
+ continue;
+ }
+ types.push(self.translate_struct(&entry)?);
+ }
+ TypeCategory::Delegate => {
+ types.push(self.translate_delegate(&entry)?);
+ }
+ TypeCategory::Attribute => {
+ // We will pull attributes directly from the other entries.
+ }
+ }
+ }
+
+ // Remove duplicate types within the same namespace, the first one wins. This is what allows
+ // us to override types by placing the overrides in the type list before traversing the index.
+ let mut tracked_names = HashSet::<(String, String)>::new();
+ types.retain(|ty| {
+ let ty_name = (ty.namespace.clone(), ty.name.clone());
+ tracked_names.insert(ty_name)
+ });
+
+ Ok(MetadataInfo {
+ types,
+ functions,
+ constants,
+ })
+ }
+
+ pub fn translate_struct(
+ &self,
+ structure: &windows_metadata::reader::TypeDef,
+ ) -> Result<MetadataTypeInfo, TranslationError> {
+ let mut fields = Vec::new();
+
+ let nested: Result<HashMap<String, _>, _> = structure
+ .index()
+ .nested(structure.clone())
+ .map(|n| {
+ // TODO: Are all nested fields a struct?
+ let nested_ty = self.translate_struct(&n)?;
+ Ok((n.name().to_string(), nested_ty))
+ })
+ .collect();
+ let nested = nested?;
+
+ for field in structure.fields() {
+ let mut field_ty = self.translate_type(&field.ty())?;
+ // TODO: This is kinda ugly.
+ // Handle nested structures by unwrapping the reference.
+ let mut nested_ty = None;
+ field_ty.visit_references(&mut |_, name| {
+ nested_ty = nested.get(name).cloned().map(|n| n.kind);
+ });
+ field_ty = nested_ty.unwrap_or(field_ty);
+
+ // Bitfields are special, they are a "fake" field that we need to look at the attributes of
+ // to unwrap the real fields that are contained within the storage type.
+ if field.has_attribute(BITFIELD_ATTR) {
+ for bitfield in field.attributes() {
+ let bitfield_values = bitfield.value();
+ let mut values = bitfield_values.iter();
+ let Some((_, Value::Utf8(bitfield_name))) = values.next() else {
+ continue;
+ };
+ let Some((_, Value::I64(bitfield_pos))) = values.next() else {
+ continue;
+ };
+ let Some((_, Value::I64(bitfield_width))) = values.next() else {
+ continue;
+ };
+ // is_private, is_public, is_virtual
+ fields.push(MetadataFieldInfo {
+ name: bitfield_name.clone(),
+ ty: field_ty.clone(),
+ is_const: field.has_attribute(CONST_ATTR),
+ bitfield: Some((*bitfield_pos as u8, *bitfield_width as u8)),
+ });
+ }
+ } else {
+ fields.push(MetadataFieldInfo {
+ name: field.name().to_string(),
+ ty: field_ty,
+ is_const: field.has_attribute(CONST_ATTR),
+ bitfield: None,
+ });
+ }
+ }
+
+ let mut is_packed = false;
+ if let Some(_layout) = structure.class_layout() {
+ is_packed = _layout.packing_size() == 1;
+ }
+
+ // ExplicitLayout seems to denote a union layout.
+ let kind = if structure.flags().contains(TypeAttributes::ExplicitLayout) {
+ MetadataTypeKind::Union { fields }
+ } else {
+ MetadataTypeKind::Struct { fields, is_packed }
+ };
+
+ Ok(MetadataTypeInfo {
+ name: structure.name().to_string(),
+ kind,
+ namespace: structure.namespace().to_string(),
+ })
+ }
+
+ pub fn translate_class(
+ &self,
+ class: &windows_metadata::reader::TypeDef,
+ ) -> Result<(Vec<MetadataFunctionInfo>, Vec<MetadataConstantInfo>), TranslationError> {
+ let namespace = class.namespace().to_string();
+ let mut functions = Vec::new();
+ for method in class.methods() {
+ match self.translate_method(&method) {
+ Ok(mut func) => {
+ func.namespace = namespace.clone();
+ functions.push(func);
+ }
+ Err(e) => tracing::warn!("Failed to translate method {}: {}", method.name(), e),
+ }
+ }
+
+ let mut constants = Vec::new();
+ for field in class.fields() {
+ if let Some(constant) = field
+ .constant()
+ .map(|c| self.value_to_u64(&c.value()))
+ .flatten()
+ {
+ constants.push(MetadataConstantInfo {
+ name: field.name().to_string(),
+ namespace: namespace.clone(),
+ ty: self.translate_type(&field.ty())?,
+ value: constant,
+ });
+ } else {
+ tracing::debug!("Field {} is not a constant, skipping...", field.name());
+ }
+ }
+
+ Ok((functions, constants))
+ }
+
+ pub fn translate_method(
+ &self,
+ method: &windows_metadata::reader::MethodDef,
+ ) -> Result<MetadataFunctionInfo, TranslationError> {
+ // TODO: Pass generics here? generic_params seems always empty? Even windows-rs doesn't use it.
+ let signature = method.signature(&[]);
+ let func_params: Result<Vec<MetadataParameterInfo>, TranslationError> = method
+ .params()
+ .filter(|p| !p.name().is_empty())
+ .zip(signature.types)
+ .map(|(param, param_ty)| {
+ Ok(MetadataParameterInfo {
+ name: param.name().to_string(),
+ ty: self.translate_type(&param_ty)?,
+ })
+ })
+ .collect();
+ let func_ty = MetadataTypeKind::Function {
+ params: func_params?,
+ return_type: Box::new(self.translate_type(&signature.return_type)?),
+ is_vararg: signature.flags.contains(MethodCallAttributes::VARARG),
+ };
+
+ let import_info = method
+ .impl_map()
+ .map(|impl_map| self.import_info_from_map(&impl_map));
+
+ Ok(MetadataFunctionInfo {
+ name: method.name().to_string(),
+ ty: func_ty,
+ // NOTE: This will be set by the associated class entry once returned.
+ namespace: "".to_string(),
+ import_info,
+ })
+ }
+
+ pub fn translate_delegate(
+ &self,
+ delegate: &windows_metadata::reader::TypeDef,
+ ) -> Result<MetadataTypeInfo, TranslationError> {
+ if !delegate.has_attribute(FNPTR_ATTR) {
+ return Err(TranslationError::UnsupportedAttribute(
+ FNPTR_ATTR.to_string(),
+ ));
+ }
+ let invoke_method = delegate
+ .methods()
+ .find(|m| m.name() == "Invoke")
+ .expect("Invoke method not found");
+ let translated_invoke_method = self.translate_method(&invoke_method)?;
+ Ok(MetadataTypeInfo {
+ name: delegate.name().to_string(),
+ kind: MetadataTypeKind::Pointer {
+ is_const: false,
+ is_pointee_const: false,
+ target: Box::new(translated_invoke_method.ty),
+ },
+ namespace: delegate.namespace().to_string(),
+ })
+ }
+
+ pub fn translate_interface(
+ &self,
+ interface: &windows_metadata::reader::TypeDef,
+ ) -> Result<(MetadataTypeInfo, MetadataTypeInfo), TranslationError> {
+ let mut vtable_fields = Vec::new();
+ for meth in interface.methods() {
+ let meth_ty = self.translate_method(&meth)?;
+ vtable_fields.push(MetadataFieldInfo {
+ name: meth.name().to_string(),
+ ty: MetadataTypeKind::Pointer {
+ is_const: false,
+ is_pointee_const: false,
+ target: Box::new(meth_ty.ty),
+ },
+ is_const: false,
+ bitfield: None,
+ })
+ }
+
+ let interface_ns = interface.namespace();
+ let interface_ty = MetadataTypeInfo {
+ name: interface.name().to_string(),
+ kind: MetadataTypeKind::Struct {
+ fields: vec![MetadataFieldInfo {
+ name: "vtable".to_string(),
+ ty: MetadataTypeKind::Pointer {
+ is_const: false,
+ is_pointee_const: false,
+ target: Box::new(MetadataTypeKind::Reference {
+ namespace: interface_ns.to_string(),
+ name: format!("{}VTable", interface.name()),
+ }),
+ },
+ is_const: false,
+ bitfield: None,
+ }],
+ is_packed: false,
+ },
+ namespace: interface_ns.to_string(),
+ };
+ let interface_vtable_ty = MetadataTypeInfo {
+ name: format!("{}VTable", interface.name()),
+ kind: MetadataTypeKind::Struct {
+ fields: Vec::new(),
+ is_packed: false,
+ },
+ namespace: interface_ns.to_string(),
+ };
+ Ok((interface_ty, interface_vtable_ty))
+ }
+
+ pub fn translate_enum(
+ &self,
+ _enum: &windows_metadata::reader::TypeDef,
+ ) -> Result<MetadataTypeInfo, TranslationError> {
+ let mut variants = Vec::new();
+ let mut last_constant = 0;
+ let mut enum_ty = MetadataTypeKind::Integer {
+ size: None,
+ is_signed: true,
+ };
+ for variant in _enum.fields() {
+ if variant.flags().contains(FieldAttributes::RTSpecialName) {
+ // Skip the hidden "value__" field.
+ continue;
+ }
+ // Pull the enums type from the constant if it exists.
+ // Otherwise, we will fall back to void and use a default type when importing.
+ if let Some(constant) = variant.constant() {
+ enum_ty = self.translate_type(&constant.ty())?;
+ }
+ let variant_constant = variant
+ .constant()
+ .map(|c| self.value_to_u64(&c.value()))
+ .flatten()
+ .unwrap_or(last_constant);
+ let variant_name = variant.name().to_string();
+ variants.push((variant_name, variant_constant));
+ last_constant = variant_constant;
+ }
+ Ok(MetadataTypeInfo {
+ name: _enum.name().to_string(),
+ kind: MetadataTypeKind::Enum {
+ ty: Box::new(enum_ty),
+ variants,
+ },
+ namespace: _enum.namespace().to_string(),
+ })
+ }
+
+ pub fn translate_type(&self, ty: &Type) -> Result<MetadataTypeKind, TranslationError> {
+ match ty {
+ Type::Void => Ok(MetadataTypeKind::Void),
+ Type::Bool => Ok(MetadataTypeKind::Bool { size: Some(1) }),
+ Type::Char => Ok(MetadataTypeKind::Character { size: 1 }),
+ Type::I8 => Ok(MetadataTypeKind::Integer {
+ size: Some(1),
+ is_signed: true,
+ }),
+ Type::U8 => Ok(MetadataTypeKind::Integer {
+ size: Some(1),
+ is_signed: false,
+ }),
+ Type::I16 => Ok(MetadataTypeKind::Integer {
+ size: Some(2),
+ is_signed: true,
+ }),
+ Type::U16 => Ok(MetadataTypeKind::Integer {
+ size: Some(2),
+ is_signed: false,
+ }),
+ Type::I32 => Ok(MetadataTypeKind::Integer {
+ size: Some(4),
+ is_signed: true,
+ }),
+ Type::U32 => Ok(MetadataTypeKind::Integer {
+ size: Some(4),
+ is_signed: false,
+ }),
+ Type::I64 => Ok(MetadataTypeKind::Integer {
+ size: Some(8),
+ is_signed: true,
+ }),
+ Type::U64 => Ok(MetadataTypeKind::Integer {
+ size: Some(8),
+ is_signed: false,
+ }),
+ Type::F32 => Ok(MetadataTypeKind::Float { size: 4 }),
+ Type::F64 => Ok(MetadataTypeKind::Float { size: 8 }),
+ Type::ISize => Ok(MetadataTypeKind::Integer {
+ size: None,
+ is_signed: true,
+ }),
+ Type::USize => Ok(MetadataTypeKind::Integer {
+ size: None,
+ is_signed: false,
+ }),
+ Type::Name(name) => {
+ if let Some((remapped_ns, remapped_name)) =
+ self.remapped_references.get(&(&name.namespace, &name.name))
+ {
+ Ok(MetadataTypeKind::Reference {
+ namespace: remapped_ns.to_string(),
+ name: remapped_name.to_string(),
+ })
+ } else {
+ Ok(MetadataTypeKind::Reference {
+ namespace: name.namespace.clone(),
+ name: name.name.clone(),
+ })
+ }
+ }
+ Type::PtrMut(target, _) => Ok(MetadataTypeKind::Pointer {
+ is_const: false,
+ is_pointee_const: false,
+ target: Box::new(self.translate_type(target)?),
+ }),
+ Type::PtrConst(target, _) => {
+ Ok(MetadataTypeKind::Pointer {
+ is_const: false,
+ // TODO: I think this might be pointee const?
+ is_pointee_const: true,
+ target: Box::new(self.translate_type(target)?),
+ })
+ }
+ Type::ArrayFixed(elem_ty, count) => Ok(MetadataTypeKind::Array {
+ element: Box::new(self.translate_type(elem_ty)?),
+ count: *count,
+ }),
+ other => Err(TranslationError::UnhandledType(format!("{:?}", other))),
+ }
+ }
+
+ pub fn import_info_from_map(
+ &self,
+ map: &windows_metadata::reader::ImplMap,
+ ) -> MetadataImportInfo {
+ MetadataImportInfo {
+ method: MetadataImportMethod::ByName(map.import_name().to_string()),
+ module: MetadataModuleInfo {
+ name: map.import_scope().name().to_string(),
+ },
+ }
+ }
+
+ pub fn value_to_u64(&self, value: &Value) -> Option<u64> {
+ match value {
+ Value::Bool(b) => Some(*b as u64),
+ Value::U8(i) => Some(*i as u64),
+ Value::I8(i) => Some(*i as u64),
+ Value::U16(i) => Some(*i as u64),
+ Value::I16(i) => Some(*i as u64),
+ Value::U32(i) => Some(*i as u64),
+ Value::I32(i) => Some(*i as u64),
+ Value::U64(i) => Some(*i),
+ Value::I64(i) => Some(*i as u64),
+ _ => None,
+ }
+ }
+}