[xmly] feat #5: implement xmly decipher

This commit is contained in:
鲁树人
2024-09-18 01:23:48 +01:00
parent 2556d04120
commit 4deb777996
16 changed files with 471 additions and 0 deletions

View File

@@ -11,4 +11,5 @@ 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_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 xmly;
#[derive(Subcommand)]
pub enum Commands {
@@ -18,4 +19,6 @@ pub enum Commands {
KGM(kgm::ArgsKGM),
#[command(name = "joox")]
JOOX(joox::ArgsJoox),
#[command(name = "xmly")]
XMLY(xmly::ArgsXimalaya),
}

94
um_cli/src/cmd/xmly.rs Normal file
View File

@@ -0,0 +1,94 @@
use crate::Cli;
use anyhow::{bail, Result};
use clap::Args;
use std::ffi::OsStr;
use std::fs::File;
use std::io;
use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
use std::path::PathBuf;
#[derive(Debug, Clone, clap::ValueEnum)]
enum XimalayaType {
/// Android: *.x2m
X2M,
/// Android: *.x3m
X3M,
/// PC: *.xm
XM,
}
/// Decrypt a X2M/X3M/XM file (Ximalaya)
#[derive(Args)]
pub struct ArgsXimalaya {
/// Path to output file, e.g. /export/Music/song.flac
#[clap(short, long)]
output: PathBuf,
/// Path to input file, e.g. /export/Music/song.x3m
#[arg(name = "input")]
input: PathBuf,
#[clap(short = 't', long = "type")]
file_type: Option<XimalayaType>,
}
impl ArgsXimalaya {
pub fn run(&self, cli: &Cli) -> Result<i32> {
let file_type = match &self.file_type {
Some(x) => x.clone(),
None => match self.input.extension().and_then(|ext: &OsStr| ext.to_str()) {
Some("x2m") => XimalayaType::X2M,
Some("x3m") => XimalayaType::X3M,
Some("xm") => XimalayaType::XM,
Some(ext) => bail!("invalid ext: {ext}"),
_ => bail!("ext not found"),
},
};
let mut read_stream = BufReader::with_capacity(cli.buffer_size, File::open(&self.input)?);
let mut write_stream =
BufWriter::with_capacity(cli.buffer_size, File::create(&self.output)?);
match file_type {
XimalayaType::X2M | XimalayaType::X3M => {
let android_type = match file_type {
XimalayaType::X2M => umc_xmly::android::FileType::X2M,
XimalayaType::X3M => umc_xmly::android::FileType::X3M,
_ => bail!("this should not happen"),
};
let mut header = [0u8; 0x400];
read_stream.read_exact(&mut header)?;
umc_xmly::android::decrypt_android(android_type, &mut header);
write_stream.write_all(&header)?;
io::copy(&mut read_stream, &mut write_stream)?;
}
XimalayaType::XM => {
let mut header = vec![0u8; 1024];
read_stream.read_exact(&mut header)?;
let xm_file = match umc_xmly::pc::Header::from_buffer(&header) {
Ok(hdr) => hdr,
Err(umc_xmly::XmlyError::MetadataTooSmall(n)) => {
let old_size = header.len();
header.resize(n, 0);
read_stream.read_exact(&mut header[old_size..])?;
umc_xmly::pc::Header::from_buffer(&header)?
}
Err(err) => bail!("failed to parse file: {err}"),
};
// Copy header
write_stream.write_all(xm_file.copy_m4a_header().as_slice())?;
// Process encrypted data
read_stream.seek(SeekFrom::Start(xm_file.data_start_offset as u64))?;
let mut header = vec![0u8; xm_file.encrypted_header_size];
read_stream.read_exact(&mut header[..])?;
write_stream.write_all(xm_file.decrypt(&mut header[..])?)?;
// Copy rest of the file
io::copy(&mut read_stream, &mut write_stream)?;
}
}
Ok(0)
}
}

View File

@@ -32,6 +32,7 @@ fn run_command(cli: &Cli) -> Result<i32> {
Some(Commands::NCM(cmd)) => cmd.run(&cli),
Some(Commands::KGM(cmd)) => cmd.run(&cli),
Some(Commands::JOOX(cmd)) => cmd.run(&cli),
Some(Commands::XMLY(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");