mirror of
https://github.com/G2-Games/minidisc-cli.git
synced 2025-04-19 11:42:53 -05:00
Compare commits
7 commits
3646a4ac05
...
8b193bd16c
Author | SHA1 | Date | |
---|---|---|---|
8b193bd16c | |||
4fa7b0ec30 | |||
ac29c6a0a5 | |||
c49a86ed75 | |||
391d0c8599 | |||
885e7508fd | |||
0a75b6a013 |
5 changed files with 180 additions and 56 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "minidisc"
|
name = "minidisc"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
homepage = "https://github.com/G2-Games/minidisc-rs/"
|
homepage = "https://github.com/G2-Games/minidisc-rs/"
|
||||||
repository = "https://github.com/G2-Games/minidisc-rs/"
|
repository = "https://github.com/G2-Games/minidisc-rs/"
|
||||||
|
@ -37,7 +37,7 @@ nofmt = "1.0.0"
|
||||||
once_cell = "1.18.0"
|
once_cell = "1.18.0"
|
||||||
unicode-normalization = "0.1.22"
|
unicode-normalization = "0.1.22"
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
cross_usb = "0.3"
|
cross_usb = "0.4"
|
||||||
num-derive = "0.4.2"
|
num-derive = "0.4.2"
|
||||||
num-traits = "0.2.14"
|
num-traits = "0.2.14"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
|
@ -6,7 +6,7 @@ use thiserror::Error;
|
||||||
|
|
||||||
// USB stuff
|
// USB stuff
|
||||||
use cross_usb::prelude::*;
|
use cross_usb::prelude::*;
|
||||||
use cross_usb::usb::{ControlIn, ControlOut, ControlType, Recipient, UsbError};
|
use cross_usb::usb::{ControlIn, ControlOut, ControlType, Recipient, Error};
|
||||||
use cross_usb::{DeviceInfo, Interface};
|
use cross_usb::{DeviceInfo, Interface};
|
||||||
|
|
||||||
use super::utils::cross_sleep;
|
use super::utils::cross_sleep;
|
||||||
|
@ -114,7 +114,7 @@ pub enum NetMDError {
|
||||||
UnknownDevice(DeviceId),
|
UnknownDevice(DeviceId),
|
||||||
|
|
||||||
#[error("usb connection error")]
|
#[error("usb connection error")]
|
||||||
UsbError(#[from] UsbError),
|
UsbError(#[from] Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A low-level USB connection to a NetMD device.
|
/// A low-level USB connection to a NetMD device.
|
||||||
|
|
|
@ -549,7 +549,6 @@ impl NetMDContext {
|
||||||
/// # tokio_test::block_on(async {
|
/// # tokio_test::block_on(async {
|
||||||
/// use minidisc::netmd::DEVICE_IDS_CROSSUSB;
|
/// use minidisc::netmd::DEVICE_IDS_CROSSUSB;
|
||||||
/// use minidisc::netmd::NetMDContext;
|
/// use minidisc::netmd::NetMDContext;
|
||||||
/// use minidisc::netmd::encryption::new_thread_encryptor;
|
|
||||||
/// use minidisc::netmd::interface::{MDTrack, NetMDInterface};
|
/// use minidisc::netmd::interface::{MDTrack, NetMDInterface};
|
||||||
///
|
///
|
||||||
/// // Get the minidisc device from cross_usb
|
/// // Get the minidisc device from cross_usb
|
||||||
|
@ -571,7 +570,6 @@ impl NetMDContext {
|
||||||
/// format: minidisc::netmd::interface::WireFormat::LP2,
|
/// format: minidisc::netmd::interface::WireFormat::LP2,
|
||||||
/// full_width_title: None,
|
/// full_width_title: None,
|
||||||
/// data: track_contents,
|
/// data: track_contents,
|
||||||
/// encrypt_packets_iterator: Box::new(new_thread_encryptor),
|
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// // Download it to the player!
|
/// // Download it to the player!
|
||||||
|
|
|
@ -9,11 +9,27 @@ use super::interface::DataEncryptorInput;
|
||||||
type DesEcbEnc = ecb::Decryptor<des::Des>;
|
type DesEcbEnc = ecb::Decryptor<des::Des>;
|
||||||
type DesCbcEnc = cbc::Encryptor<des::Des>;
|
type DesCbcEnc = cbc::Encryptor<des::Des>;
|
||||||
|
|
||||||
pub fn new_thread_encryptor(
|
pub struct Encryptor {
|
||||||
_input: DataEncryptorInput,
|
#[allow(clippy::type_complexity)]
|
||||||
) -> UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)> {
|
channel: Option<UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)>>,
|
||||||
|
state: Option<EncryptorState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EncryptorState {
|
||||||
|
input_data: Vec<u8>,
|
||||||
|
iv: [u8; 8],
|
||||||
|
random_key: [u8; 8],
|
||||||
|
encrypted_random_key: [u8; 8],
|
||||||
|
default_chunk_size: usize,
|
||||||
|
current_chunk_size: usize,
|
||||||
|
offset: usize,
|
||||||
|
packet_count: usize,
|
||||||
|
closed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encryptor {
|
||||||
|
pub fn new_threaded(input: DataEncryptorInput) -> Self {
|
||||||
let (tx, rx) = unbounded_channel::<(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 || {
|
thread::spawn(move || {
|
||||||
let mut iv = [0u8; 8];
|
let mut iv = [0u8; 8];
|
||||||
|
@ -74,5 +90,108 @@ pub fn new_thread_encryptor(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
rx
|
Self {
|
||||||
|
channel: Some(rx),
|
||||||
|
state: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(input: DataEncryptorInput) -> Self {
|
||||||
|
let 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 packet_count = 0;
|
||||||
|
let current_chunk_size = 0;
|
||||||
|
|
||||||
|
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 offset: usize = 0;
|
||||||
|
|
||||||
|
Encryptor {
|
||||||
|
channel: None,
|
||||||
|
state: Some(EncryptorState {
|
||||||
|
input_data,
|
||||||
|
iv,
|
||||||
|
random_key,
|
||||||
|
encrypted_random_key,
|
||||||
|
current_chunk_size,
|
||||||
|
offset,
|
||||||
|
default_chunk_size,
|
||||||
|
packet_count,
|
||||||
|
closed: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the next encrypted value
|
||||||
|
pub async fn next(&mut self) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> {
|
||||||
|
let output;
|
||||||
|
|
||||||
|
if let Some(state) = self.state.as_mut() {
|
||||||
|
if state.closed {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.packet_count > 0 {
|
||||||
|
state.current_chunk_size = state.default_chunk_size;
|
||||||
|
} else {
|
||||||
|
state.current_chunk_size = state.default_chunk_size - 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.current_chunk_size = std::cmp::min(state.current_chunk_size, state.input_data.len() - state.offset);
|
||||||
|
|
||||||
|
let this_data_chunk = &mut state.input_data[state.offset..state.offset + state.current_chunk_size];
|
||||||
|
DesCbcEnc::new(&state.random_key.into(), &state.iv.into())
|
||||||
|
.encrypt_padded_mut::<NoPadding>(this_data_chunk, state.current_chunk_size)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
output = Some((
|
||||||
|
state.encrypted_random_key.to_vec(),
|
||||||
|
state.iv.to_vec(),
|
||||||
|
this_data_chunk.to_vec(),
|
||||||
|
));
|
||||||
|
|
||||||
|
state.iv.copy_from_slice(&this_data_chunk[this_data_chunk.len() - 8..]);
|
||||||
|
|
||||||
|
state.packet_count += 1;
|
||||||
|
state.offset += state.current_chunk_size;
|
||||||
|
} else if let Some(channel) = self.channel.as_mut() {
|
||||||
|
output = channel.recv().await
|
||||||
|
} else {
|
||||||
|
unreachable!("If you got here, this is bad!");
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call close to return none from subsequent calls
|
||||||
|
pub fn close(&mut self) {
|
||||||
|
if let Some(state) = self.state.as_mut() {
|
||||||
|
state.closed = true;
|
||||||
|
} else if let Some(channel) = self.channel.as_mut() {
|
||||||
|
channel.close()
|
||||||
|
} else {
|
||||||
|
unreachable!("If you got here, this is bad!");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,9 @@ use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
|
||||||
|
|
||||||
use super::base::NetMD;
|
use super::base::NetMD;
|
||||||
|
use super::encryption::Encryptor;
|
||||||
use super::utils::{cross_sleep, to_sjis};
|
use super::utils::{cross_sleep, to_sjis};
|
||||||
|
|
||||||
/// An action to take on the player
|
/// An action to take on the player
|
||||||
|
@ -306,7 +306,10 @@ pub struct NetMDInterface {
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl NetMDInterface {
|
impl NetMDInterface {
|
||||||
|
/// The maximum number of times to retry after an interim response
|
||||||
const MAX_INTERIM_READ_ATTEMPTS: u8 = 4;
|
const MAX_INTERIM_READ_ATTEMPTS: u8 = 4;
|
||||||
|
|
||||||
|
/// The amount of time to wait after an interim response (in milliseconds)
|
||||||
const INTERIM_RESPONSE_RETRY_INTERVAL: u32 = 100;
|
const INTERIM_RESPONSE_RETRY_INTERVAL: u32 = 100;
|
||||||
|
|
||||||
/// Get a new interface to a NetMD device
|
/// Get a new interface to a NetMD device
|
||||||
|
@ -452,7 +455,7 @@ impl NetMDInterface {
|
||||||
/// Send a query to the NetMD player
|
/// Send a query to the NetMD player
|
||||||
async fn send_query(
|
async fn send_query(
|
||||||
&mut self,
|
&mut self,
|
||||||
query: &Vec<u8>,
|
query: &[u8],
|
||||||
test: bool,
|
test: bool,
|
||||||
accept_interim: bool,
|
accept_interim: bool,
|
||||||
) -> Result<Vec<u8>, InterfaceError> {
|
) -> Result<Vec<u8>, InterfaceError> {
|
||||||
|
@ -465,7 +468,7 @@ impl NetMDInterface {
|
||||||
|
|
||||||
async fn send_command(
|
async fn send_command(
|
||||||
&mut self,
|
&mut self,
|
||||||
query: &Vec<u8>,
|
query: &[u8],
|
||||||
test: bool,
|
test: bool,
|
||||||
) -> Result<(), InterfaceError> {
|
) -> Result<(), InterfaceError> {
|
||||||
let status_byte = match test {
|
let status_byte = match test {
|
||||||
|
@ -476,7 +479,7 @@ impl NetMDInterface {
|
||||||
let mut new_query = Vec::new();
|
let mut new_query = Vec::new();
|
||||||
|
|
||||||
new_query.push(status_byte as u8);
|
new_query.push(status_byte as u8);
|
||||||
new_query.extend_from_slice(&query);
|
new_query.extend_from_slice(query);
|
||||||
|
|
||||||
self.device.send_command(new_query).await?;
|
self.device.send_command(new_query).await?;
|
||||||
|
|
||||||
|
@ -618,8 +621,6 @@ impl NetMDInterface {
|
||||||
pub async fn disc_present(&mut self) -> Result<bool, InterfaceError> {
|
pub async fn disc_present(&mut self) -> Result<bool, InterfaceError> {
|
||||||
let status = self.status().await?;
|
let status = self.status().await?;
|
||||||
|
|
||||||
println!("{:X?}", status);
|
|
||||||
|
|
||||||
Ok(status[4] == 0x40)
|
Ok(status[4] == 0x40)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,11 +650,9 @@ impl NetMDInterface {
|
||||||
self.change_descriptor_state(&Descriptor::OperatingStatusBlock, &DescriptorAction::Close)
|
self.change_descriptor_state(&Descriptor::OperatingStatusBlock, &DescriptorAction::Close)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if operating_status.len() < 2 {
|
if operating_status.len() < 2 && !operating_status.is_empty() {
|
||||||
if !operating_status.is_empty() {
|
|
||||||
return Err(InterfaceError::InvalidStatus(StatusError(operating_status[0] as u16)));
|
return Err(InterfaceError::InvalidStatus(StatusError(operating_status[0] as u16)));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let operating_status_number =
|
let operating_status_number =
|
||||||
(operating_status[0] as u16) << 8 | operating_status[1] as u16;
|
(operating_status[0] as u16) << 8 | operating_status[1] as u16;
|
||||||
|
@ -1041,11 +1040,13 @@ impl NetMDInterface {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut remaining_tracks = Vec::new();
|
||||||
for i in 0..track_count {
|
for i in 0..track_count {
|
||||||
if !track_dict.contains_key(&i) {
|
if !track_dict.contains_key(&i) {
|
||||||
result.insert(0, (None, None, Vec::from([i])))
|
remaining_tracks.push(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result.insert(0, (None, None, remaining_tracks));
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
@ -1689,8 +1690,7 @@ impl NetMDInterface {
|
||||||
discformat: u8,
|
discformat: u8,
|
||||||
frames: u32,
|
frames: u32,
|
||||||
pkt_size: u32,
|
pkt_size: u32,
|
||||||
// key, iv, data
|
mut packets: Encryptor,
|
||||||
mut packets: UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)>,
|
|
||||||
hex_session_key: &[u8],
|
hex_session_key: &[u8],
|
||||||
progress_callback: F,
|
progress_callback: F,
|
||||||
) -> Result<(u16, Vec<u8>, Vec<u8>), InterfaceError>
|
) -> Result<(u16, Vec<u8>, Vec<u8>), InterfaceError>
|
||||||
|
@ -1729,7 +1729,7 @@ impl NetMDInterface {
|
||||||
let mut written_bytes = 0;
|
let mut written_bytes = 0;
|
||||||
let mut packet_count = 0;
|
let mut packet_count = 0;
|
||||||
|
|
||||||
while let Some((key, iv, data)) = packets.recv().await {
|
while let Some((key, iv, data)) = packets.next().await {
|
||||||
let binpack = if packet_count == 0 {
|
let binpack = if packet_count == 0 {
|
||||||
let packed_length: Vec<u8> = pkt_size.to_be_bytes().to_vec();
|
let packed_length: Vec<u8> = pkt_size.to_be_bytes().to_vec();
|
||||||
[vec![0, 0, 0, 0], packed_length, key, iv, data].concat()
|
[vec![0, 0, 0, 0], packed_length, key, iv, data].concat()
|
||||||
|
@ -1862,10 +1862,6 @@ pub struct MDTrack {
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
pub chunk_size: usize,
|
pub chunk_size: usize,
|
||||||
pub full_width_title: Option<String>,
|
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>)>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DataEncryptorInput {
|
pub struct DataEncryptorInput {
|
||||||
|
@ -1920,8 +1916,19 @@ impl MDTrack {
|
||||||
[0x14, 0xe3, 0x83, 0x4e, 0xe2, 0xd3, 0xcc, 0xa5]
|
[0x14, 0xe3, 0x83, 0x4e, 0xe2, 0xd3, 0xcc, 0xa5]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_encrypting_iterator(&mut self) -> UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)> {
|
#[cfg(not(target_family = "wasm"))]
|
||||||
(self.encrypt_packets_iterator)(DataEncryptorInput {
|
pub fn get_encrypting_iterator(&mut self) -> Encryptor {
|
||||||
|
Encryptor::new_threaded(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) -> Encryptor {
|
||||||
|
Encryptor::new(DataEncryptorInput {
|
||||||
kek: self.get_kek(),
|
kek: self.get_kek(),
|
||||||
frame_size: self.frame_size(),
|
frame_size: self.frame_size(),
|
||||||
chunk_size: self.chunk_size(),
|
chunk_size: self.chunk_size(),
|
||||||
|
|
Loading…
Reference in a new issue