Compare commits

...

3 Commits

Author SHA1 Message Date
687885b88d [wasm/xmly] feat: add wasm glue 2024-09-18 23:01:01 +01:00
6a60dec89b [xmly] chore: rename streams 2024-09-18 23:00:51 +01:00
4ca1bfe2c8 [wasm/kgm] refactor: simplify kgm interface 2024-09-18 22:02:18 +01:00
6 changed files with 95 additions and 37 deletions

1
Cargo.lock generated
View File

@ -544,6 +544,7 @@ dependencies = [
"umc_kuwo", "umc_kuwo",
"umc_ncm", "umc_ncm",
"umc_qmc", "umc_qmc",
"umc_xmly",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-test", "wasm-bindgen-test",
] ]

View File

@ -44,9 +44,8 @@ impl ArgsXimalaya {
_ => bail!("ext not found"), _ => bail!("ext not found"),
}, },
}; };
let mut read_stream = BufReader::with_capacity(cli.buffer_size, File::open(&self.input)?); let mut reader = BufReader::with_capacity(cli.buffer_size, File::open(&self.input)?);
let mut write_stream = let mut writer = BufWriter::with_capacity(cli.buffer_size, File::create(&self.output)?);
BufWriter::with_capacity(cli.buffer_size, File::create(&self.output)?);
match file_type { match file_type {
XimalayaType::X2M | XimalayaType::X3M => { XimalayaType::X2M | XimalayaType::X3M => {
@ -56,36 +55,36 @@ impl ArgsXimalaya {
_ => bail!("this should not happen"), _ => bail!("this should not happen"),
}; };
let mut header = [0u8; 0x400]; let mut header = [0u8; 0x400];
read_stream.read_exact(&mut header)?; reader.read_exact(&mut header)?;
umc_xmly::android::decrypt_android(android_type, &mut header); umc_xmly::android::decrypt_android(android_type, &mut header);
write_stream.write_all(&header)?; writer.write_all(&header)?;
io::copy(&mut read_stream, &mut write_stream)?; io::copy(&mut reader, &mut writer)?;
} }
XimalayaType::XM => { XimalayaType::XM => {
let mut header = vec![0u8; 1024]; let mut header = vec![0u8; 1024];
read_stream.read_exact(&mut header)?; reader.read_exact(&mut header)?;
let xm_file = match umc_xmly::pc::Header::from_buffer(&header) { let xm_file = match umc_xmly::pc::Header::from_buffer(&header) {
Ok(hdr) => hdr, Ok(hdr) => hdr,
Err(umc_xmly::XmlyError::MetadataTooSmall(n)) => { Err(umc_xmly::XmlyError::MetadataTooSmall(n)) => {
let old_size = header.len(); let old_size = header.len();
header.resize(n, 0); header.resize(n, 0);
read_stream.read_exact(&mut header[old_size..])?; reader.read_exact(&mut header[old_size..])?;
umc_xmly::pc::Header::from_buffer(&header)? umc_xmly::pc::Header::from_buffer(&header)?
} }
Err(err) => bail!("failed to parse file: {err}"), Err(err) => bail!("failed to parse file: {err}"),
}; };
// Copy header // Copy header
write_stream.write_all(xm_file.copy_m4a_header().as_slice())?; writer.write_all(xm_file.copy_m4a_header().as_slice())?;
// Process encrypted data // Process encrypted data
read_stream.seek(SeekFrom::Start(xm_file.data_start_offset as u64))?; reader.seek(SeekFrom::Start(xm_file.data_start_offset as u64))?;
let mut header = vec![0u8; xm_file.encrypted_header_size]; let mut header = vec![0u8; xm_file.encrypted_header_size];
read_stream.read_exact(&mut header[..])?; reader.read_exact(&mut header[..])?;
write_stream.write_all(xm_file.decrypt(&mut header[..])?)?; writer.write_all(xm_file.decrypt(&mut header[..])?)?;
// Copy rest of the file // Copy rest of the file
io::copy(&mut read_stream, &mut write_stream)?; io::copy(&mut reader, &mut writer)?;
} }
} }

View File

@ -28,6 +28,7 @@ umc_kgm = { path = "../um_crypto/kgm" }
umc_kuwo = { path = "../um_crypto/kuwo" } umc_kuwo = { path = "../um_crypto/kuwo" }
umc_ncm = { path = "../um_crypto/ncm" } umc_ncm = { path = "../um_crypto/ncm" }
umc_qmc = { path = "../um_crypto/qmc" } umc_qmc = { path = "../um_crypto/qmc" }
umc_xmly = { path = "../um_crypto/xmly" }
um_audio = { path = "../um_audio" } um_audio = { path = "../um_audio" }
[dev-dependencies] [dev-dependencies]

View File

@ -2,32 +2,18 @@ use umc_kgm::{header::Header, Decipher};
use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsError; use wasm_bindgen::JsError;
/// KuGou KGM file header.
#[wasm_bindgen(js_name=KuGouHeader)]
pub struct JsKuGouHeader(Header);
#[wasm_bindgen(js_class = KuGouHeader)]
impl JsKuGouHeader {
/// Parse the KuGou header (0x400 bytes)
pub fn parse(header: &[u8]) -> Result<JsKuGouHeader, JsError> {
Ok(JsKuGouHeader(
Header::from_buffer(header).map_err(JsError::from)?,
))
}
}
/// KuGou KGM file decipher. /// KuGou KGM file decipher.
#[wasm_bindgen(js_name=KuGouDecipher)] #[wasm_bindgen(js_name=KuGou)]
pub struct JsKuGouDecipher(Decipher); pub struct JsKuGou(Decipher);
#[wasm_bindgen(js_class=KuGouDecipher)] #[wasm_bindgen(js_class = KuGou)]
impl JsKuGouDecipher { impl JsKuGou {
/// Create an instance of cipher (decipher) for decryption /// Parse the KuGou header (0x400 bytes)
#[wasm_bindgen(constructor)] pub fn from_header(header: &[u8]) -> Result<JsKuGou, JsError> {
pub fn new(header: &JsKuGouHeader) -> Result<JsKuGouDecipher, JsError> { let header = Header::from_buffer(header).map_err(JsError::from)?;
Ok(JsKuGouDecipher( let decipher = Decipher::new(&header).map_err(JsError::from)?;
Decipher::new(&header.0).map_err(JsError::from)?,
)) Ok(JsKuGou(decipher))
} }
/// Decrypt a buffer. /// Decrypt a buffer.

View File

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

View File

@ -0,0 +1,70 @@
use std::convert::TryInto;
use umc_xmly::android::{decrypt_android, FileType};
use umc_xmly::pc::Header as PCHeader;
use umc_xmly::XmlyError;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsError;
/// Decrypt X2M Header
#[wasm_bindgen(js_name=decryptX2MHeader)]
pub fn js_decrypt_x2m_header(buffer: &mut [u8]) -> Result<(), JsError> {
decrypt_android(FileType::X2M, buffer.try_into().map_err(JsError::from)?);
Ok(())
}
/// Decrypt X3M Header
#[wasm_bindgen(js_name=decryptX3MHeader)]
pub fn js_decrypt_x3m_header(buffer: &mut [u8]) -> Result<(), JsError> {
decrypt_android(FileType::X3M, buffer.try_into().map_err(JsError::from)?);
Ok(())
}
/// Ximalaya PC Decipher.
#[wasm_bindgen(js_name=XmlyPC)]
pub struct JsXmlyPC(PCHeader);
#[wasm_bindgen(js_class=XmlyPC)]
impl JsXmlyPC {
/// Get required bytes for the header, or throw error if not valid XM file.
#[wasm_bindgen(js_name = "getHeaderSize")]
pub fn get_header_size(buffer: &[u8]) -> Result<usize, JsError> {
let required_len = match PCHeader::from_buffer(buffer) {
Ok(hdr) => hdr.data_start_offset,
Err(XmlyError::MetadataTooSmall(n)) => n,
Err(err) => Err(JsError::from(err))?,
};
Ok(required_len)
}
/// Create a new XmlyPC decipher
#[wasm_bindgen(constructor)]
pub fn new(header: &[u8]) -> Result<JsXmlyPC, JsError> {
let hdr = PCHeader::from_buffer(header)?;
Ok(Self(hdr))
}
/// Get the first few bytes of the header.
#[wasm_bindgen(getter, js_name=audioHeader)]
pub fn get_audio_header(&self) -> Vec<u8> {
self.0.copy_m4a_header()
}
/// Get the offset where the encrypted header is
#[wasm_bindgen(getter, js_name=encryptedHeaderOffset)]
pub fn get_encrypted_header_offset(&self) -> usize {
self.0.data_start_offset
}
/// Get the size of encrypted header
#[wasm_bindgen(getter, js_name=encryptedHeaderSize)]
pub fn get_encrypted_header_len(&self) -> usize {
self.0.encrypted_header_size
}
/// Decrypt encrypted header
pub fn decrypt(&self, buffer: &mut [u8]) -> Result<usize, JsError> {
let size = self.0.decrypt(buffer)?.len();
Ok(size)
}
}