From 1800f1b627b13bbc705d00ba05ab27aa9b9084e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=81=E6=A0=91=E4=BA=BA?= Date: Wed, 18 Sep 2024 23:33:22 +0100 Subject: [PATCH] [xiami] feat: implement xiami decipher --- .idea/lib_um_crypto.iml | 1 + Cargo.lock | 9 ++++++++ um_cli/Cargo.toml | 1 + um_cli/src/cmd/mod.rs | 3 +++ um_cli/src/cmd/xiami.rs | 44 +++++++++++++++++++++++++++++++++++ um_cli/src/main.rs | 1 + um_crypto/xiami/Cargo.toml | 7 ++++++ um_crypto/xiami/src/lib.rs | 45 ++++++++++++++++++++++++++++++++++++ um_wasm/Cargo.toml | 1 + um_wasm/src/exports/mod.rs | 1 + um_wasm/src/exports/xiami.rs | 27 ++++++++++++++++++++++ 11 files changed, 140 insertions(+) create mode 100644 um_cli/src/cmd/xiami.rs create mode 100644 um_crypto/xiami/Cargo.toml create mode 100644 um_crypto/xiami/src/lib.rs create mode 100644 um_wasm/src/exports/xiami.rs diff --git a/.idea/lib_um_crypto.iml b/.idea/lib_um_crypto.iml index 0148a35..6af76e9 100644 --- a/.idea/lib_um_crypto.iml +++ b/.idea/lib_um_crypto.iml @@ -13,6 +13,7 @@ + diff --git a/Cargo.lock b/Cargo.lock index 1302607..50d30f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -528,6 +528,7 @@ dependencies = [ "umc_ncm", "umc_qmc", "umc_utils", + "umc_xiami", "umc_xmly", ] @@ -544,6 +545,7 @@ dependencies = [ "umc_kuwo", "umc_ncm", "umc_qmc", + "umc_xiami", "umc_xmly", "wasm-bindgen", "wasm-bindgen-test", @@ -623,6 +625,13 @@ dependencies = [ "md-5", ] +[[package]] +name = "umc_xiami" +version = "0.1.0" +dependencies = [ + "thiserror", +] + [[package]] name = "umc_xmly" version = "0.1.0" diff --git a/um_cli/Cargo.toml b/um_cli/Cargo.toml index a4ca510..a92d285 100644 --- a/um_cli/Cargo.toml +++ b/um_cli/Cargo.toml @@ -11,5 +11,6 @@ umc_kgm = { path = "../um_crypto/kgm" } umc_kuwo = { path = "../um_crypto/kuwo" } umc_ncm = { path = "../um_crypto/ncm" } umc_qmc = { path = "../um_crypto/qmc" } +umc_xiami = { path = "../um_crypto/xiami" } umc_xmly = { path = "../um_crypto/xmly" } umc_utils = { path = "../um_crypto/utils" } diff --git a/um_cli/src/cmd/mod.rs b/um_cli/src/cmd/mod.rs index 1b19bfa..3ae580b 100644 --- a/um_cli/src/cmd/mod.rs +++ b/um_cli/src/cmd/mod.rs @@ -5,6 +5,7 @@ pub mod kgm; pub mod ncm; pub mod qmc1; pub mod qmc2; +pub mod xiami; pub mod xmly; #[derive(Subcommand)] @@ -21,4 +22,6 @@ pub enum Commands { JOOX(joox::ArgsJoox), #[command(name = "xmly")] XMLY(xmly::ArgsXimalaya), + #[command(name = "xiami")] + Xiami(xiami::ArgsXiami), } diff --git a/um_cli/src/cmd/xiami.rs b/um_cli/src/cmd/xiami.rs new file mode 100644 index 0000000..eaae205 --- /dev/null +++ b/um_cli/src/cmd/xiami.rs @@ -0,0 +1,44 @@ +use crate::Cli; +use anyhow::Result; +use clap::Args; +use std::fs::File; +use std::io; +use std::io::{BufReader, BufWriter, Read, Write}; +use std::path::PathBuf; + +/// Decrypt a XM file (Xiami) +#[derive(Args)] +pub struct ArgsXiami { + /// Path to output file, e.g. /export/Music/song.flac + #[clap(short, long)] + output: PathBuf, + + /// Path to input file, e.g. /export/Music/song.xm + #[arg(name = "input")] + input: PathBuf, +} + +impl ArgsXiami { + pub fn run(&self, cli: &Cli) -> Result { + let mut reader = BufReader::with_capacity(cli.buffer_size, File::open(&self.input)?); + let mut writer = BufWriter::with_capacity(cli.buffer_size, File::create(&self.output)?); + + let mut header = [0u8; 0x10]; + reader.read_exact(&mut header)?; + let xm = umc_xiami::XiamiFile::from_header(&header)?; + let mut copy_reader = (&mut reader).take(xm.copy_len as u64); + io::copy(&mut copy_reader, &mut writer)?; + + let mut buffer = vec![0u8; cli.buffer_size]; + loop { + let n = reader.read(&mut buffer[..])?; + if n == 0 { + break; + } + xm.decrypt(&mut buffer[..n]); + writer.write_all(&buffer[..n])?; + } + + Ok(0) + } +} diff --git a/um_cli/src/main.rs b/um_cli/src/main.rs index 1f0caed..75716b9 100644 --- a/um_cli/src/main.rs +++ b/um_cli/src/main.rs @@ -33,6 +33,7 @@ fn run_command(cli: &Cli) -> Result { Some(Commands::KGM(cmd)) => cmd.run(&cli), Some(Commands::JOOX(cmd)) => cmd.run(&cli), Some(Commands::XMLY(cmd)) => cmd.run(&cli), + Some(Commands::Xiami(cmd)) => cmd.run(&cli), None => { // https://github.com/clap-rs/clap/issues/3857#issuecomment-1161796261 todo!("implement a sensible default command, similar to um/cli"); diff --git a/um_crypto/xiami/Cargo.toml b/um_crypto/xiami/Cargo.toml new file mode 100644 index 0000000..21d17d7 --- /dev/null +++ b/um_crypto/xiami/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "umc_xiami" +version = "0.1.0" +edition = "2021" + +[dependencies] +thiserror = "1.0.63" diff --git a/um_crypto/xiami/src/lib.rs b/um_crypto/xiami/src/lib.rs new file mode 100644 index 0000000..03b838f --- /dev/null +++ b/um_crypto/xiami/src/lib.rs @@ -0,0 +1,45 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum XiamiError { + #[error("header too small, require at least {0} bytes")] + HeaderTooSmall(usize), + + #[error("not a xiami file")] + NotXiamiFile, +} + +pub struct XiamiFile { + pub copy_len: usize, + pub format: [u8; 4], + key: u8, +} + +impl XiamiFile { + pub fn from_header(buffer: &[u8]) -> Result { + if buffer.len() < 0x10 { + Err(XiamiError::HeaderTooSmall(0x10))?; + } + + let (format, copy_len, key) = match buffer[..0x10] { + [b'i', b'f', b'm', b't', f1, f2, f3, f4, 0xfe, 0xfe, 0xfe, 0xfe, a, b, c, key] => { + let copy_len = (a as usize) | ((b as usize) << 8) | ((c as usize) << 16); + let format = [f1, f2, f3, f4]; + (format, copy_len, key.wrapping_sub(1)) + } + _ => Err(XiamiError::NotXiamiFile)?, + }; + + Ok(Self { + copy_len, + format, + key, + }) + } + + pub fn decrypt(&self, buffer: &mut [u8]) { + for b in buffer.iter_mut() { + *b = self.key.wrapping_sub(*b); + } + } +} diff --git a/um_wasm/Cargo.toml b/um_wasm/Cargo.toml index 08def2b..0a24e90 100644 --- a/um_wasm/Cargo.toml +++ b/um_wasm/Cargo.toml @@ -28,6 +28,7 @@ umc_kgm = { path = "../um_crypto/kgm" } umc_kuwo = { path = "../um_crypto/kuwo" } umc_ncm = { path = "../um_crypto/ncm" } umc_qmc = { path = "../um_crypto/qmc" } +umc_xiami = { path = "../um_crypto/xiami" } umc_xmly = { path = "../um_crypto/xmly" } um_audio = { path = "../um_audio" } diff --git a/um_wasm/src/exports/mod.rs b/um_wasm/src/exports/mod.rs index e29a581..62c622c 100644 --- a/um_wasm/src/exports/mod.rs +++ b/um_wasm/src/exports/mod.rs @@ -4,4 +4,5 @@ pub mod kgm; pub mod kuwo; pub mod ncm; pub mod qmc; +mod xiami; pub mod xmly; diff --git a/um_wasm/src/exports/xiami.rs b/um_wasm/src/exports/xiami.rs new file mode 100644 index 0000000..f4882e7 --- /dev/null +++ b/um_wasm/src/exports/xiami.rs @@ -0,0 +1,27 @@ +use umc_xiami::XiamiFile; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsError; + +/// Xiami XM file decipher. +#[wasm_bindgen(js_name=Xiami)] +pub struct JsXiami(XiamiFile); + +#[wasm_bindgen(js_class = Xiami)] +impl JsXiami { + /// Parse the Xiami header (0x400 bytes) + pub fn from_header(header: &[u8]) -> Result { + let hdr = XiamiFile::from_header(header)?; + Ok(JsXiami(hdr)) + } + + /// Decrypt encrypted buffer part. + pub fn decrypt(&self, buffer: &mut [u8]) { + self.0.decrypt(buffer) + } + + /// After header (0x10 bytes), the number of bytes should be copied without decryption. + #[wasm_bindgen(getter, js_name=copyPlainLength)] + pub fn get_copy_plain_length(&self) -> usize { + self.0.copy_len + } +}