mirror of
https://git.um-react.app/um/lib_um_crypto_rust.git
synced 2026-03-07 20:19:51 +00:00
[qtfm] feat #4: implement QingTingFM decipher
This commit is contained in:
@@ -11,6 +11,8 @@ 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_qtfm = { path = "../um_crypto/qtfm" }
|
||||
umc_xiami = { path = "../um_crypto/xiami" }
|
||||
umc_xmly = { path = "../um_crypto/xmly" }
|
||||
umc_utils = { path = "../um_crypto/utils" }
|
||||
hex = "0.4.3"
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod kgm;
|
||||
pub mod ncm;
|
||||
pub mod qmc1;
|
||||
pub mod qmc2;
|
||||
mod qtfm;
|
||||
pub mod xiami;
|
||||
pub mod xmly;
|
||||
|
||||
@@ -24,4 +25,6 @@ pub enum Commands {
|
||||
XMLY(xmly::ArgsXimalaya),
|
||||
#[command(name = "xiami")]
|
||||
Xiami(xiami::ArgsXiami),
|
||||
#[command(name = "qtfm")]
|
||||
QTFM(qtfm::ArgsQingTingFM),
|
||||
}
|
||||
|
||||
100
um_cli/src/cmd/qtfm.rs
Normal file
100
um_cli/src/cmd/qtfm.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use crate::Cli;
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Args;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufWriter, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use umc_qtfm::nonce::make_decipher_iv;
|
||||
use umc_qtfm::Decipher;
|
||||
|
||||
/// Decrypt a qta file (QingTingFM)
|
||||
#[derive(Args)]
|
||||
pub struct ArgsQingTingFM {
|
||||
/// 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,
|
||||
|
||||
/// Device info (CSV), in the order of "product,device,manufacturer,brand,board,model".
|
||||
#[clap(short, long = "device")]
|
||||
device: Option<String>,
|
||||
|
||||
/// Device key, in hex.
|
||||
#[clap(short = 'k', long = "device-key")]
|
||||
device_key: Option<String>,
|
||||
|
||||
/// Override name. default to file name.
|
||||
#[clap(short, long)]
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
impl ArgsQingTingFM {
|
||||
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 file_name = match &self.name {
|
||||
Some(x) => x.clone(),
|
||||
None => match self.input.file_name() {
|
||||
Some(x) => x.to_string_lossy().to_string(),
|
||||
None => bail!("failed to get file name"),
|
||||
},
|
||||
};
|
||||
|
||||
let device_key = self.make_device_key()?;
|
||||
let iv = make_decipher_iv(file_name)?;
|
||||
|
||||
let decipher = Decipher::new(&device_key, &iv);
|
||||
|
||||
let mut offset = 0;
|
||||
let mut buffer = vec![0u8; cli.buffer_size];
|
||||
loop {
|
||||
let n = reader.read(&mut buffer[..])?;
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
decipher.decrypt(&mut buffer[..n], offset);
|
||||
writer.write_all(&buffer[..n])?;
|
||||
offset += n;
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn make_device_key(&self) -> Result<[u8; 0x10]> {
|
||||
let key = match &self.device_key {
|
||||
Some(key) => {
|
||||
let mut result = [0u8; 0x10];
|
||||
hex::decode_to_slice(key, &mut result)?;
|
||||
result
|
||||
}
|
||||
None => match &self.device {
|
||||
Some(device) => {
|
||||
let mut device_parts = vec![];
|
||||
for item in device.split(',') {
|
||||
device_parts.push(item);
|
||||
}
|
||||
if device_parts.len() != 6 {
|
||||
bail!(
|
||||
"device needs 6 parts. current: {} part(s)",
|
||||
device_parts.len()
|
||||
);
|
||||
}
|
||||
umc_qtfm::secret::make_device_secret(
|
||||
device_parts[0],
|
||||
device_parts[1],
|
||||
device_parts[2],
|
||||
device_parts[3],
|
||||
device_parts[4],
|
||||
device_parts[5],
|
||||
)
|
||||
}
|
||||
None => bail!("one of device/device-key is required."),
|
||||
},
|
||||
};
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ fn run_command(cli: &Cli) -> Result<i32> {
|
||||
Some(Commands::JOOX(cmd)) => cmd.run(&cli),
|
||||
Some(Commands::XMLY(cmd)) => cmd.run(&cli),
|
||||
Some(Commands::Xiami(cmd)) => cmd.run(&cli),
|
||||
Some(Commands::QTFM(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");
|
||||
|
||||
Reference in New Issue
Block a user