From cb0c673f5829258284b4a0cebe1af4a9f930ede3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 20 May 2022 00:41:40 +0200 Subject: [PATCH] [yabridgectl] Add a rewriter for VST3 moduleinfo --- tools/yabridgectl/Cargo.lock | 24 +++++ tools/yabridgectl/Cargo.toml | 1 + tools/yabridgectl/src/main.rs | 1 + tools/yabridgectl/src/vst3_moduleinfo.rs | 122 +++++++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 tools/yabridgectl/src/vst3_moduleinfo.rs diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock index 399922ea..21ef7c95 100644 --- a/tools/yabridgectl/Cargo.lock +++ b/tools/yabridgectl/Cargo.lock @@ -232,6 +232,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "lazy_static" version = "1.4.0" @@ -395,6 +401,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "ryu" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" + [[package]] name = "same-file" version = "1.0.6" @@ -427,6 +439,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_jsonrc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b591e90bcce7185aa4f8c775c504456586ae0f7df49a4087a1ee4179d402b8a8" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "strsim" version = "0.10.0" @@ -628,6 +651,7 @@ dependencies = [ "reflink", "serde", "serde_derive", + "serde_jsonrc", "textwrap 0.11.0", "toml", "walkdir", diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml index 80ae8f7f..af301b0c 100644 --- a/tools/yabridgectl/Cargo.toml +++ b/tools/yabridgectl/Cargo.toml @@ -22,6 +22,7 @@ reflink = { git = "https://github.com/nicokoch/reflink", rev = "e8d93b465f5d9ad3 rayon = "1.5.1" serde = "1.0.133" serde_derive = "1.0.133" +serde_jsonrc = "0.1" # NOTE: textwrap 0.12.0 up to at least 0.13.4 apply the subsequent indent after # wrapping textwrap = { version = "0.11.0", features = ["term_size"] } diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 8d160952..06d919ed 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -27,6 +27,7 @@ mod actions; mod config; mod files; mod utils; +mod vst3_moduleinfo; fn main() -> Result<()> { // We'll modify our `PATH` environment variable so it matches up with diff --git a/tools/yabridgectl/src/vst3_moduleinfo.rs b/tools/yabridgectl/src/vst3_moduleinfo.rs new file mode 100644 index 00000000..95251eb5 --- /dev/null +++ b/tools/yabridgectl/src/vst3_moduleinfo.rs @@ -0,0 +1,122 @@ +// yabridge: a Wine plugin bridge +// Copyright (C) 2020-2022 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 . + +use anyhow::{Context, Result}; +use serde_derive::{Deserialize, Serialize}; +use std::fmt::Write; + +/// Part of the VST3 `moduleinfo.json` file: +/// +/// +/// Since we only need this to rewrite the UIDs, all the other fields are stored in this `other` +/// map. And while this is technically supposed to be JSON5, `serde_jsonrc` can also parse trailing +/// commands and comments and works a lot better. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ModuleInfo { + #[serde(rename = "Classes")] + classes: Vec, + #[serde(rename = "Compatibility")] + compatibility_mappings: Vec, + #[serde(flatten)] + other: serde_jsonrc::Map, +} + +/// A single class object, we only care about the CID since we need to rewrite those. +#[derive(Debug, Clone, Serialize, Deserialize)] +struct Class { + #[serde(rename = "CID")] + cid: String, + #[serde(flatten)] + other: serde_jsonrc::Map, +} + +/// A mapping from old class IDs to new class IDs. +#[derive(Debug, Clone, Serialize, Deserialize)] +struct CompatibilityMapping { + #[serde(rename = "New")] + new: String, + #[serde(rename = "Old")] + old: Vec, + // This will probably stay empty, but let's add it just in case the format changes. + #[serde(flatten)] + other: serde_jsonrc::Map, +} + +impl ModuleInfo { + /// Rewrite the module info in place to switch between COM-style class ID byte orders and the + /// other style used on Linux and macOS. This is needed for cross platform plugin compatibility, + /// because someone at Steinberg was a genius. + pub fn rewrite_uid_byte_orders(&mut self) -> Result<()> { + for class in &mut self.classes { + class.cid = encode_hex_uid(&rewrite_uid_byte_order(&decode_hex_uid(&class.cid)?)); + } + + for mapping in &mut self.compatibility_mappings { + mapping.new = encode_hex_uid(&rewrite_uid_byte_order(&decode_hex_uid(&mapping.new)?)); + for cid in &mut mapping.old { + *cid = encode_hex_uid(&rewrite_uid_byte_order(&decode_hex_uid(cid)?)) + } + } + + Ok(()) + } +} + +/// Parse a hexadecimal UID from a string. Returns an error if the parsing failed. +fn decode_hex_uid(hex_uid: &str) -> Result<[u8; 16]> { + if hex_uid.len() != 32 { + anyhow::bail!("Incorrect UID hex string length: {hex_uid:?}"); + } + + // `u8::from_str_radix` only works with str slices, and there's no way to iterate over strings + // in str slices, so iterating over indices and manually slicing is the only solution ehre + let mut uid = [0; 16]; + for (idx, uid_byte) in uid.iter_mut().enumerate() { + let start_idx = idx * 2; + let end_idx = start_idx + 2; + *uid_byte = u8::from_str_radix(&hex_uid[start_idx..end_idx], 16) + .with_context(|| format!("Invalid hexadecimal string: {hex_uid:?}"))?; + } + + Ok(uid) +} + +/// Format a UID stored in a byte array as a 16 character hexadecimal string. +fn encode_hex_uid(uid: &[u8; 16]) -> String { + let mut hex_uid = String::with_capacity(uid.len() * 2); + for b in uid { + write!(&mut hex_uid, "{:02X}", b).unwrap(); + } + + hex_uid +} + +/// Switch between the COM and non-COM byte orders for a UID. +fn rewrite_uid_byte_order(old_uid: &[u8; 16]) -> [u8; 16] { + let mut new_uid = *old_uid; + + new_uid[0] = old_uid[3]; + new_uid[1] = old_uid[2]; + new_uid[2] = old_uid[1]; + new_uid[3] = old_uid[0]; + + new_uid[4] = old_uid[5]; + new_uid[5] = old_uid[4]; + new_uid[6] = old_uid[7]; + new_uid[7] = old_uid[6]; + + new_uid +}