mirror of
https://github.com/G2-Games/minidisc-cli.git
synced 2025-04-20 12:12:53 -05:00
Compare commits
4 commits
4151a4f5c0
...
72e13b92f9
Author | SHA1 | Date | |
---|---|---|---|
72e13b92f9 | |||
52b067ddbc | |||
291e0ba049 | |||
bf66c59799 |
9 changed files with 1709 additions and 808 deletions
|
@ -1,2 +1,6 @@
|
||||||
[target.'cfg(target_family = "wasm")']
|
[target.'cfg(target_family = "wasm")']
|
||||||
rustflags = ["--cfg=web_sys_unstable_apis"]
|
rustflags = ["--cfg=web_sys_unstable_apis"]
|
||||||
|
|
||||||
|
# Enable for testing WASM-only stuff
|
||||||
|
#[build]
|
||||||
|
#target = "wasm32-unknown-unknown"
|
||||||
|
|
|
@ -26,8 +26,7 @@ encoding_rs = "0.8.33"
|
||||||
nofmt = "1.0.0"
|
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.2"
|
regex = "1.10"
|
||||||
lazy_static = "1.4.0"
|
|
||||||
cross_usb = "0.3"
|
cross_usb = "0.3"
|
||||||
num-derive = "0.3.3"
|
num-derive = "0.3.3"
|
||||||
num-traits = "0.2.14"
|
num-traits = "0.2.14"
|
||||||
|
@ -39,9 +38,11 @@ ecb = "0.1"
|
||||||
tokio = { version = "1.36", features = ["sync"] }
|
tokio = { version = "1.36", features = ["sync"] }
|
||||||
g2-unicode-jp = "0.4.1"
|
g2-unicode-jp = "0.4.1"
|
||||||
thiserror = "1.0.57"
|
thiserror = "1.0.57"
|
||||||
|
phf = { version = "0.11.2", features = ["phf_macros", "macros"] }
|
||||||
|
byteorder = "1.5.0"
|
||||||
|
|
||||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||||
gloo = { version = "0.11.0", features = ["futures"] }
|
gloo = { version = "0.11.0", features = ["futures", "worker"] }
|
||||||
|
|
||||||
[package.metadata.wasm-pack.profile.dev.wasm-bindgen]
|
[package.metadata.wasm-pack.profile.dev.wasm-bindgen]
|
||||||
dwarf-debug-info = true
|
dwarf-debug-info = true
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
/// A crate for controlling NetMD and Hi-MD devices.
|
/// 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 [netmd::interface::NetMDInterface]
|
/// To use this library, first you need to get a device from [cross-usb] and then open [netmd::interface::NetMDInterface]
|
||||||
|
|
||||||
pub mod netmd;
|
pub mod netmd;
|
||||||
|
|
|
@ -1,80 +1,262 @@
|
||||||
#![cfg_attr(debug_assertions, allow(dead_code))]
|
#![cfg_attr(debug_assertions, allow(dead_code))]
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use nofmt;
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use thiserror::Error;
|
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, UsbError};
|
||||||
use cross_usb::{Interface, Descriptor};
|
use cross_usb::{Descriptor, Interface};
|
||||||
|
|
||||||
use super::utils::cross_sleep;
|
use super::utils::cross_sleep;
|
||||||
|
|
||||||
const BULK_WRITE_ENDPOINT: u8 = 0x02;
|
const BULK_WRITE_ENDPOINT: u8 = 0x02;
|
||||||
const BULK_READ_ENDPOINT: u8 = 0x81;
|
const BULK_READ_ENDPOINT: u8 = 0x81;
|
||||||
|
|
||||||
pub static DEVICE_IDS: Lazy<Box<[DeviceId]>> = Lazy::new(|| {
|
pub static DEVICE_IDS: &[DeviceId] = &[
|
||||||
nofmt::pls! {
|
DeviceId {
|
||||||
Box::new([
|
vendor_id: 0x04dd,
|
||||||
DeviceId {vendor_id: 0x04dd, product_id: 0x7202, name: Some(String::from("Sharp IM-MT899H"))},
|
product_id: 0x7202,
|
||||||
DeviceId {vendor_id: 0x04dd, product_id: 0x9013, name: Some(String::from("Sharp IM-DR400"))},
|
name: Some("Sharp IM-MT899H"),
|
||||||
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 {
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x0036, name: Some(String::from("Sony"))},
|
vendor_id: 0x04dd,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x0075, name: Some(String::from("Sony MZ-N1"))},
|
product_id: 0x9013,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x007c, name: Some(String::from("Sony"))},
|
name: Some("Sharp IM-DR400"),
|
||||||
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 {
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x0084, name: Some(String::from("Sony MZ-N505"))},
|
vendor_id: 0x04dd,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x0085, name: Some(String::from("Sony MZ-S1"))},
|
product_id: 0x9014,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x0086, name: Some(String::from("Sony MZ-N707"))},
|
name: Some("Sharp IM-DR80"),
|
||||||
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 {
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x00ad, name: Some(String::from("Sony CMT-L7HD"))},
|
vendor_id: 0x054c,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x00c6, name: Some(String::from("Sony MZ-N10"))},
|
product_id: 0x0034,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x00c7, name: Some(String::from("Sony MZ-N910"))},
|
name: Some("Sony PCLK-XX"),
|
||||||
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 {
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x00ca, name: Some(String::from("Sony MZ-NE410/NF520D"))},
|
vendor_id: 0x054c,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x00e7, name: Some(String::from("Sony CMT-M333NT/M373NT"))},
|
product_id: 0x0036,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x00eb, name: Some(String::from("Sony MZ-NE810/NE910"))},
|
name: Some("Sony"),
|
||||||
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 {
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x013f, name: Some(String::from("Sony MDS-S500"))},
|
vendor_id: 0x054c,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x014c, name: Some(String::from("Aiwa AM-NX9"))},
|
product_id: 0x0075,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x017e, name: Some(String::from("Sony MZ-NH1"))},
|
name: Some("Sony MZ-N1"),
|
||||||
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 {
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x0184, name: Some(String::from("Sony MZ-NH700/NH800"))},
|
vendor_id: 0x054c,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x0186, name: Some(String::from("Sony MZ-NH600"))},
|
product_id: 0x007c,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x0187, name: Some(String::from("Sony MZ-NH600D"))},
|
name: Some("Sony"),
|
||||||
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 {
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x01e9, name: Some(String::from("Sony MZ-DH10P"))},
|
vendor_id: 0x054c,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x0219, name: Some(String::from("Sony MZ-RH10"))},
|
product_id: 0x0080,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x021b, name: Some(String::from("Sony MZ-RH710/MZ-RH910"))},
|
name: Some("Sony LAM-1"),
|
||||||
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 {
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x023c, name: Some(String::from("Sony DS-HMD1"))},
|
vendor_id: 0x054c,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x0286, name: Some(String::from("Sony MZ-RH1"))},
|
product_id: 0x0081,
|
||||||
DeviceId {vendor_id: 0x054c, product_id: 0x011a, name: Some(String::from("Sony CMT-SE7"))},
|
name: Some("Sony MDS-JB980/MDS-NT1/MDS-JE780"),
|
||||||
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 {
|
||||||
DeviceId {vendor_id: 0x04da, product_id: 0x23b3, name: Some(String::from("Panasonic SJ-MR250"))},
|
vendor_id: 0x054c,
|
||||||
DeviceId {vendor_id: 0x04da, product_id: 0x23b6, name: Some(String::from("Panasonic SJ-MR270"))},
|
product_id: 0x0084,
|
||||||
])
|
name: Some("Sony MZ-N505"),
|
||||||
}
|
},
|
||||||
});
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0085,
|
||||||
|
name: Some("Sony MZ-S1"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0086,
|
||||||
|
name: Some("Sony MZ-N707"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x008e,
|
||||||
|
name: Some("Sony CMT-C7NT"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0097,
|
||||||
|
name: Some("Sony PCGA-MDN1"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x00ad,
|
||||||
|
name: Some("Sony CMT-L7HD"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x00c6,
|
||||||
|
name: Some("Sony MZ-N10"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x00c7,
|
||||||
|
name: Some("Sony MZ-N910"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x00c8,
|
||||||
|
name: Some("Sony MZ-N710/NF810"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x00c9,
|
||||||
|
name: Some("Sony MZ-N510/N610"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x00ca,
|
||||||
|
name: Some("Sony MZ-NE410/NF520D"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x00e7,
|
||||||
|
name: Some("Sony CMT-M333NT/M373NT"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x00eb,
|
||||||
|
name: Some("Sony MZ-NE810/NE910"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0101,
|
||||||
|
name: Some("Sony LAM"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0113,
|
||||||
|
name: Some("Aiwa AM-NX1"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x013f,
|
||||||
|
name: Some("Sony MDS-S500"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x014c,
|
||||||
|
name: Some("Aiwa AM-NX9"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x017e,
|
||||||
|
name: Some("Sony MZ-NH1"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0180,
|
||||||
|
name: Some("Sony MZ-NH3D"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0182,
|
||||||
|
name: Some("Sony MZ-NH900"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0184,
|
||||||
|
name: Some("Sony MZ-NH700/NH800"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0186,
|
||||||
|
name: Some("Sony MZ-NH600"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0187,
|
||||||
|
name: Some("Sony MZ-NH600D"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0188,
|
||||||
|
name: Some("Sony MZ-N920"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x018a,
|
||||||
|
name: Some("Sony LAM-3"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x01e9,
|
||||||
|
name: Some("Sony MZ-DH10P"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0219,
|
||||||
|
name: Some("Sony MZ-RH10"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x021b,
|
||||||
|
name: Some("Sony MZ-RH710/MZ-RH910"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x021d,
|
||||||
|
name: Some("Sony CMT-AH10"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x022c,
|
||||||
|
name: Some("Sony CMT-AH10"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x023c,
|
||||||
|
name: Some("Sony DS-HMD1"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0286,
|
||||||
|
name: Some("Sony MZ-RH1"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x011a,
|
||||||
|
name: Some("Sony CMT-SE7"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x054c,
|
||||||
|
product_id: 0x0148,
|
||||||
|
name: Some("Sony MDS-A1"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x0b28,
|
||||||
|
product_id: 0x1004,
|
||||||
|
name: Some("Kenwood MDX-J9"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x04da,
|
||||||
|
product_id: 0x23b3,
|
||||||
|
name: Some("Panasonic SJ-MR250"),
|
||||||
|
},
|
||||||
|
DeviceId {
|
||||||
|
vendor_id: 0x04da,
|
||||||
|
product_id: 0x23b6,
|
||||||
|
name: Some("Panasonic SJ-MR270"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
pub static DEVICE_IDS_CROSSUSB: Lazy<Box<[cross_usb::DeviceFilter]>> = Lazy::new(|| {
|
pub static DEVICE_IDS_CROSSUSB: Lazy<Box<[cross_usb::DeviceFilter]>> = Lazy::new(|| {
|
||||||
DEVICE_IDS.iter().map(|d|{
|
DEVICE_IDS
|
||||||
|
.iter()
|
||||||
|
.map(|d| {
|
||||||
cross_usb::device_filter! {
|
cross_usb::device_filter! {
|
||||||
vendor_id: d.vendor_id,
|
vendor_id: d.vendor_id,
|
||||||
product_id: d.product_id,
|
product_id: d.product_id,
|
||||||
}
|
}
|
||||||
}).collect()
|
})
|
||||||
|
.collect()
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The current status of the Minidisc device
|
/// The current status of the Minidisc device
|
||||||
|
@ -94,7 +276,7 @@ pub enum Status {
|
||||||
pub struct DeviceId {
|
pub struct DeviceId {
|
||||||
vendor_id: u16,
|
vendor_id: u16,
|
||||||
product_id: u16,
|
product_id: u16,
|
||||||
name: Option<String>,
|
name: Option<&'static str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Error, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
@ -156,8 +338,8 @@ impl NetMD {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the device name, this is limited to the devices in the list
|
/// Gets the device name, this is limited to the devices in the list
|
||||||
pub fn device_name(&self) -> &Option<String> {
|
pub fn device_name(&self) -> Option<&str> {
|
||||||
&self.model.name
|
self.model.name
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the vendor id
|
/// Gets the vendor id
|
||||||
|
@ -310,20 +492,24 @@ impl NetMD {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default chunksize should be 0x10000
|
// Default chunksize should be 0x10000
|
||||||
pub async fn read_bulk(
|
pub async fn read_bulk<F: Fn(usize, usize)>(
|
||||||
&mut self,
|
&mut self,
|
||||||
length: usize,
|
length: usize,
|
||||||
chunksize: usize,
|
chunksize: usize,
|
||||||
|
progress_callback: Option<F>,
|
||||||
) -> Result<Vec<u8>, NetMDError> {
|
) -> Result<Vec<u8>, NetMDError> {
|
||||||
let result = self.read_bulk_to_array(length, chunksize).await?;
|
let result = self
|
||||||
|
.read_bulk_to_array(length, chunksize, progress_callback)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_bulk_to_array(
|
pub async fn read_bulk_to_array<F: Fn(usize, usize)>(
|
||||||
&mut self,
|
&mut self,
|
||||||
length: usize,
|
length: usize,
|
||||||
chunksize: usize,
|
chunksize: usize,
|
||||||
|
progress_callback: Option<F>,
|
||||||
) -> Result<Vec<u8>, NetMDError> {
|
) -> Result<Vec<u8>, NetMDError> {
|
||||||
let mut final_result: Vec<u8> = Vec::new();
|
let mut final_result: Vec<u8> = Vec::new();
|
||||||
let mut done = 0;
|
let mut done = 0;
|
||||||
|
@ -341,6 +527,10 @@ impl NetMD {
|
||||||
Err(error) => return Err(NetMDError::UsbError(error)),
|
Err(error) => return Err(NetMDError::UsbError(error)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(cb) = &progress_callback {
|
||||||
|
cb(length, done)
|
||||||
|
}
|
||||||
|
|
||||||
final_result.extend_from_slice(&res);
|
final_result.extend_from_slice(&res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
#![cfg_attr(debug_assertions, allow(dead_code))]
|
#![cfg_attr(debug_assertions, allow(dead_code))]
|
||||||
|
use cross_usb::Descriptor;
|
||||||
use num_derive::FromPrimitive;
|
use num_derive::FromPrimitive;
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
|
use regex::Regex;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use cross_usb::Descriptor;
|
|
||||||
|
|
||||||
use super::interface::{MDSession, MDTrack, NetMDInterface, Direction, InterfaceError};
|
use crate::netmd::interface::DiscFlag;
|
||||||
use super::utils::cross_sleep;
|
use crate::netmd::utils::{create_aea_header, create_wav_header, AeaOptions, RawTime};
|
||||||
|
|
||||||
|
use super::interface::{
|
||||||
|
Channels, Direction, DiscFormat, Encoding, InterfaceError, MDSession, MDTrack, NetMDInterface,
|
||||||
|
TrackFlag,
|
||||||
|
};
|
||||||
|
use super::utils::{
|
||||||
|
cross_sleep, half_width_title_length, half_width_to_full_width_range,
|
||||||
|
sanitize_full_width_title, sanitize_half_width_title,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(FromPrimitive, PartialEq, Eq)]
|
#[derive(FromPrimitive, PartialEq, Eq)]
|
||||||
pub enum OperatingStatus {
|
pub enum OperatingStatus {
|
||||||
|
@ -34,6 +44,215 @@ pub struct DeviceStatus {
|
||||||
pub time: Time,
|
pub time: Time,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Track {
|
||||||
|
index: u16,
|
||||||
|
title: String,
|
||||||
|
full_width_title: String,
|
||||||
|
duration: RawTime,
|
||||||
|
channel: Channels,
|
||||||
|
encoding: Encoding,
|
||||||
|
protected: TrackFlag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Track {
|
||||||
|
pub fn cells_for_title(&self) -> (usize, usize) {
|
||||||
|
let encoding_name_correction = match self.encoding {
|
||||||
|
Encoding::SP => 0,
|
||||||
|
_ => 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let full_width_length = chars_to_cells(self.full_width_title.len() * 2);
|
||||||
|
let half_width_length = chars_to_cells(half_width_title_length(&self.title));
|
||||||
|
|
||||||
|
(
|
||||||
|
usize::max(encoding_name_correction, half_width_length),
|
||||||
|
usize::max(encoding_name_correction, full_width_length),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Group {
|
||||||
|
index: u16,
|
||||||
|
title: Option<String>,
|
||||||
|
full_width_title: Option<String>,
|
||||||
|
tracks: Vec<Track>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Disc {
|
||||||
|
title: String,
|
||||||
|
full_width_title: String,
|
||||||
|
writeable: bool,
|
||||||
|
write_protected: bool,
|
||||||
|
used: u64,
|
||||||
|
left: u64,
|
||||||
|
total: u64,
|
||||||
|
track_count: u16,
|
||||||
|
groups: Vec<Group>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Disc {
|
||||||
|
pub fn track_count(&self) -> u16 {
|
||||||
|
self.groups
|
||||||
|
.iter()
|
||||||
|
.map(|g| g.tracks.len())
|
||||||
|
.reduce(|acc, s| acc + s)
|
||||||
|
.unwrap() as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tracks(&self) -> Vec<Track> {
|
||||||
|
self.groups.iter().flat_map(|g| g.tracks.clone()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remaining_characters_for_titles(
|
||||||
|
&self,
|
||||||
|
ignore_disc_titles: bool,
|
||||||
|
include_groups: bool,
|
||||||
|
) -> (usize, usize) {
|
||||||
|
const CELL_LIMIT: usize = 255;
|
||||||
|
|
||||||
|
let groups = self.groups.iter().filter(|g| g.title.is_some());
|
||||||
|
|
||||||
|
let (disc_fw_title, disc_hw_title) = if !ignore_disc_titles {
|
||||||
|
(self.full_width_title.clone(), self.full_width_title.clone())
|
||||||
|
} else {
|
||||||
|
(String::new(), String::new())
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut fw_title = disc_fw_title + "0;//";
|
||||||
|
let mut hw_title = disc_hw_title + "0;//";
|
||||||
|
|
||||||
|
if include_groups {
|
||||||
|
for group in groups {
|
||||||
|
let indices: Vec<u16> = group.tracks.iter().map(|t| t.index).collect();
|
||||||
|
let min_group_index = indices.iter().min().unwrap();
|
||||||
|
let max_group_index = indices.iter().max().unwrap();
|
||||||
|
|
||||||
|
let range = format!("{}{}", min_group_index + 1, {
|
||||||
|
if group.tracks.len() - 1 != 0 {
|
||||||
|
format!("-{}", max_group_index + 1)
|
||||||
|
} else {
|
||||||
|
String::from("")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fw_title.push_str((group.full_width_title.clone().unwrap() + &range).as_str());
|
||||||
|
hw_title.push_str((group.title.clone().unwrap() + &range).as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut used_half_width_cells = 0;
|
||||||
|
let mut used_full_width_cells = 0;
|
||||||
|
|
||||||
|
used_full_width_cells += chars_to_cells(fw_title.len() * 2);
|
||||||
|
used_half_width_cells += chars_to_cells(half_width_title_length(&hw_title));
|
||||||
|
|
||||||
|
for track in self.tracks() {
|
||||||
|
let (half_width, full_width) = track.cells_for_title();
|
||||||
|
used_half_width_cells += half_width;
|
||||||
|
used_full_width_cells += full_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
usize::max(CELL_LIMIT - used_full_width_cells, 0) * 7,
|
||||||
|
usize::max(CELL_LIMIT - used_half_width_cells, 0) * 7,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile_disc_titles(&self) -> (String, String) {
|
||||||
|
let (available_full_width, available_half_width) =
|
||||||
|
self.remaining_characters_for_titles(true, false);
|
||||||
|
|
||||||
|
let use_full_width = self
|
||||||
|
.groups
|
||||||
|
.iter()
|
||||||
|
.filter(|n| n.full_width_title.as_ref().is_some_and(|t| !t.is_empty()))
|
||||||
|
.count()
|
||||||
|
> 0
|
||||||
|
|| self
|
||||||
|
.tracks()
|
||||||
|
.iter()
|
||||||
|
.filter(|t| !t.full_width_title.is_empty())
|
||||||
|
.count()
|
||||||
|
> 0;
|
||||||
|
|
||||||
|
let mut new_raw_title = String::new();
|
||||||
|
let mut new_raw_full_width_title = String::new();
|
||||||
|
|
||||||
|
if !self.title.is_empty() {
|
||||||
|
new_raw_title = format!("0;{}//", self.title);
|
||||||
|
}
|
||||||
|
if use_full_width {
|
||||||
|
new_raw_full_width_title = format!("0;{}//", self.full_width_title);
|
||||||
|
}
|
||||||
|
|
||||||
|
for group in &self.groups {
|
||||||
|
if group.title.is_none() || group.tracks.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let min_group_index = group
|
||||||
|
.tracks
|
||||||
|
.iter()
|
||||||
|
.map(|t| t.index)
|
||||||
|
.min()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mut range = format!("{}", min_group_index + 1);
|
||||||
|
|
||||||
|
if group.tracks.len() != 1 {
|
||||||
|
range.push_str(&format!(
|
||||||
|
"-{}",
|
||||||
|
min_group_index as usize + group.tracks.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_raw_title_after_group =
|
||||||
|
new_raw_title.clone() + &format!("{};{}//", range, group.title.as_ref().unwrap());
|
||||||
|
let new_raw_full_width_title_after_group = new_raw_full_width_title.clone()
|
||||||
|
+ &half_width_to_full_width_range(&range)
|
||||||
|
+ &format!(
|
||||||
|
";{}//",
|
||||||
|
group.full_width_title.as_ref().unwrap_or(&String::new())
|
||||||
|
);
|
||||||
|
|
||||||
|
let half_width_titles_length_in_toc =
|
||||||
|
chars_to_cells(half_width_title_length(&new_raw_title_after_group));
|
||||||
|
|
||||||
|
if use_full_width {
|
||||||
|
let full_width_titles_length_in_toc =
|
||||||
|
chars_to_cells(new_raw_full_width_title_after_group.len() * 2) * 7;
|
||||||
|
if available_full_width as isize - full_width_titles_length_in_toc as isize >= 0 {
|
||||||
|
new_raw_full_width_title = new_raw_full_width_title_after_group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if available_half_width as isize - half_width_titles_length_in_toc as isize >= 0 {
|
||||||
|
new_raw_title = new_raw_title_after_group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let half_width_titles_length_in_toc =
|
||||||
|
chars_to_cells(half_width_title_length(&new_raw_title)) * 7;
|
||||||
|
let full_width_titles_length_in_toc = chars_to_cells(new_raw_full_width_title.len() * 2);
|
||||||
|
|
||||||
|
if (available_half_width as isize - half_width_titles_length_in_toc as isize) < 0 {
|
||||||
|
new_raw_title = String::new();
|
||||||
|
}
|
||||||
|
if (available_full_width as isize - full_width_titles_length_in_toc as isize) < 0 {
|
||||||
|
new_raw_full_width_title = String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
new_raw_title,
|
||||||
|
if use_full_width {
|
||||||
|
new_raw_full_width_title
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct NetMDContext {
|
pub struct NetMDContext {
|
||||||
interface: NetMDInterface,
|
interface: NetMDInterface,
|
||||||
}
|
}
|
||||||
|
@ -43,9 +262,7 @@ impl NetMDContext {
|
||||||
pub async fn new(device: Descriptor) -> Result<Self, InterfaceError> {
|
pub async fn new(device: Descriptor) -> Result<Self, InterfaceError> {
|
||||||
let interface = NetMDInterface::new(device).await?;
|
let interface = NetMDInterface::new(device).await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self { interface })
|
||||||
interface,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change to the next track (skip forward)
|
/// Change to the next track (skip forward)
|
||||||
|
@ -93,9 +310,201 @@ impl NetMDContext {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn list_content(&mut self) -> Result<Disc, Box<dyn Error>> {
|
||||||
|
let flags = self.interface.disc_flags().await?;
|
||||||
|
let title = self.interface.disc_title(false).await?;
|
||||||
|
let full_width_title = self.interface.disc_title(true).await?;
|
||||||
|
let disc_capacity: [RawTime; 3] = self.interface.disc_capacity().await?;
|
||||||
|
let track_count = self.interface.track_count().await?;
|
||||||
|
|
||||||
|
let mut frames_used = disc_capacity[0].as_frames();
|
||||||
|
let mut frames_total = disc_capacity[1].as_frames();
|
||||||
|
let mut frames_left = disc_capacity[2].as_frames();
|
||||||
|
|
||||||
|
// Some devices report the time remaining of the currently selected recording mode. (Sharps)
|
||||||
|
while frames_total > 512 * 60 * 82 {
|
||||||
|
frames_used /= 2;
|
||||||
|
frames_total /= 2;
|
||||||
|
frames_left /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let track_group_list = self.interface.track_group_list().await?;
|
||||||
|
|
||||||
|
let mut groups = vec![];
|
||||||
|
for (index, group) in track_group_list.iter().enumerate() {
|
||||||
|
let mut tracks = vec![];
|
||||||
|
for track in &group.2 {
|
||||||
|
let (encoding, channel) = self.interface.track_encoding(*track).await?;
|
||||||
|
let duration = self.interface.track_length(*track).await?;
|
||||||
|
let flags = self.interface.track_flags(*track).await?;
|
||||||
|
let title = self.interface.track_title(*track, false).await?;
|
||||||
|
let full_width_title = self.interface.track_title(*track, true).await?;
|
||||||
|
|
||||||
|
tracks.push(Track {
|
||||||
|
index: *track,
|
||||||
|
title,
|
||||||
|
full_width_title,
|
||||||
|
duration,
|
||||||
|
channel,
|
||||||
|
encoding,
|
||||||
|
protected: TrackFlag::from_u8(flags).unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
groups.push(Group {
|
||||||
|
index: index as u16,
|
||||||
|
title: group.0.clone(),
|
||||||
|
full_width_title: group.1.clone(),
|
||||||
|
tracks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let disc = Disc {
|
||||||
|
title,
|
||||||
|
full_width_title,
|
||||||
|
writeable: (flags & DiscFlag::Writable as u8) != 0,
|
||||||
|
write_protected: (flags & DiscFlag::WriteProtected as u8) != 0,
|
||||||
|
used: frames_used,
|
||||||
|
left: frames_left,
|
||||||
|
total: frames_total,
|
||||||
|
track_count,
|
||||||
|
groups,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(disc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn rewrite_disc_groups(&mut self, disc: Disc) -> Result<(), Box<dyn Error>> {
|
||||||
|
let (new_raw_title, new_raw_full_width_title) = disc.compile_disc_titles();
|
||||||
|
|
||||||
|
self.interface.set_disc_title(&new_raw_title, false).await?;
|
||||||
|
self.interface
|
||||||
|
.set_disc_title(&new_raw_full_width_title, false)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn rename_disc(
|
||||||
|
&mut self,
|
||||||
|
new_name: &str,
|
||||||
|
new_fw_name: Option<&str>,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let new_name = sanitize_half_width_title(new_name);
|
||||||
|
let new_fw_name = if let Some(name) = new_fw_name {
|
||||||
|
Some(sanitize_full_width_title(name))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let old_name = self.interface.disc_title(false).await?;
|
||||||
|
let old_fw_name = self.interface.disc_title(true).await?;
|
||||||
|
let old_raw_name = self.interface.raw_disc_title(false).await?;
|
||||||
|
let old_raw_fw_name = self.interface.raw_disc_title(true).await?;
|
||||||
|
|
||||||
|
let has_groups = old_raw_name.find("//").is_some();
|
||||||
|
let has_fw_groups = old_raw_fw_name.find("//").is_some();
|
||||||
|
|
||||||
|
let has_groups_and_title = old_raw_name.starts_with("0;");
|
||||||
|
let has_fw_groups_and_title = old_raw_fw_name.starts_with("0;");
|
||||||
|
|
||||||
|
if new_fw_name.as_ref().is_some_and(|n| n != &old_fw_name) {
|
||||||
|
let new_fw_name_with_groups;
|
||||||
|
if has_fw_groups {
|
||||||
|
if has_fw_groups_and_title {
|
||||||
|
let re = Regex::new("/^0;.*?///").unwrap();
|
||||||
|
new_fw_name_with_groups = re
|
||||||
|
.replace_all(
|
||||||
|
&old_raw_fw_name,
|
||||||
|
if !new_fw_name.as_ref().unwrap().is_empty() {
|
||||||
|
format!("0;{}//", new_fw_name.unwrap())
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
new_fw_name_with_groups =
|
||||||
|
format!(r"0;{}//{}", new_fw_name.unwrap(), old_raw_fw_name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
new_fw_name_with_groups = new_fw_name.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.set_disc_title(&new_fw_name_with_groups, true)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_name == old_name {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_name_with_groups;
|
||||||
|
if has_groups {
|
||||||
|
if has_groups_and_title {
|
||||||
|
let re = Regex::new(r"/^0;.*?\/\//").unwrap();
|
||||||
|
new_name_with_groups = re
|
||||||
|
.replace_all(
|
||||||
|
&old_raw_name,
|
||||||
|
if !new_name.is_empty() {
|
||||||
|
format!("0;{}//", new_name)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
new_name_with_groups = format!("0;{}//{}", new_name, old_raw_name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
new_name_with_groups = new_name
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.set_disc_title(&new_name_with_groups, false)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn upload<F: Fn(usize, usize)>(
|
||||||
|
&mut self,
|
||||||
|
track: u16,
|
||||||
|
progress_callback: Option<F>,
|
||||||
|
) -> Result<(DiscFormat, Vec<u8>), Box<dyn Error>> {
|
||||||
|
let mut output_vec = Vec::new();
|
||||||
|
let (format, _frames, result) = self
|
||||||
|
.interface
|
||||||
|
.save_track_to_array(track, progress_callback)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let header;
|
||||||
|
match format {
|
||||||
|
DiscFormat::SPMono | DiscFormat::SPStereo => {
|
||||||
|
let aea_options = AeaOptions {
|
||||||
|
name: &self.interface.track_title(track, false).await?,
|
||||||
|
channels: if format == DiscFormat::SPStereo { 2 } else { 1 },
|
||||||
|
sound_groups: f32::floor(result.len() as f32 / 212.0) as u32,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
header = create_aea_header(aea_options);
|
||||||
|
}
|
||||||
|
DiscFormat::LP2 | DiscFormat::LP4 => {
|
||||||
|
header = create_wav_header(format, result.len() as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output_vec.extend_from_slice(&header);
|
||||||
|
output_vec.extend_from_slice(&result);
|
||||||
|
|
||||||
|
Ok((format, header))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn prepare_download(&mut self) -> Result<(), Box<dyn Error>> {
|
pub async fn prepare_download(&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
while ![OperatingStatus::DiscBlank, OperatingStatus::Ready].contains(
|
while ![OperatingStatus::DiscBlank, OperatingStatus::Ready].contains(
|
||||||
&self.device_status()
|
&self
|
||||||
|
.device_status()
|
||||||
.await?
|
.await?
|
||||||
.state
|
.state
|
||||||
.unwrap_or(OperatingStatus::NoDisc),
|
.unwrap_or(OperatingStatus::NoDisc),
|
||||||
|
@ -133,3 +542,7 @@ impl NetMDContext {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn chars_to_cells(len: usize) -> usize {
|
||||||
|
f32::ceil(len as f32 / 7.0) as usize
|
||||||
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
use crate::netmd::base;
|
use crate::netmd::base;
|
||||||
use crate::netmd::query_utils::{format_query, scan_query, QueryValue};
|
use crate::netmd::query_utils::{format_query, scan_query, QueryValue};
|
||||||
use crate::netmd::utils::{
|
use crate::netmd::utils::{
|
||||||
half_width_to_full_width_range, length_after_encoding_to_jis, sanitize_full_width_title,
|
half_width_to_full_width_range, length_after_encoding_to_sjis, sanitize_full_width_title,
|
||||||
sanitize_half_width_title, time_to_duration,
|
sanitize_half_width_title, RawTime,
|
||||||
};
|
};
|
||||||
use cbc::cipher::block_padding::NoPadding;
|
use cbc::cipher::block_padding::NoPadding;
|
||||||
use cbc::cipher::{BlockDecryptMut, BlockEncryptMut, KeyInit, KeyIvInit};
|
use cbc::cipher::{BlockDecryptMut, BlockEncryptMut, KeyInit, KeyIvInit};
|
||||||
|
@ -11,15 +11,13 @@ use encoding_rs::SHIFT_JIS;
|
||||||
use num_derive::FromPrimitive;
|
use num_derive::FromPrimitive;
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::error::Error;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use std::error::Error;
|
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
use super::base::NetMD;
|
use super::base::NetMD;
|
||||||
use super::utils::cross_sleep;
|
use super::utils::{cross_sleep, to_sjis};
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
enum Action {
|
enum Action {
|
||||||
|
@ -35,7 +33,7 @@ pub enum Direction {
|
||||||
Restart = 0x0001,
|
Restart = 0x0001,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, FromPrimitive)]
|
#[derive(Debug, Clone, Copy, FromPrimitive, PartialEq, Eq)]
|
||||||
pub enum DiscFormat {
|
pub enum DiscFormat {
|
||||||
LP4 = 0,
|
LP4 = 0,
|
||||||
LP2 = 2,
|
LP2 = 2,
|
||||||
|
@ -52,7 +50,7 @@ pub enum WireFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WireFormat {
|
impl WireFormat {
|
||||||
fn frame_size(&self) -> u16 {
|
const fn frame_size(&self) -> u16 {
|
||||||
match self {
|
match self {
|
||||||
WireFormat::Pcm => 2048,
|
WireFormat::Pcm => 2048,
|
||||||
WireFormat::L105kbps => 192,
|
WireFormat::L105kbps => 192,
|
||||||
|
@ -60,31 +58,70 @@ impl WireFormat {
|
||||||
WireFormat::LP4 => 96,
|
WireFormat::LP4 => 96,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn disc_for_wire(&self) -> DiscFormat {
|
||||||
|
match self {
|
||||||
|
WireFormat::Pcm => DiscFormat::SPStereo,
|
||||||
|
WireFormat::L105kbps => DiscFormat::LP2,
|
||||||
|
WireFormat::LP2 => DiscFormat::LP2,
|
||||||
|
WireFormat::LP4 => DiscFormat::LP4,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Encoding {
|
pub enum Encoding {
|
||||||
SP = 0x90,
|
SP = 0x90,
|
||||||
LP2 = 0x92,
|
LP2 = 0x92,
|
||||||
LP4 = 0x93,
|
LP4 = 0x93,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Channels {
|
impl ToString for Encoding {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Encoding::SP => String::from("sp"),
|
||||||
|
Encoding::LP2 => String::from("lp2"),
|
||||||
|
Encoding::LP4 => String::from("lp4"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Channels {
|
||||||
Mono = 0x01,
|
Mono = 0x01,
|
||||||
Stereo = 0x00,
|
Stereo = 0x00,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToString for Channels {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Channels::Mono => String::from("mono"),
|
||||||
|
Channels::Stereo => String::from("stereo"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum ChannelCount {
|
enum ChannelCount {
|
||||||
Mono = 1,
|
Mono = 1,
|
||||||
Stereo = 2,
|
Stereo = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TrackFlag {
|
#[derive(Debug, Clone, Copy, FromPrimitive)]
|
||||||
|
pub enum TrackFlag {
|
||||||
Protected = 0x03,
|
Protected = 0x03,
|
||||||
Unprotected = 0x00,
|
Unprotected = 0x00,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DiscFlag {
|
impl ToString for TrackFlag {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
TrackFlag::Protected => String::from("protected"),
|
||||||
|
TrackFlag::Unprotected => String::from("unprotected"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DiscFlag {
|
||||||
Writable = 0x10,
|
Writable = 0x10,
|
||||||
WriteProtected = 0x40,
|
WriteProtected = 0x40,
|
||||||
}
|
}
|
||||||
|
@ -161,15 +198,6 @@ enum NetmdStatus {
|
||||||
Interim = 0x0f,
|
Interim = 0x0f,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref FRAME_SIZE: HashMap<WireFormat, usize> = HashMap::from([
|
|
||||||
(WireFormat::Pcm, 2048),
|
|
||||||
(WireFormat::LP2, 192),
|
|
||||||
(WireFormat::L105kbps, 152),
|
|
||||||
(WireFormat::LP4, 96),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Error, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
#[error("invalid status code")]
|
#[error("invalid status code")]
|
||||||
pub struct StatusError;
|
pub struct StatusError;
|
||||||
|
@ -289,7 +317,8 @@ impl NetMDInterface {
|
||||||
self.change_descriptor_state(
|
self.change_descriptor_state(
|
||||||
&Descriptor::DiscSubunitIdentifier,
|
&Descriptor::DiscSubunitIdentifier,
|
||||||
&DescriptorAction::OpenRead,
|
&DescriptorAction::OpenRead,
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut query = format_query("1809 00 ff00 0000 0000".to_string(), vec![])?;
|
let mut query = format_query("1809 00 ff00 0000 0000".to_string(), vec![])?;
|
||||||
|
|
||||||
|
@ -421,7 +450,11 @@ impl NetMDInterface {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_command(&mut self, query: &mut Vec<u8>, test: bool) -> Result<(), InterfaceError> {
|
async fn send_command(
|
||||||
|
&mut self,
|
||||||
|
query: &mut Vec<u8>,
|
||||||
|
test: bool,
|
||||||
|
) -> Result<(), InterfaceError> {
|
||||||
let status_byte = match test {
|
let status_byte = match test {
|
||||||
true => NetmdStatus::GeneralInquiry,
|
true => NetmdStatus::GeneralInquiry,
|
||||||
false => NetmdStatus::Control,
|
false => NetmdStatus::Control,
|
||||||
|
@ -450,7 +483,9 @@ impl NetMDInterface {
|
||||||
NetmdStatus::NotImplemented => {
|
NetmdStatus::NotImplemented => {
|
||||||
return Err(InterfaceError::NotImplemented(format!("{:02X?}", data)))
|
return Err(InterfaceError::NotImplemented(format!("{:02X?}", data)))
|
||||||
}
|
}
|
||||||
NetmdStatus::Rejected => return Err(InterfaceError::Rejected(format!("{:02X?}", data))),
|
NetmdStatus::Rejected => {
|
||||||
|
return Err(InterfaceError::Rejected(format!("{:02X?}", data)))
|
||||||
|
}
|
||||||
NetmdStatus::Interim if !accept_interim => {
|
NetmdStatus::Interim if !accept_interim => {
|
||||||
let sleep_time = Self::INTERIM_RESPONSE_RETRY_INTERVAL
|
let sleep_time = Self::INTERIM_RESPONSE_RETRY_INTERVAL
|
||||||
* (u32::pow(2, current_attempt as u32) - 1);
|
* (u32::pow(2, current_attempt as u32) - 1);
|
||||||
|
@ -542,7 +577,8 @@ impl NetMDInterface {
|
||||||
self.change_descriptor_state(
|
self.change_descriptor_state(
|
||||||
&Descriptor::OperatingStatusBlock,
|
&Descriptor::OperatingStatusBlock,
|
||||||
&DescriptorAction::OpenRead,
|
&DescriptorAction::OpenRead,
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut query = format_query(
|
let mut query = format_query(
|
||||||
"1809 8001 0230 8800 0030 8804 00 ff00 00000000".to_string(),
|
"1809 8001 0230 8800 0030 8804 00 ff00 00000000".to_string(),
|
||||||
|
@ -664,11 +700,9 @@ impl NetMDInterface {
|
||||||
|
|
||||||
let reply = match self.send_query(&mut query, false, false).await {
|
let reply = match self.send_query(&mut query, false, false).await {
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(e) => {
|
Err(e) => match e {
|
||||||
match e {
|
|
||||||
InterfaceError::Rejected(_) => Vec::new(),
|
InterfaceError::Rejected(_) => Vec::new(),
|
||||||
_ => return Err(e)
|
_ => return Err(e),
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -817,7 +851,7 @@ impl NetMDInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the disc title as it is stored
|
/// Gets the disc title as it is stored
|
||||||
async fn raw_disc_title(&mut self, wchar: bool) -> Result<String, InterfaceError> {
|
pub async fn raw_disc_title(&mut self, wchar: bool) -> Result<String, InterfaceError> {
|
||||||
self.change_descriptor_state(&Descriptor::AudioContentsTD, &DescriptorAction::OpenRead)
|
self.change_descriptor_state(&Descriptor::AudioContentsTD, &DescriptorAction::OpenRead)
|
||||||
.await?;
|
.await?;
|
||||||
self.change_descriptor_state(&Descriptor::DiscTitleTD, &DescriptorAction::OpenRead)
|
self.change_descriptor_state(&Descriptor::DiscTitleTD, &DescriptorAction::OpenRead)
|
||||||
|
@ -971,9 +1005,10 @@ impl NetMDInterface {
|
||||||
let mut track_list: Vec<u16> = Vec::new();
|
let mut track_list: Vec<u16> = Vec::new();
|
||||||
for track in track_min - 1..track_max {
|
for track in track_min - 1..track_max {
|
||||||
if track_dict.contains_key(&track) {
|
if track_dict.contains_key(&track) {
|
||||||
return Err(
|
return Err(InterfaceError::GroupError(format!(
|
||||||
InterfaceError::GroupError(format!("track {} is in 2 groups", track))
|
"track {} is in 2 groups",
|
||||||
);
|
track
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
track_dict.insert(track, (String::from(group_name), i as u16));
|
track_dict.insert(track, (String::from(group_name), i as u16));
|
||||||
track_list.push(track);
|
track_list.push(track);
|
||||||
|
@ -1065,15 +1100,15 @@ impl NetMDInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_title: Vec<u8>;
|
let new_title: Vec<u8>;
|
||||||
let old_len = length_after_encoding_to_jis(¤t_title);
|
let old_len = length_after_encoding_to_sjis(¤t_title);
|
||||||
|
|
||||||
let wchar_value = match wchar {
|
let wchar_value = match wchar {
|
||||||
true => {
|
true => {
|
||||||
new_title = sanitize_full_width_title(title, false);
|
new_title = to_sjis(&sanitize_full_width_title(title));
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
new_title = sanitize_half_width_title(title);
|
new_title = to_sjis(&sanitize_half_width_title(title));
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1127,11 +1162,11 @@ impl NetMDInterface {
|
||||||
let new_title: Vec<u8>;
|
let new_title: Vec<u8>;
|
||||||
let (wchar_value, descriptor) = match wchar {
|
let (wchar_value, descriptor) = match wchar {
|
||||||
true => {
|
true => {
|
||||||
new_title = sanitize_full_width_title(title, false);
|
new_title = to_sjis(&sanitize_full_width_title(title));
|
||||||
(3, Descriptor::AudioUTOC4TD)
|
(3, Descriptor::AudioUTOC4TD)
|
||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
new_title = sanitize_half_width_title(title);
|
new_title = to_sjis(&sanitize_half_width_title(title));
|
||||||
(2, Descriptor::AudioUTOC1TD)
|
(2, Descriptor::AudioUTOC1TD)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1143,7 +1178,7 @@ impl NetMDInterface {
|
||||||
if title == current_title {
|
if title == current_title {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
length_after_encoding_to_jis(¤t_title) as u16
|
length_after_encoding_to_sjis(¤t_title) as u16
|
||||||
}
|
}
|
||||||
Err(error) if error.to_string() == "Rejected" => 0,
|
Err(error) if error.to_string() == "Rejected" => 0,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
|
@ -1232,12 +1267,12 @@ impl NetMDInterface {
|
||||||
Ok(res[0].to_vec().unwrap())
|
Ok(res[0].to_vec().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the length of tracks as a [std::time::Duration] from a set
|
/// Gets the length of tracks as a raw duration from a set
|
||||||
pub async fn track_lengths(
|
pub async fn track_lengths(
|
||||||
&mut self,
|
&mut self,
|
||||||
tracks: Vec<u16>,
|
tracks: Vec<u16>,
|
||||||
) -> Result<Vec<std::time::Duration>, InterfaceError> {
|
) -> Result<Vec<RawTime>, InterfaceError> {
|
||||||
let mut times: Vec<std::time::Duration> = vec![];
|
let mut times = vec![];
|
||||||
|
|
||||||
self.change_descriptor_state(&Descriptor::AudioContentsTD, &DescriptorAction::OpenRead)
|
self.change_descriptor_state(&Descriptor::AudioContentsTD, &DescriptorAction::OpenRead)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -1269,8 +1304,12 @@ impl NetMDInterface {
|
||||||
.map(|v| v.to_i64().unwrap() as u64)
|
.map(|v| v.to_i64().unwrap() as u64)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let length = time_to_duration(×_num);
|
times.push(RawTime {
|
||||||
times.push(length);
|
hours: times_num[0],
|
||||||
|
minutes: times_num[1],
|
||||||
|
seconds: times_num[2],
|
||||||
|
frames: times_num[3],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.change_descriptor_state(&Descriptor::AudioContentsTD, &DescriptorAction::Close)
|
self.change_descriptor_state(&Descriptor::AudioContentsTD, &DescriptorAction::Close)
|
||||||
|
@ -1279,20 +1318,20 @@ impl NetMDInterface {
|
||||||
Ok(times)
|
Ok(times)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the length of a track as a [std::time::Duration]
|
/// Gets the length of a track as a raw duration
|
||||||
pub async fn track_length(
|
pub async fn track_length(&mut self, track: u16) -> Result<RawTime, InterfaceError> {
|
||||||
&mut self,
|
|
||||||
track: u16,
|
|
||||||
) -> Result<std::time::Duration, InterfaceError> {
|
|
||||||
Ok(self.track_lengths([track].into()).await?[0])
|
Ok(self.track_lengths([track].into()).await?[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the encoding of a track (SP, LP2, LP4)
|
/// Gets the encoding of a track (SP, LP2, LP4)
|
||||||
pub async fn track_encoding(&mut self, track_number: u16) -> Result<Encoding, InterfaceError> {
|
pub async fn track_encoding(
|
||||||
|
&mut self,
|
||||||
|
track_number: u16,
|
||||||
|
) -> Result<(Encoding, Channels), InterfaceError> {
|
||||||
let raw_value = self.raw_track_info(track_number, 0x3080, 0x0700).await?;
|
let raw_value = self.raw_track_info(track_number, 0x3080, 0x0700).await?;
|
||||||
let result = scan_query(raw_value, "07 0004 0110 %b %b".to_string())?;
|
let result = scan_query(raw_value, "07 0004 0110 %b %b".to_string())?;
|
||||||
|
|
||||||
let final_encoding = match result[0].to_i64() {
|
let encoding = match result[0].to_i64() {
|
||||||
Ok(0x90) => Encoding::SP,
|
Ok(0x90) => Encoding::SP,
|
||||||
Ok(0x92) => Encoding::LP2,
|
Ok(0x92) => Encoding::LP2,
|
||||||
Ok(0x93) => Encoding::LP4,
|
Ok(0x93) => Encoding::LP4,
|
||||||
|
@ -1300,7 +1339,14 @@ impl NetMDInterface {
|
||||||
Err(_) => unreachable!(),
|
Err(_) => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(final_encoding)
|
let channels = match result[0].to_i64() {
|
||||||
|
Ok(0x01) => Channels::Stereo,
|
||||||
|
Ok(0x00) => Channels::Mono,
|
||||||
|
Ok(e) => return Err(InterfaceError::InvalidEncoding(e as u8)),
|
||||||
|
Err(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((encoding, channels))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a track's flags
|
/// Gets a track's flags
|
||||||
|
@ -1323,36 +1369,34 @@ impl NetMDInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the disc capacity as a [std::time::Duration]
|
/// Gets the disc capacity as a [std::time::Duration]
|
||||||
pub async fn disc_capacity(&mut self) -> Result<[std::time::Duration; 3], InterfaceError> {
|
pub async fn disc_capacity(&mut self) -> Result<[RawTime; 3], InterfaceError> {
|
||||||
self.change_descriptor_state(&Descriptor::RootTD, &DescriptorAction::OpenRead)
|
self.change_descriptor_state(&Descriptor::RootTD, &DescriptorAction::OpenRead)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut query = format_query("1806 02101000 3080 0300 ff00 00000000".to_string(), vec![])?;
|
let mut query = format_query("1806 02101000 3080 0300 ff00 00000000".to_string(), vec![])?;
|
||||||
let reply = self.send_query(&mut query, false, false).await?;
|
let reply = self.send_query(&mut query, false, false).await?;
|
||||||
let mut result: [std::time::Duration; 3] = [std::time::Duration::from_secs(0); 3];
|
|
||||||
|
|
||||||
// 8003 changed to %?03 - Panasonic returns 0803 instead. This byte's meaning is unknown
|
// 8003 changed to %?03 - Panasonic returns 0803 instead. This byte's meaning is unknown
|
||||||
let res = scan_query(
|
let res = scan_query(
|
||||||
reply,
|
reply,
|
||||||
"1806 02101000 3080 0300 1000 001d0000 001b %?03 0017 8000 0005 %W %B %B %B 0005 %W %B %B %B 0005 %W %B %B %B".to_string()
|
"1806 02101000 3080 0300 1000 001d0000 001b %?03 0017 8000 0005 %W %B %B %B 0005 %W %B %B %B 0005 %W %B %B %B".to_string()
|
||||||
)?; //25^
|
)?;
|
||||||
let res_num: Vec<u64> = res
|
|
||||||
.into_iter()
|
|
||||||
.map(|v| v.to_i64().unwrap() as u64)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Create 3 values, `Frames Used`, `Frames Total`, and `Frames Left`
|
let res_num: Vec<RawTime> = res
|
||||||
for i in 0..3 {
|
.windows(4)
|
||||||
let tmp = &res_num[(4 * i)..=(4 * i) + 3];
|
.step_by(4)
|
||||||
let time_micros =
|
.map(|t| RawTime {
|
||||||
(tmp[0] * 3600000000) + (tmp[1] * 60000000) + (tmp[2] * 1000000) + (tmp[3] * 11600);
|
hours: t[0].to_i64().unwrap() as u64,
|
||||||
result[i] = std::time::Duration::from_micros(time_micros);
|
minutes: t[1].to_i64().unwrap() as u64,
|
||||||
}
|
seconds: t[2].to_i64().unwrap() as u64,
|
||||||
|
frames: t[3].to_i64().unwrap() as u64,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
self.change_descriptor_state(&Descriptor::RootTD, &DescriptorAction::Close)
|
self.change_descriptor_state(&Descriptor::RootTD, &DescriptorAction::Close)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(result)
|
Ok(res_num.try_into().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn recording_parameters(&mut self) -> Result<Vec<u8>, InterfaceError> {
|
pub async fn recording_parameters(&mut self) -> Result<Vec<u8>, InterfaceError> {
|
||||||
|
@ -1380,9 +1424,10 @@ impl NetMDInterface {
|
||||||
/// Gets the bytes of a track
|
/// Gets the bytes of a track
|
||||||
///
|
///
|
||||||
/// This can only be executed on an MZ-RH1 / M200
|
/// This can only be executed on an MZ-RH1 / M200
|
||||||
pub async fn save_track_to_array(
|
pub async fn save_track_to_array<F: Fn(usize, usize)>(
|
||||||
&mut self,
|
&mut self,
|
||||||
track: u16,
|
track: u16,
|
||||||
|
progress_callback: Option<F>,
|
||||||
) -> Result<(DiscFormat, u16, Vec<u8>), InterfaceError> {
|
) -> Result<(DiscFormat, u16, Vec<u8>), InterfaceError> {
|
||||||
let mut query = format_query(
|
let mut query = format_query(
|
||||||
"1800 080046 f003010330 ff00 1001 %w".to_string(),
|
"1800 080046 f003010330 ff00 1001 %w".to_string(),
|
||||||
|
@ -1400,14 +1445,17 @@ impl NetMDInterface {
|
||||||
let codec = res[1].to_i64().unwrap() as u8;
|
let codec = res[1].to_i64().unwrap() as u8;
|
||||||
let length = res[2].to_i64().unwrap() as usize;
|
let length = res[2].to_i64().unwrap() as usize;
|
||||||
|
|
||||||
let result = self.device.read_bulk(length, 0x10000).await?;
|
let result = self
|
||||||
|
.device
|
||||||
|
.read_bulk(length, 0x10000, progress_callback)
|
||||||
|
.await?;
|
||||||
|
|
||||||
scan_query(
|
scan_query(
|
||||||
self.read_reply(false).await?,
|
self.read_reply(false).await?,
|
||||||
"1800 080046 f003010330 0000 1001 %?%? %?%?".to_string(),
|
"1800 080046 f003010330 0000 1001 %?%? %?%?".to_string(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
cross_sleep(Duration::from_millis(500)).await;
|
||||||
|
|
||||||
let format: DiscFormat = match codec & 0x06 {
|
let format: DiscFormat = match codec & 0x06 {
|
||||||
0 => DiscFormat::LP4,
|
0 => DiscFormat::LP4,
|
||||||
|
@ -1506,7 +1554,10 @@ impl NetMDInterface {
|
||||||
hostnonce: Vec<u8>,
|
hostnonce: Vec<u8>,
|
||||||
) -> Result<Vec<u8>, InterfaceError> {
|
) -> Result<Vec<u8>, InterfaceError> {
|
||||||
if hostnonce.len() != 8 {
|
if hostnonce.len() != 8 {
|
||||||
return Err(EncryptionError::InvalidLength("host nonce", hostnonce.len()))?;
|
return Err(EncryptionError::InvalidLength(
|
||||||
|
"host nonce",
|
||||||
|
hostnonce.len(),
|
||||||
|
))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut query = format_query(
|
let mut query = format_query(
|
||||||
|
@ -1537,13 +1588,22 @@ impl NetMDInterface {
|
||||||
hex_session_key: &[u8],
|
hex_session_key: &[u8],
|
||||||
) -> Result<(), InterfaceError> {
|
) -> Result<(), InterfaceError> {
|
||||||
if contentid.len() != 20 {
|
if contentid.len() != 20 {
|
||||||
return Err(EncryptionError::InvalidLength("content ID", contentid.len()))?;
|
return Err(EncryptionError::InvalidLength(
|
||||||
|
"content ID",
|
||||||
|
contentid.len(),
|
||||||
|
))?;
|
||||||
}
|
}
|
||||||
if keyenckey.len() != 8 {
|
if keyenckey.len() != 8 {
|
||||||
return Err(EncryptionError::InvalidLength("key encryption", keyenckey.len()))?;
|
return Err(EncryptionError::InvalidLength(
|
||||||
|
"key encryption",
|
||||||
|
keyenckey.len(),
|
||||||
|
))?;
|
||||||
}
|
}
|
||||||
if hex_session_key.len() != 8 {
|
if hex_session_key.len() != 8 {
|
||||||
return Err(EncryptionError::InvalidLength("session key", hex_session_key.len()))?;
|
return Err(EncryptionError::InvalidLength(
|
||||||
|
"session key",
|
||||||
|
hex_session_key.len(),
|
||||||
|
))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut message = [vec![1, 1, 1, 1], contentid.to_vec(), keyenckey.to_vec()].concat();
|
let mut message = [vec![1, 1, 1, 1], contentid.to_vec(), keyenckey.to_vec()].concat();
|
||||||
|
@ -1569,7 +1629,10 @@ impl NetMDInterface {
|
||||||
hex_session_key: &[u8],
|
hex_session_key: &[u8],
|
||||||
) -> Result<(), InterfaceError> {
|
) -> Result<(), InterfaceError> {
|
||||||
if hex_session_key.len() != 8 {
|
if hex_session_key.len() != 8 {
|
||||||
return Err(EncryptionError::InvalidLength("hex session key", hex_session_key.len()))?;
|
return Err(EncryptionError::InvalidLength(
|
||||||
|
"hex session key",
|
||||||
|
hex_session_key.len(),
|
||||||
|
))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut message = [0u8; 8];
|
let mut message = [0u8; 8];
|
||||||
|
@ -1608,7 +1671,10 @@ impl NetMDInterface {
|
||||||
F: Fn(usize, usize),
|
F: Fn(usize, usize),
|
||||||
{
|
{
|
||||||
if hex_session_key.len() != 8 {
|
if hex_session_key.len() != 8 {
|
||||||
return Err(EncryptionError::InvalidLength("hex session key", hex_session_key.len()))?;
|
return Err(EncryptionError::InvalidLength(
|
||||||
|
"hex session key",
|
||||||
|
hex_session_key.len(),
|
||||||
|
))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sharps are slow
|
// Sharps are slow
|
||||||
|
@ -1724,15 +1790,6 @@ pub fn retailmac(key: &[u8], value: &[u8], iv: &[u8; 8]) -> Vec<u8> {
|
||||||
end[..8].to_vec()
|
end[..8].to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref DISC_FOR_WIRE: HashMap<WireFormat, DiscFormat> = HashMap::from([
|
|
||||||
(WireFormat::Pcm, DiscFormat::SPStereo),
|
|
||||||
(WireFormat::LP2, DiscFormat::LP2),
|
|
||||||
(WireFormat::L105kbps, DiscFormat::LP2),
|
|
||||||
(WireFormat::LP4, DiscFormat::LP4),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EKBData {
|
pub struct EKBData {
|
||||||
chains: [[u8; 16]; 2],
|
chains: [[u8; 16]; 2],
|
||||||
depth: i32,
|
depth: i32,
|
||||||
|
@ -1811,7 +1868,7 @@ impl MDTrack {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn frame_size(&self) -> usize {
|
pub fn frame_size(&self) -> usize {
|
||||||
*FRAME_SIZE.get(&self.format).unwrap()
|
self.format.frame_size() as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chunk_size(&self) -> usize {
|
pub fn chunk_size(&self) -> usize {
|
||||||
|
@ -1907,7 +1964,7 @@ impl<'a> MDSession<'a> {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let data_format = track.data_format();
|
let data_format = track.data_format();
|
||||||
let final_disc_format = disc_format.unwrap_or(*DISC_FOR_WIRE.get(&data_format).unwrap());
|
let final_disc_format = disc_format.unwrap_or(data_format.disc_for_wire());
|
||||||
|
|
||||||
let (track_index, uuid, ccid) = self
|
let (track_index, uuid, ccid) = self
|
||||||
.md
|
.md
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,9 +1,6 @@
|
||||||
use crate::netmd::utils;
|
use crate::netmd::utils;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use std::collections::hash_map::HashMap;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
/// %b, w, d, q - explained above (can have endiannes overriden by '>' and '<' operators, f. ex. %>d %<q)
|
/// %b, w, d, q - explained above (can have endiannes overriden by '>' and '<' operators, f. ex. %>d %<q)
|
||||||
/// %s - Uint8Array preceded by 2 bytes of length
|
/// %s - Uint8Array preceded by 2 bytes of length
|
||||||
/// %x - Uint8Array preceded by 2 bytes of length
|
/// %x - Uint8Array preceded by 2 bytes of length
|
||||||
|
@ -11,13 +8,12 @@ lazy_static! {
|
||||||
/// %* - raw Uint8Array
|
/// %* - raw Uint8Array
|
||||||
/// %B - BCD-encoded 1-byte number
|
/// %B - BCD-encoded 1-byte number
|
||||||
/// %W - BCD-encoded 2-byte number
|
/// %W - BCD-encoded 2-byte number
|
||||||
static ref FORMAT_TYPE_LEN_DICT: HashMap<char, i32> = HashMap::from([
|
static FORMAT_TYPE_LEN_DICT: phf::Map<char, i32> = phf::phf_map! {
|
||||||
('b', 1), // byte
|
'b' => 1, // byte
|
||||||
('w', 2), // word
|
'w' => 2, // word
|
||||||
('d', 4), // doubleword
|
'd' => 4, // doubleword
|
||||||
('q', 8), // quadword
|
'q' => 8, // quadword
|
||||||
]);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const DEBUG: bool = false;
|
const DEBUG: bool = false;
|
||||||
|
|
||||||
|
@ -30,18 +26,15 @@ pub enum QueryValue {
|
||||||
#[derive(Error, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Error, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
pub enum ValueError {
|
pub enum ValueError {
|
||||||
#[error("type mismatch: expected {expected}, got {actual}")]
|
#[error("type mismatch: expected {expected}, got {actual}")]
|
||||||
TypeMismatch {
|
TypeMismatch { expected: String, actual: String },
|
||||||
expected: String,
|
|
||||||
actual: String
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QueryValue {
|
impl QueryValue {
|
||||||
pub fn from_array<const S: usize>(value: [u8; S]) -> Self {
|
pub fn _from_array<const S: usize>(value: [u8; S]) -> Self {
|
||||||
Self::Array(value.to_vec())
|
Self::Array(value.to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_array<const S: usize>(&self) -> Result<[u8; S], ValueError> {
|
pub fn _to_array<const S: usize>(&self) -> Result<[u8; S], ValueError> {
|
||||||
let mut array = [0u8; S];
|
let mut array = [0u8; S];
|
||||||
match self {
|
match self {
|
||||||
QueryValue::Array(a) => {
|
QueryValue::Array(a) => {
|
||||||
|
@ -49,10 +42,10 @@ impl QueryValue {
|
||||||
array[i] = *byte
|
array[i] = *byte
|
||||||
}
|
}
|
||||||
Ok(array)
|
Ok(array)
|
||||||
},
|
}
|
||||||
_ => Err(ValueError::TypeMismatch {
|
_ => Err(ValueError::TypeMismatch {
|
||||||
expected: String::from("Vec<u8>"),
|
expected: String::from("Vec<u8>"),
|
||||||
actual: format!("{:?}", self)
|
actual: format!("{:?}", self),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +55,7 @@ impl QueryValue {
|
||||||
QueryValue::Array(a) => Ok(a.to_vec()),
|
QueryValue::Array(a) => Ok(a.to_vec()),
|
||||||
_ => Err(ValueError::TypeMismatch {
|
_ => Err(ValueError::TypeMismatch {
|
||||||
expected: String::from("Vec<u8>"),
|
expected: String::from("Vec<u8>"),
|
||||||
actual: format!("{:?}", self)
|
actual: format!("{:?}", self),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +65,21 @@ impl QueryValue {
|
||||||
QueryValue::Number(a) => Ok(*a),
|
QueryValue::Number(a) => Ok(*a),
|
||||||
_ => Err(ValueError::TypeMismatch {
|
_ => Err(ValueError::TypeMismatch {
|
||||||
expected: String::from("i64"),
|
expected: String::from("i64"),
|
||||||
actual: format!("{:?}", self)
|
actual: format!("{:?}", self),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<i64> for QueryValue {
|
||||||
|
type Error = ValueError;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<i64, Self::Error> {
|
||||||
|
match self {
|
||||||
|
QueryValue::Number(a) => Ok(a),
|
||||||
|
_ => Err(ValueError::TypeMismatch {
|
||||||
|
expected: String::from("i64"),
|
||||||
|
actual: format!("{:?}", self),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +96,7 @@ pub enum QueryError {
|
||||||
expected: u8,
|
expected: u8,
|
||||||
actual: u8,
|
actual: u8,
|
||||||
format_string: String,
|
format_string: String,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Formats a query using a standard input to send to the player
|
/// Formats a query using a standard input to send to the player
|
||||||
|
@ -188,10 +195,7 @@ pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, Qu
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scans a result using a standard input to recieve from the player
|
/// Scans a result using a standard input to recieve from the player
|
||||||
pub fn scan_query(
|
pub fn scan_query(query_result: Vec<u8>, format: String) -> Result<Vec<QueryValue>, QueryError> {
|
||||||
query_result: Vec<u8>,
|
|
||||||
format: String,
|
|
||||||
) -> Result<Vec<QueryValue>, QueryError> {
|
|
||||||
let mut result: Vec<QueryValue> = Vec::new();
|
let mut result: Vec<QueryValue> = Vec::new();
|
||||||
|
|
||||||
let initial_length = query_result.len();
|
let initial_length = query_result.len();
|
||||||
|
@ -297,7 +301,7 @@ pub fn scan_query(
|
||||||
index: i,
|
index: i,
|
||||||
expected: format_value,
|
expected: format_value,
|
||||||
actual: input_value,
|
actual: input_value,
|
||||||
format_string: format
|
format_string: format,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
half = None;
|
half = None;
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
use crate::netmd::mappings::{ALLOWED_HW_KANA, MAPPINGS_DE, MAPPINGS_HW, MAPPINGS_JP, MAPPINGS_RU};
|
use crate::netmd::mappings::{ALLOWED_HW_KANA, MAPPINGS_DE, MAPPINGS_HW, MAPPINGS_JP, MAPPINGS_RU};
|
||||||
|
use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
use diacritics;
|
use diacritics;
|
||||||
use encoding_rs::SHIFT_JIS;
|
use encoding_rs::SHIFT_JIS;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::{collections::hash_map::HashMap, error::Error, vec::IntoIter, time::Duration};
|
use std::{error::Error, io::Write, time::Duration, vec::IntoIter};
|
||||||
use unicode_normalization::UnicodeNormalization;
|
use unicode_normalization::UnicodeNormalization;
|
||||||
|
|
||||||
extern crate kana;
|
extern crate kana;
|
||||||
use kana::*;
|
use kana::*;
|
||||||
|
|
||||||
/// Sleep for a specified number of milliseconds on any platform
|
use super::{
|
||||||
|
interface::DiscFormat,
|
||||||
|
mappings::{HW_TO_FW_RANGE_MAP, MULTI_BYTE_CHARS},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Sleep for a specified [Duration] on any platform
|
||||||
pub async fn cross_sleep(duration: Duration) {
|
pub async fn cross_sleep(duration: Duration) {
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
std::thread::sleep(duration);
|
std::thread::sleep(duration);
|
||||||
|
@ -19,13 +25,13 @@ pub async fn cross_sleep(duration: Duration) {
|
||||||
|
|
||||||
pub fn bcd_to_int(mut bcd: i32) -> i32 {
|
pub fn bcd_to_int(mut bcd: i32) -> i32 {
|
||||||
let mut value = 0;
|
let mut value = 0;
|
||||||
let mut nibble = 0;
|
let mut nybble = 0;
|
||||||
|
|
||||||
while bcd != 0 {
|
while bcd != 0 {
|
||||||
let nibble_value = bcd & 0xf;
|
let nybble_value = bcd & 0xf;
|
||||||
bcd >>= 4;
|
bcd >>= 4;
|
||||||
value += nibble_value * i32::pow(10, nibble);
|
value += nybble_value * i32::pow(10, nybble);
|
||||||
nibble += 1;
|
nybble += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
value
|
value
|
||||||
|
@ -46,25 +52,9 @@ pub fn int_to_bcd(mut value: i32) -> i32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn half_width_to_full_width_range(range: &str) -> String {
|
pub fn half_width_to_full_width_range(range: &str) -> String {
|
||||||
let mappings: HashMap<char, char> = HashMap::from([
|
|
||||||
('0', '0'),
|
|
||||||
('1', '1'),
|
|
||||||
('2', '2'),
|
|
||||||
('3', '3'),
|
|
||||||
('4', '4'),
|
|
||||||
('5', '5'),
|
|
||||||
('6', '6'),
|
|
||||||
('7', '7'),
|
|
||||||
('8', '8'),
|
|
||||||
('9', '9'),
|
|
||||||
('-', '-'),
|
|
||||||
('/', '/'),
|
|
||||||
(';', ';'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
range
|
range
|
||||||
.chars()
|
.chars()
|
||||||
.map(|char| mappings.get(&char).unwrap())
|
.map(|char| HW_TO_FW_RANGE_MAP.get(&char).unwrap())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,30 +65,41 @@ pub fn get_bytes<const S: usize>(iterator: &mut IntoIter<u8>) -> Result<[u8; S],
|
||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn length_after_encoding_to_jis(string: &str) -> usize {
|
pub fn length_after_encoding_to_sjis(string: &str) -> usize {
|
||||||
let new_string = SHIFT_JIS.encode(string);
|
let new_string = SHIFT_JIS.encode(string);
|
||||||
|
|
||||||
new_string.0.len()
|
new_string.0.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_shift_jis(sjis_string: Vec<u8>) -> bool {
|
pub fn validate_sjis(sjis_string: Vec<u8>) -> bool {
|
||||||
let (_, _, had_errors) = SHIFT_JIS.decode(&sjis_string);
|
let (_, _, had_errors) = SHIFT_JIS.decode(&sjis_string);
|
||||||
|
|
||||||
had_errors
|
had_errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure string contains only hardware allowed characters
|
||||||
fn check(string: String) -> Option<String> {
|
fn check(string: String) -> Option<String> {
|
||||||
if MAPPINGS_HW.contains_key(&string) {
|
if MAPPINGS_HW.contains_key(string.as_str()) {
|
||||||
return Some(MAPPINGS_HW.get(&string).unwrap().to_string());
|
return Some(MAPPINGS_HW.get(string.as_str()).unwrap().to_string());
|
||||||
}
|
}
|
||||||
let mut ch = string.chars();
|
let mut ch = string.chars();
|
||||||
if (ch.next().unwrap() as u32) < 0x7f || ALLOWED_HW_KANA.contains(&string) {
|
if (ch.next().unwrap() as u32) < 0x7f || ALLOWED_HW_KANA.contains(&string.as_str()) {
|
||||||
return Some(string);
|
return Some(string);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sanitize_half_width_title(title: &str) -> Vec<u8> {
|
pub fn half_width_title_length(title: &str) -> usize {
|
||||||
|
let multibyte_len = title
|
||||||
|
.chars()
|
||||||
|
.map(|c| (*MULTI_BYTE_CHARS.get(&c).unwrap_or(&0) as usize))
|
||||||
|
.reduce(|a, b| a + b)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
title.len() + multibyte_len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sanitize_half_width_title(title: &str) -> String {
|
||||||
let mut string_title = wide2ascii(title);
|
let mut string_title = wide2ascii(title);
|
||||||
string_title = nowidespace(&string_title);
|
string_title = nowidespace(&string_title);
|
||||||
string_title = hira2kata(&string_title);
|
string_title = hira2kata(&string_title);
|
||||||
|
@ -113,50 +114,46 @@ pub fn sanitize_half_width_title(title: &str) -> Vec<u8> {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let sjis_string = SHIFT_JIS.encode(&new_title).0;
|
new_title
|
||||||
|
|
||||||
if validate_shift_jis(sjis_string.clone().into()) {
|
|
||||||
return agressive_sanitize_title(title).into();
|
|
||||||
}
|
|
||||||
|
|
||||||
sjis_string.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This function is bad, probably should do the string sanitization in the frontend
|
// TODO: This function is bad, probably should do the string sanitization in the frontend
|
||||||
pub fn sanitize_full_width_title(title: &str, just_remap: bool) -> Vec<u8> {
|
pub fn sanitize_full_width_title(title: &str) -> String {
|
||||||
let new_title: String = title
|
let new_title: String = title
|
||||||
.chars()
|
.chars()
|
||||||
|
.map(|c| c.to_string())
|
||||||
.map(|character| {
|
.map(|character| {
|
||||||
match MAPPINGS_JP.get(&character.to_string()) {
|
match MAPPINGS_JP.get(character.to_string().as_str()) {
|
||||||
Some(string) => string.clone(),
|
Some(string) => string,
|
||||||
None => character.to_string().clone(),
|
None => character.as_str(),
|
||||||
}
|
}
|
||||||
.to_string()
|
.to_string()
|
||||||
})
|
})
|
||||||
.map(|character| {
|
.map(|character| {
|
||||||
match MAPPINGS_RU.get(&character.to_string()) {
|
match MAPPINGS_RU.get(character.as_str()) {
|
||||||
Some(string) => string.clone(),
|
Some(string) => string,
|
||||||
None => character.to_string().clone(),
|
None => character.as_str(),
|
||||||
}
|
}
|
||||||
.to_string()
|
.to_string()
|
||||||
})
|
})
|
||||||
.map(|character| {
|
.map(|character| {
|
||||||
match MAPPINGS_DE.get(&character.to_string()) {
|
match MAPPINGS_DE.get(character.as_str()) {
|
||||||
Some(string) => string.clone(),
|
Some(string) => string,
|
||||||
None => character.to_string().clone(),
|
None => character.as_str(),
|
||||||
}
|
}
|
||||||
.to_string()
|
.to_string()
|
||||||
})
|
})
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
if just_remap {
|
new_title
|
||||||
return new_title.into();
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let sjis_string = SHIFT_JIS.encode(&new_title).0;
|
/// Convert a UTF-8 string to Shift-JIS for use on the player
|
||||||
|
pub fn to_sjis(sjis_str: &str) -> Vec<u8> {
|
||||||
|
let sjis_string = SHIFT_JIS.encode(&sjis_str).0;
|
||||||
|
|
||||||
if validate_shift_jis(sjis_string.clone().into()) {
|
if validate_sjis(sjis_string.clone().into()) {
|
||||||
return agressive_sanitize_title(title).into();
|
return agressive_sanitize_title(sjis_str).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
sjis_string.into()
|
sjis_string.into()
|
||||||
|
@ -173,9 +170,148 @@ pub fn agressive_sanitize_title(title: &str) -> String {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn time_to_duration(time: &[u64]) -> std::time::Duration {
|
pub struct AeaOptions<'a> {
|
||||||
assert_eq!(time.len(), 4);
|
pub name: &'a str,
|
||||||
|
pub channels: u32,
|
||||||
|
pub sound_groups: u32,
|
||||||
|
pub group_start: u32,
|
||||||
|
pub encrypted: u32,
|
||||||
|
pub flags: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Default for AeaOptions<'a> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
name: "",
|
||||||
|
channels: 2,
|
||||||
|
sound_groups: 1,
|
||||||
|
group_start: 0,
|
||||||
|
encrypted: 0,
|
||||||
|
flags: &[0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_aea_header(options: AeaOptions) -> Vec<u8> {
|
||||||
|
let encoded_name = options.name.as_bytes();
|
||||||
|
|
||||||
|
let mut header: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
|
header.write_u32::<LittleEndian>(2048).unwrap();
|
||||||
|
header.write_all(encoded_name).unwrap();
|
||||||
|
header
|
||||||
|
.write_all(&vec![0; 256 - encoded_name.len()])
|
||||||
|
.unwrap();
|
||||||
|
header
|
||||||
|
.write_u32::<LittleEndian>(options.sound_groups as u32)
|
||||||
|
.unwrap();
|
||||||
|
header.write_all(&[options.channels as u8, 0]).unwrap();
|
||||||
|
|
||||||
|
// Write the flags
|
||||||
|
header
|
||||||
|
.write_u32::<LittleEndian>(options.flags[0] as u32)
|
||||||
|
.unwrap();
|
||||||
|
header
|
||||||
|
.write_u32::<LittleEndian>(options.flags[1] as u32)
|
||||||
|
.unwrap();
|
||||||
|
header
|
||||||
|
.write_u32::<LittleEndian>(options.flags[2] as u32)
|
||||||
|
.unwrap();
|
||||||
|
header
|
||||||
|
.write_u32::<LittleEndian>(options.flags[3] as u32)
|
||||||
|
.unwrap();
|
||||||
|
header
|
||||||
|
.write_u32::<LittleEndian>(options.flags[4] as u32)
|
||||||
|
.unwrap();
|
||||||
|
header
|
||||||
|
.write_u32::<LittleEndian>(options.flags[5] as u32)
|
||||||
|
.unwrap();
|
||||||
|
header
|
||||||
|
.write_u32::<LittleEndian>(options.flags[6] as u32)
|
||||||
|
.unwrap();
|
||||||
|
header
|
||||||
|
.write_u32::<LittleEndian>(options.flags[7] as u32)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
header.write_u32::<LittleEndian>(0).unwrap();
|
||||||
|
|
||||||
|
header
|
||||||
|
.write_u32::<LittleEndian>(options.encrypted as u32)
|
||||||
|
.unwrap();
|
||||||
|
header
|
||||||
|
.write_u32::<LittleEndian>(options.group_start as u32)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// return the header
|
||||||
|
header
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_wav_header(format: DiscFormat, bytes: u32) -> Vec<u8> {
|
||||||
|
let mut header: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
|
let (joint_stereo, bytes_per_frame) = match format {
|
||||||
|
DiscFormat::LP4 => (192, 0),
|
||||||
|
DiscFormat::LP2 => (96, 1),
|
||||||
|
_ => unreachable!("Cannot create WAV header for disc type {:?}", format),
|
||||||
|
};
|
||||||
|
|
||||||
|
let bytes_per_second = (bytes_per_frame * 44100) / 512;
|
||||||
|
|
||||||
|
header.write_all(r"RIFF".as_bytes()).unwrap();
|
||||||
|
header.write_u32::<LittleEndian>(bytes + 60).unwrap();
|
||||||
|
header.write_all(r"WAVEfmt".as_bytes()).unwrap();
|
||||||
|
header.write_u32::<LittleEndian>(32).unwrap();
|
||||||
|
header.write_u16::<LittleEndian>(0x270).unwrap(); // ATRAC3
|
||||||
|
header.write_u16::<LittleEndian>(2).unwrap(); // Stereo
|
||||||
|
header.write_u32::<LittleEndian>(44100).unwrap();
|
||||||
|
header.write_u32::<LittleEndian>(bytes_per_second).unwrap();
|
||||||
|
header
|
||||||
|
.write_u16::<LittleEndian>(bytes_per_frame as u16 * 2)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
header.write_all(&[0, 0]).unwrap();
|
||||||
|
|
||||||
|
header.write_u16::<LittleEndian>(14).unwrap();
|
||||||
|
header.write_u16::<LittleEndian>(1).unwrap();
|
||||||
|
header.write_u32::<LittleEndian>(bytes_per_frame).unwrap();
|
||||||
|
header.write_u16::<LittleEndian>(joint_stereo).unwrap();
|
||||||
|
header.write_u16::<LittleEndian>(joint_stereo).unwrap();
|
||||||
|
|
||||||
|
header.write_u16::<LittleEndian>(1).unwrap();
|
||||||
|
header.write_u16::<LittleEndian>(0).unwrap();
|
||||||
|
|
||||||
|
header.write_all(r"data".as_bytes()).unwrap();
|
||||||
|
|
||||||
|
header.write_u32::<LittleEndian>(bytes).unwrap();
|
||||||
|
|
||||||
|
header
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct RawTime {
|
||||||
|
pub hours: u64,
|
||||||
|
pub minutes: u64,
|
||||||
|
pub seconds: u64,
|
||||||
|
pub frames: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Duration> for RawTime {
|
||||||
|
fn into(self) -> std::time::Duration {
|
||||||
|
self.as_duration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTime {
|
||||||
|
pub fn as_duration(&self) -> Duration {
|
||||||
std::time::Duration::from_micros(
|
std::time::Duration::from_micros(
|
||||||
(time[0] * 3600000000) + (time[1] * 60000000) + (time[2] * 1000000) + (time[3] * 11600),
|
(self.hours * 3600000000)
|
||||||
|
+ (self.minutes * 60000000)
|
||||||
|
+ (self.seconds * 1000000)
|
||||||
|
+ (self.frames * 11600),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_frames(&self) -> u64 {
|
||||||
|
((self.hours * 60 + self.minutes) * 60 + self.seconds) * 512 + self.frames
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue