From 397a886693e60a43777bed3b77d29e8a3b2670a2 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sat, 23 Sep 2023 00:58:35 -0500
Subject: [PATCH] Initial code commit

---
 Cargo.toml             |  12 ++
 src/main.rs            |  49 +++++++
 src/netmd/base.rs      | 228 +++++++++++++++++++++++++++++++
 src/netmd/interface.rs | 298 +++++++++++++++++++++++++++++++++++++++++
 src/netmd/mod.rs       |   3 +
 src/netmd/utils.rs     |   8 ++
 6 files changed, 598 insertions(+)
 create mode 100644 Cargo.toml
 create mode 100644 src/main.rs
 create mode 100644 src/netmd/base.rs
 create mode 100644 src/netmd/interface.rs
 create mode 100644 src/netmd/mod.rs
 create mode 100644 src/netmd/utils.rs

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<Vec<DeviceId>> = 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<String>,
+}
+
+pub struct NetMD {
+    device_connection: DeviceHandle<GlobalContext>,
+    model: DeviceId,
+    status: Option<Status>,
+}
+
+impl NetMD {
+    /// Creates a new `NetMD` struct
+    pub fn new(
+        device: DeviceHandle<GlobalContext>,
+        device_desc: DeviceDescriptor,
+    ) -> Result<Self, Box<dyn Error>> {
+        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<String> {
+        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<dyn Error>> {
+        // 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<u8>,
+        use_factory_command: bool,
+    ) -> Result<(), Box<dyn Error>> {
+        //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<Vec<u8>, Box<dyn Error>> {
+        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<u8> {
+        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<u8> for Status {
+    type Error = Box<dyn Error>;
+
+    fn try_from(item: u8) -> Result<Self, Box<dyn Error>> {
+        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<dyn Error>> {
+        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<dyn Error>> {
+        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<dyn Error>> {
+        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<u8>,
+        test: bool,
+        accept_interim: bool,
+    ) -> Result<Vec<u8>, Box<dyn Error>> {
+        self.send_command(query, test)?;
+
+        let result = self.read_reply(accept_interim)?;
+
+        Ok(result)
+    }
+
+    fn send_command(&self, query: &mut Vec<u8>, test: bool) -> Result<(), Box<dyn Error>> {
+        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<Vec<u8>, Box<dyn Error>> {
+        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<u8>, expected: &[u8]) -> Result<(), Box<dyn Error>> {
+    match result.as_slice().eq(expected) {
+        true => Ok(()),
+        false => Err("Response was not expected!".into()),
+    }
+}