summaryrefslogtreecommitdiff
path: root/plugins/bntl_utils/src/command/create.rs
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/command/create.rs
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/command/create.rs')
-rw-r--r--plugins/bntl_utils/src/command/create.rs277
1 files changed, 277 insertions, 0 deletions
diff --git a/plugins/bntl_utils/src/command/create.rs b/plugins/bntl_utils/src/command/create.rs
new file mode 100644
index 00000000..41b5517a
--- /dev/null
+++ b/plugins/bntl_utils/src/command/create.rs
@@ -0,0 +1,277 @@
+use crate::command::{InputDirectoryField, OutputDirectoryField};
+use crate::process::{new_processing_state_background_thread, TypeLibProcessor};
+use crate::validate::TypeLibValidater;
+use binaryninja::background_task::BackgroundTask;
+use binaryninja::binary_view::{BinaryView, BinaryViewExt};
+use binaryninja::command::{Command, ProjectCommand};
+use binaryninja::interaction::{Form, FormInputField, MessageBoxButtonSet, MessageBoxIcon};
+use binaryninja::platform::Platform;
+use binaryninja::project::Project;
+use binaryninja::types::TypeLibrary;
+use std::thread;
+
+pub struct CreateFromCurrentView;
+
+impl Command for CreateFromCurrentView {
+ fn action(&self, view: &BinaryView) {
+ let mut form = Form::new("Create From View");
+ // TODO: The choice to select what types to include
+ form.add_field(OutputDirectoryField::field());
+ if !form.prompt() {
+ return;
+ }
+ let output_dir = OutputDirectoryField::from_form(&form).unwrap();
+ let Some(default_platform) = view.default_platform() else {
+ tracing::error!("No default platform set for view");
+ return;
+ };
+
+ let file_path = view.file().file_path();
+ let file_name = file_path.file_name().unwrap_or_default().to_string_lossy();
+ let processor = TypeLibProcessor::new(&file_name, &default_platform.name());
+ let data = match processor.process_view(file_path, view) {
+ Ok(data) => data,
+ Err(err) => {
+ tracing::error!("Failed to process view: {}", err);
+ return;
+ }
+ }
+ .prune();
+
+ let attached_libraries = view
+ .type_libraries()
+ .iter()
+ .map(|t| t.to_owned())
+ .chain(data.type_libraries.iter().map(|t| t.to_owned()))
+ .collect::<Vec<_>>();
+ let mut validator = TypeLibValidater::new()
+ .with_platform(&default_platform)
+ .with_type_libraries(attached_libraries);
+
+ for type_library in data.type_libraries {
+ let output_path = output_dir.join(format!("{}.bntl", type_library.name()));
+
+ let validation_result = validator.validate(&type_library);
+ if !validation_result.issues.is_empty() {
+ tracing::error!(
+ "Found {} issues in type library '{}'",
+ validation_result.issues.len(),
+ type_library.name()
+ );
+ match validation_result.render_report() {
+ Ok(rendered) => {
+ view.show_html_report(&type_library.name(), &rendered, "");
+ if let Err(e) = std::fs::write(output_path.with_extension("html"), rendered)
+ {
+ tracing::error!(
+ "Failed to write validation report to {}: {}",
+ output_path.display(),
+ e
+ );
+ }
+ }
+ Err(err) => tracing::error!("Failed to render validation report: {}", err),
+ }
+ }
+
+ if type_library.write_to_file(&output_path) {
+ tracing::info!(
+ "Created type library '{}': {}",
+ type_library.name(),
+ output_path.display()
+ );
+ } else {
+ tracing::error!("Failed to write type library to {}", output_path.display());
+ }
+ }
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
+
+pub struct NameField;
+
+impl NameField {
+ pub fn field() -> FormInputField {
+ FormInputField::TextLine {
+ prompt: "Dependency Name".to_string(),
+ default: Some("foo.dll".to_string()),
+ value: None,
+ }
+ }
+
+ pub fn from_form(form: &Form) -> Option<String> {
+ let field = form.get_field_with_name("Dependency Name")?;
+ field.try_value_string()
+ }
+}
+
+pub struct PlatformField;
+
+impl PlatformField {
+ pub fn field() -> FormInputField {
+ FormInputField::TextLine {
+ prompt: "Platform Name".to_string(),
+ default: Some("windows-x86_64".to_string()),
+ value: None,
+ }
+ }
+
+ pub fn from_form(form: &Form) -> Option<String> {
+ let field = form.get_field_with_name("Platform Name")?;
+ field.try_value_string()
+ }
+}
+
+pub struct CreateFromDirectory;
+
+impl CreateFromDirectory {
+ pub fn execute() {
+ let mut form = Form::new("Create From Directory");
+ // TODO: The choice to select what types to include
+ form.add_field(InputDirectoryField::field());
+ form.add_field(PlatformField::field());
+ form.add_field(NameField::field());
+ form.add_field(OutputDirectoryField::field());
+ if !form.prompt() {
+ return;
+ }
+ let input_dir = InputDirectoryField::from_form(&form).unwrap();
+ let platform_name = PlatformField::from_form(&form).unwrap();
+ let default_name = NameField::from_form(&form).unwrap();
+ let output_dir = OutputDirectoryField::from_form(&form).unwrap();
+
+ let Some(default_platform) = Platform::by_name(&platform_name) else {
+ tracing::error!("Invalid platform name: {}", platform_name);
+ return;
+ };
+
+ let processor = TypeLibProcessor::new(&default_name, &default_platform.name());
+
+ let background_task = BackgroundTask::new("Processing started...", true);
+ new_processing_state_background_thread(background_task.clone(), processor.state());
+ let data = processor.process_directory(&input_dir);
+ background_task.finish();
+
+ let pruned_data = match data {
+ // Prune off empty type libraries, no need to save them.
+ Ok(data) => data.prune(),
+ Err(err) => {
+ binaryninja::interaction::show_message_box(
+ "Failed to process directory",
+ &err.to_string(),
+ MessageBoxButtonSet::OKButtonSet,
+ MessageBoxIcon::ErrorIcon,
+ );
+ tracing::error!("Failed to process directory: {}", err);
+ return;
+ }
+ };
+
+ for type_library in pruned_data.type_libraries {
+ // Place the type libraries in a folder with the architecture name, as that is necessary
+ // information for the user to correctly place the following type libraries in the user directory.
+ let arch_output_path = output_dir.join(type_library.arch().name());
+ let _ = std::fs::create_dir_all(&arch_output_path);
+ let output_path = arch_output_path.join(format!("{}.bntl", type_library.name()));
+ if type_library.write_to_file(&output_path) {
+ tracing::info!(
+ "Created type library '{}': {}",
+ type_library.name(),
+ output_path.display()
+ );
+ } else {
+ tracing::error!("Failed to write type library to {}", output_path.display());
+ }
+ }
+ }
+}
+
+impl Command for CreateFromDirectory {
+ fn action(&self, _view: &BinaryView) {
+ thread::spawn(move || {
+ CreateFromDirectory::execute();
+ });
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
+
+pub struct CreateFromProject;
+
+impl CreateFromProject {
+ pub fn execute(project: &Project) {
+ let mut form = Form::new("Create From Project");
+ // TODO: The choice to select what types to include
+ form.add_field(PlatformField::field());
+ form.add_field(NameField::field());
+ form.add_field(OutputDirectoryField::field());
+ if !form.prompt() {
+ return;
+ }
+ let platform_name = PlatformField::from_form(&form).unwrap();
+ let default_name = NameField::from_form(&form).unwrap();
+ let output_dir = OutputDirectoryField::from_form(&form).unwrap();
+
+ let Some(default_platform) = Platform::by_name(&platform_name) else {
+ tracing::error!("Invalid platform name: {}", platform_name);
+ return;
+ };
+
+ let processor = TypeLibProcessor::new(&default_name, &default_platform.name());
+
+ let background_task = BackgroundTask::new("Processing started...", true);
+ new_processing_state_background_thread(background_task.clone(), processor.state());
+ let data = processor.process_project(&project);
+ background_task.finish();
+
+ let mut finalized_data = match data {
+ // Prune off empty type libraries, no need to save them.
+ Ok(data) => data.finalized(&default_name),
+ Err(err) => {
+ binaryninja::interaction::show_message_box(
+ "Failed to process project",
+ &err.to_string(),
+ MessageBoxButtonSet::OKButtonSet,
+ MessageBoxIcon::ErrorIcon,
+ );
+ tracing::error!("Failed to process project: {}", err);
+ return;
+ }
+ };
+
+ for type_library in finalized_data.type_libraries {
+ // Place the type libraries in a folder with the architecture name, as that is necessary
+ // information for the user to correctly place the following type libraries in the user directory.
+ let arch_output_path = output_dir.join(type_library.arch().name());
+ let _ = std::fs::create_dir_all(&arch_output_path);
+ let output_path = arch_output_path.join(format!("{}.bntl", type_library.name()));
+ if type_library.write_to_file(&output_path) {
+ tracing::info!(
+ "Created type library '{}': {}",
+ type_library.name(),
+ output_path.display()
+ );
+ } else {
+ tracing::error!("Failed to write type library to {}", output_path.display());
+ }
+ }
+ }
+}
+
+impl ProjectCommand for CreateFromProject {
+ fn action(&self, project: &Project) {
+ let owned_project = project.to_owned();
+ thread::spawn(move || {
+ CreateFromProject::execute(&owned_project);
+ });
+ }
+
+ fn valid(&self, _project: &Project) -> bool {
+ true
+ }
+}