diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock
index 755c06f8..c05c3344 100644
--- a/tools/yabridgectl/Cargo.lock
+++ b/tools/yabridgectl/Cargo.lock
@@ -440,6 +440,7 @@ version = "1.2.1"
dependencies = [
"aho-corasick",
"clap",
+ "lazy_static",
"rayon",
"serde",
"serde_derive",
diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml
index 5594950d..f6f2c5f0 100644
--- a/tools/yabridgectl/Cargo.toml
+++ b/tools/yabridgectl/Cargo.toml
@@ -12,6 +12,7 @@ license = "GPL-3.0-or-later"
[dependencies]
aho-corasick = "0.7.13"
clap = "3.0.0-beta.1"
+lazy_static = "1.4.0"
rayon = "1.3.1"
serde = "1.0.114"
serde_derive = "1.0.114"
diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs
new file mode 100644
index 00000000..efc965e6
--- /dev/null
+++ b/tools/yabridgectl/src/files.rs
@@ -0,0 +1,106 @@
+// yabridge: a Wine VST bridge
+// Copyright (C) 2020 Robbert van der Helm
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+//! Functions to index plugins and to set up yabridge for those plugins.
+
+use aho_corasick::AhoCorasick;
+use lazy_static::lazy_static;
+use rayon::prelude::*;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use walkdir::WalkDir;
+
+/// Stores the results from searching for Windows VST plugin `.dll` files and native Linux `.so`
+/// files inside of a directory. These `.so` files are kept track of so we can report the current
+/// installation status and to be able to purge orphan files.
+#[derive(Debug)]
+pub struct SearchResults {
+ /// Absolute paths to the found VST2 `.dll` files.
+ pub vst2_files: Vec,
+ /// Absolute paths to any `.so` files inside of the directory, and whether they're a symlink or
+ /// a regular file.
+ pub so_files: Vec,
+}
+
+#[derive(Debug)]
+pub enum FoundFile {
+ Symlink(PathBuf),
+ Regular(PathBuf),
+}
+
+/// Search for Windows VST2 plugins and .so files under a directory. This will return an error if
+/// the directory does not exist, or if `winedump` could not be found.
+pub fn index(directory: &Path) -> Result {
+ // First we'll find all .dll and .so files in the directory
+ let mut dll_files: Vec = Vec::new();
+ let mut so_files: Vec = Vec::new();
+ // XXX: We're silently skipping directories and files we don't have permission to read. This
+ // sounds like the expected behavior, but I"m not entirely sure.
+ for entry in WalkDir::new(directory)
+ .follow_links(true)
+ .into_iter()
+ .filter_map(|e| e.ok())
+ .filter(|x| !x.file_type().is_dir())
+ {
+ match entry.path().extension().and_then(|os| os.to_str()) {
+ Some("dll") => dll_files.push(entry.into_path()),
+ Some("so") => {
+ if entry.path_is_symlink() {
+ so_files.push(FoundFile::Symlink(entry.into_path()));
+ } else {
+ so_files.push(FoundFile::Regular(entry.into_path()));
+ }
+ }
+ _ => (),
+ }
+ }
+
+ // Then we'll filter out any .dll files that are not VST2 plugins by checking whether the
+ // exptected entry points for VST2 plugins are present
+ lazy_static! {
+ static ref VST2_AUTOMATON: AhoCorasick =
+ AhoCorasick::new_auto_configured(&["VSTPluginMain", "main", "main_plugin"]);
+ }
+
+ let vst2_files: Vec = dll_files
+ .into_par_iter()
+ .map(|path| {
+ let exported_functions = Command::new("winedump")
+ .arg("-j")
+ .arg("export")
+ .arg(&path)
+ .output()?
+ .stdout;
+
+ Ok((path, exported_functions))
+ })
+ .filter_map(|result| match result {
+ Ok((path, exported_functions)) => {
+ if VST2_AUTOMATON.is_match(exported_functions) {
+ Some(Ok(path))
+ } else {
+ None
+ }
+ }
+ Err(err) => Some(Err(err)),
+ })
+ .collect::>()?;
+
+ Ok(SearchResults {
+ vst2_files,
+ so_files,
+ })
+}