diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..48877a5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "minidisc-rs" +version = "0.0.1" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nofmt = "1.0.0" +once_cell = "1.18.0" +rusb = "0.9.3" + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..96078ed --- /dev/null +++ b/src/main.rs @@ -0,0 +1,49 @@ +mod netmd; + +use crate::netmd::base::NetMD; +use crate::netmd::interface::NetMDInterface; + +use std::thread; +use std::time::Duration; + +fn main() { + for device in rusb::devices().unwrap().iter() { + let device_desc = device.device_descriptor().unwrap(); + + let new_device = match device.open() { + Ok(device) => device, + Err(_) => continue, + }; + + println!("Connected to Bus {:03} Device {:03} VID: {:04x}, PID: {:04x}, {:?}", + device.bus_number(), + device.address(), + device_desc.vendor_id(), + device_desc.product_id(), + new_device.read_product_string_ascii(&device_desc)); + + let player = NetMD::new(new_device, device_desc).unwrap(); + let player_controller = NetMDInterface::new(player); + + println!( + "Player Model: {}", + player_controller.net_md_device.device_name().unwrap() + ); + + player_controller.get_disc_subunit_identifier(); + + /* + let mut request: [u8; 19] = [0x00, 0x18, 0x06, 0x02, 0x20, 0x18, + 0x01, 0x00, 0x00, 0x30, 0x00, 0xa, + 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00]; + request[4] = 0x75; + let test_result = player.send_command(&request); + + match test_result { + Ok(_) => println!("Successfully sent command! Response: {:?}", test_result), + Err(error) => println!("Command failed! Error: {}", error) + } + */ + } +} diff --git a/src/netmd/base.rs b/src/netmd/base.rs new file mode 100644 index 0000000..606e88b --- /dev/null +++ b/src/netmd/base.rs @@ -0,0 +1,228 @@ +use nofmt; +use once_cell::sync::Lazy; +use rusb::{DeviceDescriptor, DeviceHandle, Direction, GlobalContext, Recipient, RequestType}; +use std::error::Error; + +const DEFAULT_TIMEOUT: std::time::Duration = std::time::Duration::new(1, 0); + +const STANDARD_SEND: u8 = + rusb::request_type(Direction::Out, RequestType::Vendor, Recipient::Interface); +const STANDARD_RECV: u8 = + rusb::request_type(Direction::In, RequestType::Vendor, Recipient::Interface); + +// TODO: I think this sucks, figure out a better way +pub static DEVICE_IDS: Lazy> = Lazy::new(|| nofmt::pls!{ + Vec::from([ + DeviceId {vendor_id: 0x04dd, product_id: 0x7202, name: Some(String::from("Sharp IM-MT899H"))}, + DeviceId {vendor_id: 0x04dd, product_id: 0x9013, name: Some(String::from("Sharp IM-DR400"))}, + DeviceId {vendor_id: 0x04dd, product_id: 0x9014, name: Some(String::from("Sharp IM-DR80"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0034, name: Some(String::from("Sony PCLK-XX"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0036, name: Some(String::from("Sony"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0075, name: Some(String::from("Sony MZ-N1"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x007c, name: Some(String::from("Sony"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0080, name: Some(String::from("Sony LAM-1"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0081, name: Some(String::from("Sony MDS-JB980/MDS-NT1/MDS-JE780"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0084, name: Some(String::from("Sony MZ-N505"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0085, name: Some(String::from("Sony MZ-S1"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0086, name: Some(String::from("Sony MZ-N707"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x008e, name: Some(String::from("Sony CMT-C7NT"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0097, name: Some(String::from("Sony PCGA-MDN1"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x00ad, name: Some(String::from("Sony CMT-L7HD"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x00c6, name: Some(String::from("Sony MZ-N10"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x00c7, name: Some(String::from("Sony MZ-N910"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x00c8, name: Some(String::from("Sony MZ-N710/NF810"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x00c9, name: Some(String::from("Sony MZ-N510/N610"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x00ca, name: Some(String::from("Sony MZ-NE410/NF520D"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x00e7, name: Some(String::from("Sony CMT-M333NT/M373NT"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x00eb, name: Some(String::from("Sony MZ-NE810/NE910"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0101, name: Some(String::from("Sony LAM"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0113, name: Some(String::from("Aiwa AM-NX1"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x013f, name: Some(String::from("Sony MDS-S500"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x014c, name: Some(String::from("Aiwa AM-NX9"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x017e, name: Some(String::from("Sony MZ-NH1"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0180, name: Some(String::from("Sony MZ-NH3D"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0182, name: Some(String::from("Sony MZ-NH900"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0184, name: Some(String::from("Sony MZ-NH700/NH800"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0186, name: Some(String::from("Sony MZ-NH600"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0187, name: Some(String::from("Sony MZ-NH600D"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0188, name: Some(String::from("Sony MZ-N920"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x018a, name: Some(String::from("Sony LAM-3"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x01e9, name: Some(String::from("Sony MZ-DH10P"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0219, name: Some(String::from("Sony MZ-RH10"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x021b, name: Some(String::from("Sony MZ-RH710/MZ-RH910"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x021d, name: Some(String::from("Sony CMT-AH10"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x022c, name: Some(String::from("Sony CMT-AH10"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x023c, name: Some(String::from("Sony DS-HMD1"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0286, name: Some(String::from("Sony MZ-RH1"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x011a, name: Some(String::from("Sony CMT-SE7"))}, + DeviceId {vendor_id: 0x054c, product_id: 0x0148, name: Some(String::from("Sony MDS-A1"))}, + DeviceId {vendor_id: 0x0b28, product_id: 0x1004, name: Some(String::from("Kenwood MDX-J9"))}, + DeviceId {vendor_id: 0x04da, product_id: 0x23b3, name: Some(String::from("Panasonic SJ-MR250"))}, + DeviceId {vendor_id: 0x04da, product_id: 0x23b6, name: Some(String::from("Panasonic SJ-MR270"))}, + ]) +}); + +pub enum Status { + Ready, + Playing, + Paused, + FastForward, + Rewind, + ReadingTOC, + NoDisc, + DiscBlank, +} + +pub struct DeviceId { + vendor_id: u16, + product_id: u16, + name: Option, +} + +pub struct NetMD { + device_connection: DeviceHandle, + model: DeviceId, + status: Option, +} + +impl NetMD { + /// Creates a new `NetMD` struct + pub fn new( + device: DeviceHandle, + device_desc: DeviceDescriptor, + ) -> Result> { + let mut model = DeviceId { + vendor_id: device_desc.vendor_id(), + product_id: device_desc.product_id(), + name: None, + }; + + for device_type in DEVICE_IDS.iter() { + if device_type.vendor_id == model.vendor_id + && device_type.product_id == model.product_id + { + model.name = device_type.name.clone() + } + } + + match model.name { + None => return Err("Could not find device in list".into()), + Some(_) => (), + } + + Ok(Self { + device_connection: device, + model, + status: None, + }) + } + + /// Gets the device name from the struct + pub fn device_name(&self) -> Option { + self.model.name.clone() + } + + /// Gets the vendor id from the struct + pub fn vendor_id(&self) -> u16 { + self.model.vendor_id.clone() + } + + /// Gets the product id from the struct + pub fn product_id(&self) -> u16 { + self.model.product_id.clone() + } + + /// Poll the device to get either the result + /// of the previous command, or the status + fn poll(&self, tries: usize) -> Result<(usize, [u8; 4]), Box> { + // Create an array to store the result of the poll + let mut poll_result = [0u8; 4]; + + // Try until failure or `tries` reached + for i in 0..tries { + let _status = match self.device_connection.read_control( + STANDARD_RECV, + 0x01, + 0, + 0, + &mut poll_result, + DEFAULT_TIMEOUT, + ) { + Ok(size) => size, + Err(error) => return Err(error.into()), + }; + + if poll_result[0] != 0 { + return Ok((poll_result[2] as usize, poll_result)); + } + + if i > 0 { + std::thread::sleep(std::time::Duration::from_secs(1)); + } + } + + Ok((poll_result[2] as usize, poll_result)) + } + + /// Send a control message to the device + pub fn send_command( + &self, + command: Vec, + use_factory_command: bool, + ) -> Result<(), Box> { + //First poll to ensure the device is ready + match self.poll(1) { + Ok(buffer) => match buffer.1[2] { + 0 => 0, + _ => return Err("Device not ready!".into()), + }, + Err(error) => return Err(error), + }; + + let _ = match use_factory_command { + false => 0x80, + true => 0xff, + }; + + match self.device_connection.write_control( + STANDARD_SEND, + 0x80, + 0, + 0, + &command, + DEFAULT_TIMEOUT, + ) { + Ok(_) => Ok(()), + Err(error) => Err(error.into()), + } + } + + /// Poll to see if a message is ready, + /// and if so, recieve it + pub fn read_reply(&self, use_factory_command: bool) -> Result, Box> { + let poll_result = match self.poll(30) { + Ok(buffer) => buffer, + Err(error) => return Err(error), + }; + + let request = match use_factory_command { + false => 0x81, + true => 0xff, + }; + + // Create a buffer to fill with the result + let mut buf: [u8; 255] = [0; 255]; + + match self.device_connection.read_control( + STANDARD_RECV, + request, + 0, + 0, + &mut buf, + DEFAULT_TIMEOUT, + ) { + Ok(_) => Ok(buf[0..poll_result.0].to_vec()), + Err(error) => return Err(error.into()), + } + } +} diff --git a/src/netmd/interface.rs b/src/netmd/interface.rs new file mode 100644 index 0000000..0da5ec2 --- /dev/null +++ b/src/netmd/interface.rs @@ -0,0 +1,298 @@ +use crate::netmd::utils; +use crate::NetMD; +use std::error::Error; + +#[derive(Copy, Clone)] +enum Action { + Play = 0x75, + Pause = 0x7d, + FastForward = 0x39, + Rewind = 0x49, +} + +enum Track { + Previous = 0x0002, + Next = 0x8001, + Restart = 0x0001, +} + +enum DiscFormat { + LP4 = 0, + LP2 = 2, + SPMono = 4, + SPStereo = 6, +} + +enum WireFormat { + PCM = 0x00, + L105kbps = 0x90, + LP2 = 0x94, + LP4 = 0xA8, +} + +impl WireFormat { + fn frame_size(&self) -> u16 { + match self { + WireFormat::PCM => 2048, + WireFormat::L105kbps => 192, + WireFormat::LP2 => 152, + WireFormat::LP4 => 96, + } + } +} + +enum Encoding { + SP = 0x90, + LP2 = 0x92, + LP4 = 0x93, +} + +enum Channels { + Mono = 0x01, + Stereo = 0x00, +} + +enum ChannelCount { + Mono = 1, + Stereo = 2, +} + +enum TrackFlag { + Protected = 0x03, + Unprotected = 0x00, +} + +enum DiscFlag { + Writable = 0x10, + WriteProtected = 0x40, +} + +enum NetMDLevel { + Level1 = 0x20, // Network MD + Level2 = 0x50, // Program play MD + Level3 = 0x70, // Editing MD +} + +enum Descriptor { + DiscTitleTD, + AudioUTOC1TD, + AudioUTOC4TD, + DSITD, + AudioContentsTD, + RootTD, + + DiscSubunitIdentifier, + OperatingStatusBlock, +} + +impl Descriptor { + fn get_array(&self) -> Vec { + match self { + Descriptor::DiscTitleTD => vec![0x10, 0x18, 0x01], + Descriptor::AudioUTOC1TD => vec![0x10, 0x18, 0x02], + Descriptor::AudioUTOC4TD => vec![0x10, 0x18, 0x03], + Descriptor::DSITD => vec![0x10, 0x18, 0x04], + Descriptor::AudioContentsTD => vec![0x10, 0x10, 0x01], + Descriptor::RootTD => vec![0x10, 0x10, 0x00], + Descriptor::DiscSubunitIdentifier => vec![0x00], + Descriptor::OperatingStatusBlock => vec![0x80, 0x00], + } + } +} + +enum DescriptorAction { + OpenRead = 1, + OpenWrite = 3, + Close = 0, +} + +#[repr(u8)] +enum Status { + // NetMD Protocol return status (first byte of request) + Control = 0x00, + Status = 0x01, + SpecificInquiry = 0x02, + Notify = 0x03, + GeneralInquiry = 0x04, + // ... (first byte of response) + NotImplemented = 0x08, + Accepted = 0x09, + Rejected = 0x0a, + InTransition = 0x0b, + Implemented = 0x0c, + Changed = 0x0d, + Interim = 0x0f, +} + +impl std::convert::TryFrom for Status { + type Error = Box; + + fn try_from(item: u8) -> Result> { + match item { + 0x00 => Ok(Status::Control), + 0x01 => Ok(Status::Status), + 0x02 => Ok(Status::SpecificInquiry), + 0x03 => Ok(Status::Notify), + 0x04 => Ok(Status::GeneralInquiry), + 0x08 => Ok(Status::NotImplemented), + 0x09 => Ok(Status::Accepted), + 0x0a => Ok(Status::Rejected), + 0x0b => Ok(Status::InTransition), + 0x0c => Ok(Status::Implemented), + 0x0d => Ok(Status::Changed), + 0x0f => Ok(Status::Interim), + _ => Err("Not a valid value".into()), + } + } +} + +pub struct NetMDInterface { + pub net_md_device: NetMD, +} + +impl NetMDInterface { + const MAX_INTERIM_READ_ATTEMPTS: u8 = 4; + const INTERIM_RESPONSE_RETRY_INTERVAL: u32 = 100; + + pub fn new(net_md_device: NetMD) -> Self { + NetMDInterface { net_md_device } + } + + pub fn get_disc_subunit_identifier(&self) -> Result<(), Box> { + self.change_descriptor_state( + Descriptor::DiscSubunitIdentifier, + DescriptorAction::OpenRead, + ); + + let mut query = vec![0x18, 0x09, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00]; + + let reply = self.send_query(&mut query, false, false); + + println!("{:X?}", reply); + + /* + let descriptor_length = i16::from_le_bytes(reply[0..1].try_into()?); + let generation_id = i16::from_le_bytes(reply[0..1].try_into()?); + */ + + Ok(()) + } + + fn playback_control(&self, action: Action) -> Result<(), Box> { + let mut query = vec![0x18, 0xc3, 0xff, 0x00, 0x00, 0x00, 0x00]; + + query[3] = action as u8; + + let result = self.send_query(&mut query, false, false)?; + + utils::check_result(result, &[0x18, 0xc5, 0x00, action as u8, 0x00, 0x00, 0x00])?; + + Ok(()) + } + + pub fn play(&self) { + let _ = self.playback_control(Action::Play); + } + + pub fn fast_forward(&self) { + let _ = self.playback_control(Action::FastForward); + } + + pub fn rewind(&self) { + let _ = self.playback_control(Action::Rewind); + } + + pub fn pause(&self) { + let _ = self.playback_control(Action::Pause); + } + + pub fn stop(&self) -> Result<(), Box> { + let mut query = vec![0x18, 0xc5, 0xff, 0x00, 0x00, 0x00, 0x00]; + + let result = self.send_query(&mut query, false, false)?; + + utils::check_result(result, &[0x18, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x00])?; + + Ok(()) + } + + fn change_descriptor_state(&self, descriptor: Descriptor, action: DescriptorAction) { + let mut query = vec![0x18, 0x08]; + + query.append(&mut descriptor.get_array()); + + query.push(action as u8); + + query.push(0x00); + + let _ = self.send_query(&mut query, false, false); + } + + fn send_query( + &self, + query: &mut Vec, + test: bool, + accept_interim: bool, + ) -> Result, Box> { + self.send_command(query, test)?; + + let result = self.read_reply(accept_interim)?; + + Ok(result) + } + + fn send_command(&self, query: &mut Vec, test: bool) -> Result<(), Box> { + let status_byte = match test { + true => Status::GeneralInquiry, + false => Status::Control, + }; + + let mut new_query = Vec::new(); + + new_query.push(status_byte as u8); + new_query.append(query); + + self.net_md_device.send_command(new_query, false)?; + + Ok(()) + } + + fn read_reply(&self, accept_interim: bool) -> Result, Box> { + let mut current_attempt = 0; + let mut data; + + while current_attempt < Self::MAX_INTERIM_READ_ATTEMPTS { + data = match self.net_md_device.read_reply(false) { + Ok(reply) => reply, + Err(error) => return Err(error.into()), + }; + + let status = match Status::try_from(data[0]) { + Ok(status) => status, + Err(error) => return Err(error), + }; + + match status { + Status::NotImplemented => return Err("Not implemented".into()), + Status::Rejected => return Err("Rejected".into()), + Status::Interim if !accept_interim => { + let sleep_time = Self::INTERIM_RESPONSE_RETRY_INTERVAL as u64 * (u64::pow(2, current_attempt as u32) - 1); + let sleep_dur = std::time::Duration::from_millis(sleep_time); + std::thread::sleep(sleep_dur); // Sleep to wait before retrying + current_attempt += 1; + continue; // Retry! + } + Status::Accepted | Status::Implemented | Status::Interim => { + if current_attempt >= Self::MAX_INTERIM_READ_ATTEMPTS { + return Err("Max interim retry attempts reached".into()); + } + return Ok(data); + } + _ => return Err("Unknown return status".into()), + } + } + + // This should NEVER happen unless the code is changed wrongly + Err("The max retries is set to 0".into()) + } +} diff --git a/src/netmd/mod.rs b/src/netmd/mod.rs new file mode 100644 index 0000000..4b456cb --- /dev/null +++ b/src/netmd/mod.rs @@ -0,0 +1,3 @@ +pub mod base; +pub mod interface; +pub mod utils; diff --git a/src/netmd/utils.rs b/src/netmd/utils.rs new file mode 100644 index 0000000..6998147 --- /dev/null +++ b/src/netmd/utils.rs @@ -0,0 +1,8 @@ +use std::error::Error; + +pub fn check_result(result: Vec, expected: &[u8]) -> Result<(), Box> { + match result.as_slice().eq(expected) { + true => Ok(()), + false => Err("Response was not expected!".into()), + } +}