diff --git a/.cargo/config.toml b/.cargo/config.toml index 35033f3..dc382b6 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ rustflags = ["--cfg=web_sys_unstable_apis"] # Enable for testing WASM-only stuff [build] -#target = "wasm32-unknown-unknown" +target = "wasm32-unknown-unknown" diff --git a/Cargo.toml b/Cargo.toml index aecea99..e72af94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,13 +45,16 @@ getrandom = { version = "0.2", features = ["js"] } des = "0.8" cbc = "0.1" ecb = "0.1" -tokio = { version = "1.36", features = ["sync"] } g2-unicode-jp = "0.4.1" thiserror = "1.0.57" phf = { version = "0.11.2", features = ["phf_macros", "macros"] } byteorder = "1.5.0" log = "0.4.22" +[target.'cfg(not(target_family = "wasm"))'.dependencies] +tokio = { version = "1.36", features = ["sync"] } + [target.'cfg(target_family = "wasm")'.dependencies] -gloo = { version = "0.11.0", features = ["futures", "worker"] } -futures = "0.3.30" +gloo = { version = "0.11", features = ["futures", "worker"] } +futures = "0.3" +serde = { version = "1.0", features = ["derive"] } diff --git a/src/netmd/encryption.rs b/src/netmd/encryption.rs index fb1b9ab..2c2f25a 100644 --- a/src/netmd/encryption.rs +++ b/src/netmd/encryption.rs @@ -1,21 +1,28 @@ use cbc::cipher::block_padding::NoPadding; use cbc::cipher::{BlockDecryptMut, BlockEncryptMut, KeyInit, KeyIvInit}; use rand::RngCore; +use super::interface::DataEncryptorInput; + +#[cfg(not(target_family = "wasm"))] use std::thread; +#[cfg(not(target_family = "wasm"))] use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; -use super::interface::DataEncryptorInput; +#[cfg(target_family = "wasm")] +use futures::{SinkExt, StreamExt}; +#[cfg(target_family = "wasm")] +use gloo::worker::reactor::{reactor, ReactorBridge, ReactorScope}; type DesEcbEnc = ecb::Decryptor<des::Des>; type DesCbcEnc = cbc::Encryptor<des::Des>; +#[cfg(not(target_family = "wasm"))] pub fn new_thread_encryptor( - _input: DataEncryptorInput, + input: DataEncryptorInput, ) -> UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)> { let (tx, rx) = unbounded_channel::<(Vec<u8>, Vec<u8>, Vec<u8>)>(); - let input = Box::from(_input); - thread::spawn(move || { + let _ = thread::spawn(move || { let mut iv = [0u8; 8]; // Create the random key @@ -76,3 +83,81 @@ pub fn new_thread_encryptor( rx } + + +#[cfg(target_family = "wasm")] +pub fn web_worker_encryptor( + input: DataEncryptorInput, +) -> ReactorBridge<WebThread> { + use gloo::worker::Spawnable; + + let bridge = WebThread::spawner().spawn("..."); + bridge.send_input(input); + + bridge +} + +#[cfg(target_family = "wasm")] +#[reactor] +pub async fn WebThread( + mut scope: ReactorScope<DataEncryptorInput, (Vec<u8>, Vec<u8>, Vec<u8>)> +) { + // Get the initial input data + let input = scope.next().await.unwrap(); + + let mut iv = [0u8; 8]; + + // Create the random key + let mut random_key = [0u8; 8]; + rand::thread_rng().fill_bytes(&mut random_key); + + // Encrypt it with the kek + let mut encrypted_random_key = random_key; + if let Err(x) = DesEcbEnc::new(&input.kek.into()) + .decrypt_padded_mut::<NoPadding>(&mut encrypted_random_key) + { + panic!("Cannot create main key {:?}", x) + }; + + let default_chunk_size = match input.chunk_size { + 0 => 0x00100000, + e => e, + }; + + let mut packet_count = 0u32; + let mut current_chunk_size; + + let mut input_data = input.data.clone(); + if (input_data.len() % input.frame_size) != 0 { + let padding_remaining = input.frame_size - (input_data.len() % input.frame_size); + input_data.extend(std::iter::repeat(0).take(padding_remaining)); + } + let input_data_length = input_data.len(); + + let mut offset: usize = 0; + while offset < input_data_length { + if packet_count > 0 { + current_chunk_size = default_chunk_size; + } else { + current_chunk_size = default_chunk_size - 24; + } + + current_chunk_size = std::cmp::min(current_chunk_size, input_data_length - offset); + + let this_data_chunk = &mut input_data[offset..offset + current_chunk_size]; + DesCbcEnc::new(&random_key.into(), &iv.into()) + .encrypt_padded_mut::<NoPadding>(this_data_chunk, current_chunk_size) + .unwrap(); + + scope.send(( + encrypted_random_key.to_vec(), + iv.to_vec(), + this_data_chunk.to_vec(), + )).await.unwrap(); + + iv.copy_from_slice(&this_data_chunk[this_data_chunk.len() - 8..]); + + packet_count += 1; + offset += current_chunk_size; + } +} diff --git a/src/netmd/interface.rs b/src/netmd/interface.rs index 6057276..1bcbc27 100644 --- a/src/netmd/interface.rs +++ b/src/netmd/interface.rs @@ -15,7 +15,6 @@ use std::collections::HashMap; use std::error::Error; use std::time::Duration; use thiserror::Error; -use tokio::sync::mpsc::UnboundedReceiver; use super::base::NetMD; use super::utils::{cross_sleep, to_sjis}; @@ -1691,7 +1690,10 @@ impl NetMDInterface { frames: u32, pkt_size: u32, // key, iv, data - mut packets: UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)>, + #[cfg(not(target_family = "wasm"))] + mut packets: tokio::sync::mpsc::UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)>, + #[cfg(target_family = "wasm")] + mut packets: gloo::worker::reactor::ReactorBridge<super::encryption::WebThread>, hex_session_key: &[u8], progress_callback: F, ) -> Result<(u16, Vec<u8>, Vec<u8>), InterfaceError> @@ -1730,7 +1732,15 @@ impl NetMDInterface { let mut written_bytes = 0; let mut packet_count = 0; - while let Some((key, iv, data)) = packets.recv().await { + while let Some((key, iv, data)) = { + { + #[cfg(not(target_family = "wasm"))] + packets.recv().await + } + + #[cfg(target_family = "wasm")] + futures::StreamExt::next(&mut packets).await + } { let binpack = if packet_count == 0 { let packed_length: Vec<u8> = pkt_size.to_be_bytes().to_vec(); [vec![0, 0, 0, 0], packed_length, key, iv, data].concat() @@ -1742,7 +1752,6 @@ impl NetMDInterface { packet_count += 1; (progress_callback)(total_bytes, written_bytes); if total_bytes == written_bytes { - packets.close(); break; } } @@ -1863,12 +1872,9 @@ pub struct MDTrack { pub data: Vec<u8>, pub chunk_size: usize, pub full_width_title: Option<String>, - - #[allow(clippy::type_complexity)] - pub encrypt_packets_iterator: - Box<dyn Fn(DataEncryptorInput) -> UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)>>, } +#[cfg_attr(target_family = "wasm", derive(serde::Serialize, serde::Deserialize))] pub struct DataEncryptorInput { pub kek: [u8; 8], pub frame_size: usize, @@ -1921,8 +1927,19 @@ impl MDTrack { [0x14, 0xe3, 0x83, 0x4e, 0xe2, 0xd3, 0xcc, 0xa5] } - pub fn get_encrypting_iterator(&mut self) -> UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)> { - (self.encrypt_packets_iterator)(DataEncryptorInput { + #[cfg(not(target_family = "wasm"))] + pub fn get_encrypting_iterator(&mut self) -> tokio::sync::mpsc::UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)> { + super::encryption::new_thread_encryptor(DataEncryptorInput { + kek: self.get_kek(), + frame_size: self.frame_size(), + chunk_size: self.chunk_size(), + data: std::mem::take(&mut self.data), + }) + } + + #[cfg(target_family = "wasm")] + pub fn get_encrypting_iterator(&mut self) -> gloo::worker::reactor::ReactorBridge<super::encryption::WebThread> { + super::encryption::web_worker_encryptor(DataEncryptorInput { kek: self.get_kek(), frame_size: self.frame_size(), chunk_size: self.chunk_size(),