Compare commits

..

No commits in common. "1c0b0c7f11981715d2f83a9421947c81ab0d2cc8" and "9bdd7ddb393fe10c43057e64af68a117c0640b93" have entirely different histories.

8 changed files with 203 additions and 344 deletions

View file

@ -1,12 +1,12 @@
[package]
name = "minidisc"
name = "minidisc_rs"
version = "0.1.0"
edition = "2021"
homepage = "https://github.com/G2-Games/minidisc-cli/"
repository = "https://github.com/G2-Games/minidisc-cli/minidisc-rs/"
description = "A library for interacting with NetMD and Hi-MD minidisc devices."
description = "A library for controlling and uploading data to NetMD and Hi-MD minidisc devices."
license-file = "LICENSE"
authors = ["G2 <ke0bhogsg@gmail.com>", "Asivery"]
authors = ["G2 <ke0bhogsg@gmail.com>"]
readme = "README.md"
# Have docs.rs make documentation for most supported targets
@ -33,8 +33,7 @@ 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"
unicode-jp = { git = "https://github.com/uzabase/unicode-jp-rs.git" }
[target.'cfg(target_family = "wasm")'.dependencies]
gloo = { version = "0.11.0", features = ["futures"] }

View file

@ -10,12 +10,10 @@ Documentation has not been finished and is a work in progress.
- [x] Track upload
- [x] Track management
- [x] Playback control
- [ ] Group Management
- [x] Track download ([MZ-RH1](https://www.minidisc.wiki/equipment/sony/portable/mz-rh1) only)
### Hi-MD
- [ ] Track upload
- [ ] Track management
- [ ] Playback control
- [ ] Group Management
- [ ] Track download

View file

@ -1,5 +1,7 @@
/// A crate for controlling NetMD and Hi-MD devices.
///
/// To use this library, first you need to get a device from [cross-usb] and then open a [netmd::interface::NetMDInterface]
/*!
* This crate is an interface in rust to control NetMD and Hi-MD minidisc devices.
*
* Documentation coming soon
*/
pub mod netmd;

View file

@ -1,16 +1,16 @@
#![cfg_attr(debug_assertions, allow(dead_code))]
use std::time::Duration;
use nofmt;
use once_cell::sync::Lazy;
use thiserror::Error;
use std::error::Error;
use std::time::Duration;
// USB stuff
use cross_usb::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError};
use cross_usb::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient};
use cross_usb::{UsbDevice, UsbInterface};
use super::utils::cross_sleep;
const DEFAULT_TIMEOUT: Duration = Duration::new(10000, 0);
const BULK_WRITE_ENDPOINT: u8 = 0x02;
const BULK_READ_ENDPOINT: u8 = 0x81;
@ -89,32 +89,13 @@ pub enum Status {
}
/// The ID of a device, including the name
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct DeviceId {
vendor_id: u16,
product_id: u16,
name: Option<String>,
}
#[derive(Error, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum NetMDError {
#[error("communication timed out")]
Timeout,
#[error("invalid usb result")]
InvalidResult,
#[error("the device is not ready")]
NotReady,
#[error("could not find device")]
UnknownDevice(DeviceId),
#[error("usb connection error")]
UsbError(#[from] UsbError),
}
/// A USB connection to a NetMD device
/// A connection to a NetMD device
pub struct NetMD {
usb_interface: UsbInterface,
model: DeviceId,
@ -124,7 +105,7 @@ impl NetMD {
const READ_REPLY_RETRY_INTERVAL: u32 = 10;
/// Creates a new interface to a NetMD device
pub async fn new(usb_device: &UsbDevice) -> Result<Self, NetMDError> {
pub async fn new(usb_device: &UsbDevice) -> Result<Self, Box<dyn Error>> {
let mut model = DeviceId {
vendor_id: usb_device.vendor_id().await,
product_id: usb_device.product_id().await,
@ -141,7 +122,7 @@ impl NetMD {
}
match model.name {
None => return Err(NetMDError::UnknownDevice(model)),
None => return Err("Could not find device in list".into()),
Some(_) => (),
}
@ -170,7 +151,7 @@ impl NetMD {
/// Poll the device to get either the result
/// of the previous command, or the status
pub async fn poll(&mut self) -> Result<(u16, [u8; 4]), NetMDError> {
pub async fn poll(&mut self) -> Result<(u16, [u8; 4]), Box<dyn Error>> {
// Create an array to store the result of the poll
let poll_result = match self
.usb_interface
@ -192,17 +173,17 @@ impl NetMD {
let poll_result: [u8; 4] = match poll_result.try_into() {
Ok(val) => val,
Err(_) => return Err(NetMDError::InvalidResult),
Err(_) => return Err("could not convert result".into()),
};
Ok((length_bytes, poll_result))
}
pub async fn send_command(&mut self, command: Vec<u8>) -> Result<(), NetMDError> {
pub async fn send_command(&mut self, command: Vec<u8>) -> Result<(), Box<dyn Error>> {
self._send_command(command, false).await
}
pub async fn send_factory_command(&mut self, command: Vec<u8>) -> Result<(), NetMDError> {
pub async fn send_factory_command(&mut self, command: Vec<u8>) -> Result<(), Box<dyn Error>> {
self._send_command(command, true).await
}
@ -211,12 +192,12 @@ impl NetMD {
&mut self,
command: Vec<u8>,
use_factory_command: bool,
) -> Result<(), NetMDError> {
) -> Result<(), Box<dyn Error>> {
// First poll to ensure the device is ready
match self.poll().await {
Ok(buffer) => match buffer.1[2] {
0 => 0,
_ => return Err(NetMDError::NotReady),
_ => return Err("Device not ready!".into()),
},
Err(error) => return Err(error),
};
@ -246,14 +227,14 @@ impl NetMD {
pub async fn read_reply(
&mut self,
override_length: Option<i32>,
) -> Result<Vec<u8>, NetMDError> {
) -> Result<Vec<u8>, Box<dyn Error>> {
self._read_reply(false, override_length).await
}
pub async fn read_factory_reply(
&mut self,
override_length: Option<i32>,
) -> Result<Vec<u8>, NetMDError> {
) -> Result<Vec<u8>, Box<dyn Error>> {
self._read_reply(true, override_length).await
}
@ -262,12 +243,12 @@ impl NetMD {
&mut self,
use_factory_command: bool,
override_length: Option<i32>,
) -> Result<Vec<u8>, NetMDError> {
) -> Result<Vec<u8>, Box<dyn Error>> {
let mut length = 0;
for attempt in 0..40 {
if attempt == 39 {
return Err(NetMDError::Timeout);
return Err("Failed to get response length".into());
}
length = self.poll().await?.0;
@ -279,7 +260,7 @@ impl NetMD {
// Back off while trying again
let sleep_time = Self::READ_REPLY_RETRY_INTERVAL * (u32::pow(2, attempt) - 1);
cross_sleep(Duration::from_millis(sleep_time as u64)).await;
cross_sleep(sleep_time).await;
}
if let Some(value) = override_length {
@ -312,7 +293,7 @@ impl NetMD {
&mut self,
length: usize,
chunksize: usize,
) -> Result<Vec<u8>, NetMDError> {
) -> Result<Vec<u8>, Box<dyn Error>> {
let result = self.read_bulk_to_array(length, chunksize).await?;
Ok(result)
@ -322,7 +303,7 @@ impl NetMD {
&mut self,
length: usize,
chunksize: usize,
) -> Result<Vec<u8>, NetMDError> {
) -> Result<Vec<u8>, Box<dyn Error>> {
let mut final_result: Vec<u8> = Vec::new();
let mut done = 0;
@ -336,7 +317,7 @@ impl NetMD {
.await
{
Ok(result) => result,
Err(error) => return Err(NetMDError::UsbError(error)),
Err(error) => return Err(format!("USB error: {:?}", error).into()),
};
final_result.extend_from_slice(&res);
@ -345,7 +326,7 @@ impl NetMD {
Ok(final_result)
}
pub async fn write_bulk(&mut self, data: &[u8]) -> Result<usize, NetMDError> {
pub async fn write_bulk(&mut self, data: &[u8]) -> Result<usize, Box<dyn Error>> {
Ok(self
.usb_interface
.bulk_out(BULK_WRITE_ENDPOINT, data)

View file

@ -2,7 +2,6 @@
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use std::error::Error;
use std::time::Duration;
use super::interface::{MDSession, MDTrack, NetMDInterface};
use super::utils::cross_sleep;
@ -70,7 +69,7 @@ pub async fn prepare_download(interface: &mut NetMDInterface) -> Result<(), Box<
.state
.unwrap_or(OperatingStatus::NoDisc),
) {
cross_sleep(Duration::from_millis(200)).await;
cross_sleep(200).await;
}
let _ = interface.session_key_forget().await;

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
use crate::netmd::utils;
use lazy_static::lazy_static;
use std::collections::hash_map::HashMap;
use thiserror::Error;
use std::error::Error;
lazy_static! {
/// %b, w, d, q - explained above (can have endiannes overriden by '>' and '<' operators, f. ex. %>d %<q)
@ -27,73 +27,24 @@ pub enum QueryValue {
Array(Vec<u8>),
}
#[derive(Error, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum ValueError {
#[error("type mismatch: expected {expected}, got {actual}")]
TypeMismatch {
expected: String,
actual: String
}
}
impl QueryValue {
pub fn from_array<const S: usize>(value: [u8; S]) -> Self {
Self::Array(value.to_vec())
}
pub fn to_array<const S: usize>(&self) -> Result<[u8; S], ValueError> {
let mut array = [0u8; S];
match self {
QueryValue::Array(a) => {
for (i, byte) in a.iter().take(S).enumerate() {
array[i] = *byte
}
Ok(array)
},
_ => Err(ValueError::TypeMismatch {
expected: String::from("Vec<u8>"),
actual: format!("{:?}", self)
}),
}
}
pub fn to_vec(&self) -> Result<Vec<u8>, ValueError> {
pub fn to_vec(&self) -> Result<Vec<u8>, Box<dyn Error>> {
match self {
QueryValue::Array(a) => Ok(a.to_vec()),
_ => Err(ValueError::TypeMismatch {
expected: String::from("Vec<u8>"),
actual: format!("{:?}", self)
}),
_ => Err("QueryValue type mismatch! Expected Vec<u8>, got i64".into()),
}
}
pub fn to_i64(&self) -> Result<i64, ValueError> {
pub fn to_i64(&self) -> Result<i64, Box<dyn Error>> {
match self {
QueryValue::Number(a) => Ok(*a),
_ => Err(ValueError::TypeMismatch {
expected: String::from("i64"),
actual: format!("{:?}", self)
}),
_ => Err("QueryValue type mismatch! Expected i64, got Vec<u8>".into()),
}
}
}
#[derive(Error, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum QueryError {
#[error("unrecognized format character: `{0}`")]
UnrecognizedChar(char),
#[error("Format and input mismatch at {index}: expected {expected:#04x}, got {actual:#04x} (format {format_string})")]
InputMismatch {
index: usize,
expected: u8,
actual: u8,
format_string: String,
}
}
/// Formats a query using a standard input to send to the player
pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, QueryError> {
pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, Box<dyn Error>> {
if DEBUG {
println!("SENT>>> F: {}", format);
}
@ -163,7 +114,7 @@ pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, Qu
}
result.push((converted & 0xFF) as u8);
}
_ => return Err(QueryError::UnrecognizedChar(character)),
_ => return Err(format!("Unrecognized format char {}", character).into()),
}
continue;
}
@ -191,7 +142,7 @@ pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, Qu
pub fn scan_query(
query_result: Vec<u8>,
format: String,
) -> Result<Vec<QueryValue>, QueryError> {
) -> Result<Vec<QueryValue>, Box<dyn Error>> {
let mut result: Vec<QueryValue> = Vec::new();
let initial_length = query_result.len();
@ -273,7 +224,7 @@ pub fn scan_query(
| input_stack.next().unwrap() as i32;
result.push(QueryValue::Number(utils::bcd_to_int(v) as i64));
}
_ => return Err(QueryError::UnrecognizedChar(character)),
_ => return Err(format!("Unrecognized format char {}", character).into()),
}
continue;
}
@ -293,12 +244,7 @@ pub fn scan_query(
u8::from_str_radix(&String::from_iter([half.unwrap(), character]), 16).unwrap();
if format_value != input_value {
let i = initial_length - input_stack.len() - 1;
return Err(QueryError::InputMismatch {
index: i,
expected: format_value,
actual: input_value,
format_string: format
});
return Err(format!("Format and input mismatch at {i}: expected {format_value:#04x}, got {input_value:#04x} (format {format})").into());
}
half = None;
}

View file

@ -2,19 +2,19 @@ use crate::netmd::mappings::{ALLOWED_HW_KANA, MAPPINGS_DE, MAPPINGS_HW, MAPPINGS
use diacritics;
use encoding_rs::SHIFT_JIS;
use regex::Regex;
use std::{collections::hash_map::HashMap, error::Error, vec::IntoIter, time::Duration};
use std::{collections::hash_map::HashMap, error::Error, vec::IntoIter};
use unicode_normalization::UnicodeNormalization;
extern crate kana;
use kana::*;
/// Sleep for a specified number of milliseconds on any platform
pub async fn cross_sleep(duration: Duration) {
pub async fn cross_sleep(millis: u32) {
#[cfg(not(target_family = "wasm"))]
std::thread::sleep(duration);
std::thread::sleep(std::time::Duration::from_millis(millis as u64));
#[cfg(target_family = "wasm")]
gloo::timers::future::TimeoutFuture::new(duration.as_millis() as u32).await;
gloo::timers::future::TimeoutFuture::new(millis).await;
}
pub fn bcd_to_int(mut bcd: i32) -> i32 {