From 4a845ec9529071202b326ee869f2338beb504ab1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 20 May 2022 01:03:27 +0200 Subject: [PATCH] [yabridgectl] Convert VST 3.7.5 moduleinfo files --- CHANGELOG.md | 2 ++ tools/yabridgectl/src/actions.rs | 32 ++++++++++++++++++++++++++++++++ tools/yabridgectl/src/files.rs | 27 +++++++++++++++++++++++++++ tools/yabridgectl/src/utils.rs | 13 +++++++++++++ 4 files changed, 74 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d2d2115..cd90769c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,8 @@ Versioning](https://semver.org/spec/v2.0.0.html). once after updating to yabridge 4.0, yabridge can now be updated without needing to rerun `yabridgectl sync`. This is particularly useful when using a distro packaged version of yabridge. +- Added support for the new VST 3.7.5 `moduleinfo.json` format to allow + VST3 plugins to replace other VST3 and VST2 plugins with different class IDs. - `yabridgectl status` now shows the locations where bridged VST2 and VST3 plugins will be set up. - `yabridgectl sync --prune` now also considers broken symlinks. diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 18d5597a..66b0abc4 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -29,6 +29,7 @@ use crate::config::{ use crate::files::{self, NativeFile, Plugin, Vst2Plugin}; use crate::utils::{self, get_file_type}; use crate::utils::{verify_path_setup, verify_wine_setup}; +use crate::vst3_moduleinfo::ModuleInfo; pub mod blacklist; @@ -471,6 +472,37 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { managed_vst3_bundle_files.insert(target_resources_dir); } + // If the plugin has a VST 3.7.10 moduleinfo file, then we'll rewrite the byte + // orders of the class IDs stored within the file and then write it to the + // bridged VST3 bundle. + // https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/VST+Module+Architecture/ModuleInfo-JSON.html + if let Some(original_moduleinfo_path) = module.original_moduleinfo_path() { + let target_moduleinfo_path = module.target_moduleinfo_path(); + + let result = utils::read_to_string(&original_moduleinfo_path) + .and_then(|module_info_json| { + serde_jsonrc::from_str(&module_info_json) + .context("Could not parse JSON file") + }) + .and_then(|mut module_info: ModuleInfo| { + module_info.rewrite_uid_byte_orders()?; + Ok(module_info) + }) + .and_then(|converted_module_info| { + let converted_json = + serde_jsonrc::to_string_pretty(&converted_module_info) + .context("Could not format JSON file")?; + utils::write(target_moduleinfo_path, converted_json) + }); + if let Err(error) = result { + eprintln!( + "Error converting '{}', skipping...\n{}", + original_moduleinfo_path.display(), + error + ); + } + } + module.original_path().to_path_buf() } }; diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index 49257ec1..392e920f 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -217,6 +217,24 @@ impl Vst3Module { } } + /// Return the path to the Windows plugin's `moduleinfo.json` file if this is a bundle-style + /// plugin and the plugin has one. This file would need to be rewritten using + /// `ModuleInfo::rewrite_uid_byte_orders()` first. + pub fn original_moduleinfo_path(&self) -> Option { + match &self.module { + Vst3ModuleType::Bundle(bundle_home) => { + let mut path = bundle_home.join("Contents"); + path.push("moduleinfo.json"); + if path.exists() { + Some(path) + } else { + None + } + } + Vst3ModuleType::Legacy(_) => None, + } + } + /// Get the path to the bundle in `~/.vst3` corresponding to the bridged version of this module. /// We will try to recreate the original subdirectory structure so plugins are still grouped by /// manufacturer. @@ -283,6 +301,15 @@ impl Vst3Module { path } + /// If the Windows VST3 plugin had a `moduleinfo.json` file, then it should be translated using + /// `ModuleInfo::rewrite_uid_byte_orders()` and then written to this path. + pub fn target_moduleinfo_path(&self) -> PathBuf { + let mut path = self.target_bundle_home(); + path.push("Contents"); + path.push("moduleinfo.json"); + path + } + /// Get a textual representation of the module type. Used in `yabridgectl status`. pub fn type_str(&self) -> &str { match &self.module { diff --git a/tools/yabridgectl/src/utils.rs b/tools/yabridgectl/src/utils.rs index 38323a50..e3b250ca 100644 --- a/tools/yabridgectl/src/utils.rs +++ b/tools/yabridgectl/src/utils.rs @@ -61,6 +61,13 @@ pub fn create_dir_all>(path: P) -> Result<()> { }) } +/// Wrapper around [`std::fs::read_to_string()`](std::fs::read_to_string) with a human readable +/// error message. +pub fn read_to_string>(path: P) -> Result { + fs::read_to_string(&path) + .with_context(|| format!("Could not read file '{}'", path.as_ref().display())) +} + /// Wrapper around [`std::fs::remove_dir_all()`](std::fs::remove_dir_all) with a human readable /// error message. pub fn remove_dir_all>(path: P) -> Result<()> { @@ -87,6 +94,12 @@ pub fn symlink, Q: AsRef>(src: P, dst: Q) -> Result<()> { }) } +/// Wrapper around [`std::fs::write()`](std::fs::write) with a human readable error message. +pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> { + fs::write(&path, contents) + .with_context(|| format!("Could write to '{}'", path.as_ref().display())) +} + /// Get the architecture of the ELF file at `path`. This detection is a bit naive, but we'd rather /// not depend on `libmagic` or `libreadelf` just for this, since encountering a 32-bit yabridge /// library is going to be incredibly rare.