// 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 }