[xiami] feat: implement xiami decipher

This commit is contained in:
鲁树人 2024-09-18 23:33:22 +01:00
parent 687885b88d
commit 1800f1b627
11 changed files with 140 additions and 0 deletions

View File

@ -13,6 +13,7 @@
<sourceFolder url="file://$MODULE_DIR$/um_crypto/kgm/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/um_crypto/joox/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/um_crypto/xmly/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/um_crypto/xiami/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
<excludeFolder url="file://$MODULE_DIR$/um_wasm_loader/dist" />
<excludeFolder url="file://$MODULE_DIR$/um_wasm_loader/pkg" />

9
Cargo.lock generated
View File

@ -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"

View File

@ -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" }

View File

@ -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),
}

44
um_cli/src/cmd/xiami.rs Normal file
View File

@ -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<i32> {
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)
}
}

View File

@ -33,6 +33,7 @@ fn run_command(cli: &Cli) -> Result<i32> {
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");

View File

@ -0,0 +1,7 @@
[package]
name = "umc_xiami"
version = "0.1.0"
edition = "2021"
[dependencies]
thiserror = "1.0.63"

View File

@ -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<Self, XiamiError> {
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);
}
}
}

View File

@ -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" }

View File

@ -4,4 +4,5 @@ pub mod kgm;
pub mod kuwo;
pub mod ncm;
pub mod qmc;
mod xiami;
pub mod xmly;

View File

@ -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<JsXiami, JsError> {
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
}
}