[yabridgectl] Add a rewriter for VST3 moduleinfo

This commit is contained in:
Robbert van der Helm
2022-05-20 00:41:40 +02:00
parent cc7988867d
commit cb0c673f58
4 changed files with 148 additions and 0 deletions
+24
View File
@@ -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",
+1
View File
@@ -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"] }
+1
View File
@@ -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
+122
View File
@@ -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 <https://www.gnu.org/licenses/>.
use anyhow::{Context, Result};
use serde_derive::{Deserialize, Serialize};
use std::fmt::Write;
/// Part of the VST3 `moduleinfo.json` file:
/// <https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/VST+Module+Architecture/ModuleInfo-JSON.html>
///
/// 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<Class>,
#[serde(rename = "Compatibility")]
compatibility_mappings: Vec<CompatibilityMapping>,
#[serde(flatten)]
other: serde_jsonrc::Map<String, serde_jsonrc::Value>,
}
/// 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<String, serde_jsonrc::Value>,
}
/// 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<String>,
// This will probably stay empty, but let's add it just in case the format changes.
#[serde(flatten)]
other: serde_jsonrc::Map<String, serde_jsonrc::Value>,
}
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
}