Implemented all functions in commands.rs

This commit is contained in:
G2-Games 2024-05-28 21:44:15 -05:00
parent bf66c59799
commit 291e0ba049
8 changed files with 1126 additions and 981 deletions

View file

@ -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"

View file

@ -26,7 +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"
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 +39,10 @@ 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"] } 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

View file

@ -1,7 +1,6 @@
#![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;
@ -15,58 +14,54 @@ 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 {vendor_id: 0x04dd, product_id: 0x7202, name: Some("Sharp IM-MT899H")},
Box::new([ DeviceId {vendor_id: 0x04dd, product_id: 0x9013, name: Some("Sharp IM-DR400")},
DeviceId {vendor_id: 0x04dd, product_id: 0x7202, name: Some(String::from("Sharp IM-MT899H"))}, DeviceId {vendor_id: 0x04dd, product_id: 0x9014, name: Some("Sharp IM-DR80")},
DeviceId {vendor_id: 0x04dd, product_id: 0x9013, name: Some(String::from("Sharp IM-DR400"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0034, name: Some("Sony PCLK-XX")},
DeviceId {vendor_id: 0x04dd, product_id: 0x9014, name: Some(String::from("Sharp IM-DR80"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0036, name: Some("Sony")},
DeviceId {vendor_id: 0x054c, product_id: 0x0034, name: Some(String::from("Sony PCLK-XX"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0075, name: Some("Sony MZ-N1")},
DeviceId {vendor_id: 0x054c, product_id: 0x0036, name: Some(String::from("Sony"))}, DeviceId {vendor_id: 0x054c, product_id: 0x007c, name: Some("Sony")},
DeviceId {vendor_id: 0x054c, product_id: 0x0075, name: Some(String::from("Sony MZ-N1"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0080, name: Some("Sony LAM-1")},
DeviceId {vendor_id: 0x054c, product_id: 0x007c, name: Some(String::from("Sony"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0081, name: Some("Sony MDS-JB980/MDS-NT1/MDS-JE780")},
DeviceId {vendor_id: 0x054c, product_id: 0x0080, name: Some(String::from("Sony LAM-1"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0084, name: Some("Sony MZ-N505")},
DeviceId {vendor_id: 0x054c, product_id: 0x0081, name: Some(String::from("Sony MDS-JB980/MDS-NT1/MDS-JE780"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0085, name: Some("Sony MZ-S1")},
DeviceId {vendor_id: 0x054c, product_id: 0x0084, name: Some(String::from("Sony MZ-N505"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0086, name: Some("Sony MZ-N707")},
DeviceId {vendor_id: 0x054c, product_id: 0x0085, name: Some(String::from("Sony MZ-S1"))}, DeviceId {vendor_id: 0x054c, product_id: 0x008e, name: Some("Sony CMT-C7NT")},
DeviceId {vendor_id: 0x054c, product_id: 0x0086, name: Some(String::from("Sony MZ-N707"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0097, name: Some("Sony PCGA-MDN1")},
DeviceId {vendor_id: 0x054c, product_id: 0x008e, name: Some(String::from("Sony CMT-C7NT"))}, DeviceId {vendor_id: 0x054c, product_id: 0x00ad, name: Some("Sony CMT-L7HD")},
DeviceId {vendor_id: 0x054c, product_id: 0x0097, name: Some(String::from("Sony PCGA-MDN1"))}, DeviceId {vendor_id: 0x054c, product_id: 0x00c6, name: Some("Sony MZ-N10")},
DeviceId {vendor_id: 0x054c, product_id: 0x00ad, name: Some(String::from("Sony CMT-L7HD"))}, DeviceId {vendor_id: 0x054c, product_id: 0x00c7, name: Some("Sony MZ-N910")},
DeviceId {vendor_id: 0x054c, product_id: 0x00c6, name: Some(String::from("Sony MZ-N10"))}, DeviceId {vendor_id: 0x054c, product_id: 0x00c8, name: Some("Sony MZ-N710/NF810")},
DeviceId {vendor_id: 0x054c, product_id: 0x00c7, name: Some(String::from("Sony MZ-N910"))}, DeviceId {vendor_id: 0x054c, product_id: 0x00c9, name: Some("Sony MZ-N510/N610")},
DeviceId {vendor_id: 0x054c, product_id: 0x00c8, name: Some(String::from("Sony MZ-N710/NF810"))}, DeviceId {vendor_id: 0x054c, product_id: 0x00ca, name: Some("Sony MZ-NE410/NF520D")},
DeviceId {vendor_id: 0x054c, product_id: 0x00c9, name: Some(String::from("Sony MZ-N510/N610"))}, DeviceId {vendor_id: 0x054c, product_id: 0x00e7, name: Some("Sony CMT-M333NT/M373NT")},
DeviceId {vendor_id: 0x054c, product_id: 0x00ca, name: Some(String::from("Sony MZ-NE410/NF520D"))}, DeviceId {vendor_id: 0x054c, product_id: 0x00eb, name: Some("Sony MZ-NE810/NE910")},
DeviceId {vendor_id: 0x054c, product_id: 0x00e7, name: Some(String::from("Sony CMT-M333NT/M373NT"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0101, name: Some("Sony LAM")},
DeviceId {vendor_id: 0x054c, product_id: 0x00eb, name: Some(String::from("Sony MZ-NE810/NE910"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0113, name: Some("Aiwa AM-NX1")},
DeviceId {vendor_id: 0x054c, product_id: 0x0101, name: Some(String::from("Sony LAM"))}, DeviceId {vendor_id: 0x054c, product_id: 0x013f, name: Some("Sony MDS-S500")},
DeviceId {vendor_id: 0x054c, product_id: 0x0113, name: Some(String::from("Aiwa AM-NX1"))}, DeviceId {vendor_id: 0x054c, product_id: 0x014c, name: Some("Aiwa AM-NX9")},
DeviceId {vendor_id: 0x054c, product_id: 0x013f, name: Some(String::from("Sony MDS-S500"))}, DeviceId {vendor_id: 0x054c, product_id: 0x017e, name: Some("Sony MZ-NH1")},
DeviceId {vendor_id: 0x054c, product_id: 0x014c, name: Some(String::from("Aiwa AM-NX9"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0180, name: Some("Sony MZ-NH3D")},
DeviceId {vendor_id: 0x054c, product_id: 0x017e, name: Some(String::from("Sony MZ-NH1"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0182, name: Some("Sony MZ-NH900")},
DeviceId {vendor_id: 0x054c, product_id: 0x0180, name: Some(String::from("Sony MZ-NH3D"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0184, name: Some("Sony MZ-NH700/NH800")},
DeviceId {vendor_id: 0x054c, product_id: 0x0182, name: Some(String::from("Sony MZ-NH900"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0186, name: Some("Sony MZ-NH600")},
DeviceId {vendor_id: 0x054c, product_id: 0x0184, name: Some(String::from("Sony MZ-NH700/NH800"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0187, name: Some("Sony MZ-NH600D")},
DeviceId {vendor_id: 0x054c, product_id: 0x0186, name: Some(String::from("Sony MZ-NH600"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0188, name: Some("Sony MZ-N920")},
DeviceId {vendor_id: 0x054c, product_id: 0x0187, name: Some(String::from("Sony MZ-NH600D"))}, DeviceId {vendor_id: 0x054c, product_id: 0x018a, name: Some("Sony LAM-3")},
DeviceId {vendor_id: 0x054c, product_id: 0x0188, name: Some(String::from("Sony MZ-N920"))}, DeviceId {vendor_id: 0x054c, product_id: 0x01e9, name: Some("Sony MZ-DH10P")},
DeviceId {vendor_id: 0x054c, product_id: 0x018a, name: Some(String::from("Sony LAM-3"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0219, name: Some("Sony MZ-RH10")},
DeviceId {vendor_id: 0x054c, product_id: 0x01e9, name: Some(String::from("Sony MZ-DH10P"))}, DeviceId {vendor_id: 0x054c, product_id: 0x021b, name: Some("Sony MZ-RH710/MZ-RH910")},
DeviceId {vendor_id: 0x054c, product_id: 0x0219, name: Some(String::from("Sony MZ-RH10"))}, DeviceId {vendor_id: 0x054c, product_id: 0x021d, name: Some("Sony CMT-AH10")},
DeviceId {vendor_id: 0x054c, product_id: 0x021b, name: Some(String::from("Sony MZ-RH710/MZ-RH910"))}, DeviceId {vendor_id: 0x054c, product_id: 0x022c, name: Some("Sony CMT-AH10")},
DeviceId {vendor_id: 0x054c, product_id: 0x021d, name: Some(String::from("Sony CMT-AH10"))}, DeviceId {vendor_id: 0x054c, product_id: 0x023c, name: Some("Sony DS-HMD1")},
DeviceId {vendor_id: 0x054c, product_id: 0x022c, name: Some(String::from("Sony CMT-AH10"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0286, name: Some("Sony MZ-RH1")},
DeviceId {vendor_id: 0x054c, product_id: 0x023c, name: Some(String::from("Sony DS-HMD1"))}, DeviceId {vendor_id: 0x054c, product_id: 0x011a, name: Some("Sony CMT-SE7")},
DeviceId {vendor_id: 0x054c, product_id: 0x0286, name: Some(String::from("Sony MZ-RH1"))}, DeviceId {vendor_id: 0x054c, product_id: 0x0148, name: Some("Sony MDS-A1")},
DeviceId {vendor_id: 0x054c, product_id: 0x011a, name: Some(String::from("Sony CMT-SE7"))}, DeviceId {vendor_id: 0x0b28, product_id: 0x1004, name: Some("Kenwood MDX-J9")},
DeviceId {vendor_id: 0x054c, product_id: 0x0148, name: Some(String::from("Sony MDS-A1"))}, DeviceId {vendor_id: 0x04da, product_id: 0x23b3, name: Some("Panasonic SJ-MR250")},
DeviceId {vendor_id: 0x0b28, product_id: 0x1004, name: Some(String::from("Kenwood MDX-J9"))}, DeviceId {vendor_id: 0x04da, product_id: 0x23b6, name: Some("Panasonic SJ-MR270")},
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 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|{
@ -94,7 +89,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 +151,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 +305,22 @@ 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 +338,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);
} }

View file

@ -1,15 +1,16 @@
#![cfg_attr(debug_assertions, allow(dead_code))] #![cfg_attr(debug_assertions, allow(dead_code))]
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 cross_usb::Descriptor;
use crate::netmd::interface::DiscFlag; use crate::netmd::interface::DiscFlag;
use crate::netmd::utils::RawTime; use crate::netmd::utils::{create_aea_header, create_wav_header, AeaOptions, RawTime};
use super::interface::{Channels, Encoding, InterfaceError, MDSession, MDTrack, NetMDInterface, TrackFlag}; use super::interface::{Channels, Direction, DiscFormat, Encoding, InterfaceError, MDSession, MDTrack, NetMDInterface, TrackFlag};
use super::utils::cross_sleep; 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 {
@ -49,17 +50,19 @@ pub struct Track {
} }
impl Track { impl Track {
pub fn chars_to_cells(len: usize) -> usize { pub fn cells_for_title(&self) -> (usize, usize) {
f32::ceil(len as f32 / 7.0) as usize
}
pub async fn cells_for_title(&mut self) {
let encoding_name_correction = match self.encoding { let encoding_name_correction = match self.encoding {
Encoding::SP => 0, Encoding::SP => 0,
_ => 1 _ => 1
}; };
let full_width_length = Self::chars_to_cells(self.full_width_title.len() * 2); 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),
)
} }
} }
@ -83,18 +86,142 @@ pub struct Disc {
} }
impl Disc { impl Disc {
pub async fn track_count(&self) -> u16 { pub fn track_count(&self) -> u16 {
self.groups.iter() self.groups.iter()
.map(|g| g.tracks.len()) .map(|g| g.tracks.len())
.reduce(|acc, s| acc + s) .reduce(|acc, s| acc + s)
.unwrap() as u16 .unwrap() as u16
} }
pub async fn tracks(&self) -> Vec<Track> { pub fn tracks(&self) -> Vec<Track> {
self.groups.iter() self.groups.iter()
.flat_map(|g| g.tracks.clone()) .flat_map(|g| g.tracks.clone())
.collect() .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!("{}", 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 {
@ -111,7 +238,6 @@ impl NetMDContext {
}) })
} }
/*
/// Change to the next track (skip forward) /// Change to the next track (skip forward)
pub async fn next_track(&mut self) -> Result<(), InterfaceError> { pub async fn next_track(&mut self) -> Result<(), InterfaceError> {
self.interface.track_change(Direction::Next).await self.interface.track_change(Direction::Next).await
@ -126,7 +252,6 @@ impl NetMDContext {
pub async fn restart_track(&mut self) -> Result<(), InterfaceError> { pub async fn restart_track(&mut self) -> Result<(), InterfaceError> {
self.interface.track_change(Direction::Restart).await self.interface.track_change(Direction::Restart).await
} }
*/
pub async fn device_status(&mut self) -> Result<DeviceStatus, Box<dyn Error>> { pub async fn device_status(&mut self) -> Result<DeviceStatus, Box<dyn Error>> {
let status = self.interface.status().await?; let status = self.interface.status().await?;
@ -158,46 +283,6 @@ impl NetMDContext {
}) })
} }
pub async fn prepare_download(&mut self) -> Result<(), Box<dyn Error>> {
while ![OperatingStatus::DiscBlank, OperatingStatus::Ready].contains(
&self.device_status()
.await?
.state
.unwrap_or(OperatingStatus::NoDisc),
) {
cross_sleep(Duration::from_millis(200)).await;
}
let _ = self.interface.session_key_forget().await;
let _ = self.interface.leave_secure_session().await;
self.interface.acquire().await?;
let _ = self.interface.disable_new_track_protection(1).await;
Ok(())
}
pub async fn download<F>(
&mut self,
track: MDTrack,
progress_callback: F,
) -> Result<(u16, Vec<u8>, Vec<u8>), Box<dyn Error>>
where
F: Fn(usize, usize),
{
self.prepare_download().await?;
// Lock the interface by providing it to the session
let mut session = MDSession::new(&mut self.interface);
session.init().await?;
let result = session
.download_track(track, progress_callback, None)
.await?;
session.close().await?;
self.interface.release().await?;
Ok(result)
}
pub async fn list_content(&mut self) -> Result<Disc, Box<dyn Error>> { pub async fn list_content(&mut self) -> Result<Disc, Box<dyn Error>> {
let flags = self.interface.disc_flags().await?; let flags = self.interface.disc_flags().await?;
let title = self.interface.disc_title(false).await?; let title = self.interface.disc_title(false).await?;
@ -265,4 +350,157 @@ impl NetMDContext {
Ok(disc) 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("");
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("/^.*?/").unwrap();
new_fw_name_with_groups = re.replace_all(
&old_raw_fw_name,
if !new_fw_name.as_ref().unwrap().is_empty() {
format!("{}", new_fw_name.unwrap())
} else {
String::new()
}
).into()
} else {
new_fw_name_with_groups = format!(r"{}{}", 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>> {
while ![OperatingStatus::DiscBlank, OperatingStatus::Ready].contains(
&self.device_status()
.await?
.state
.unwrap_or(OperatingStatus::NoDisc),
) {
cross_sleep(Duration::from_millis(200)).await;
}
let _ = self.interface.session_key_forget().await;
let _ = self.interface.leave_secure_session().await;
self.interface.acquire().await?;
let _ = self.interface.disable_new_track_protection(1).await;
Ok(())
}
pub async fn download<F>(
&mut self,
track: MDTrack,
progress_callback: F,
) -> Result<(u16, Vec<u8>, Vec<u8>), Box<dyn Error>>
where
F: Fn(usize, usize),
{
self.prepare_download().await?;
// Lock the interface by providing it to the session
let mut session = MDSession::new(&mut self.interface);
session.init().await?;
let result = session
.download_track(track, progress_callback, None)
.await?;
session.close().await?;
self.interface.release().await?;
Ok(result)
}
}
fn chars_to_cells(len: usize) -> usize {
f32::ceil(len as f32 / 7.0) as usize
} }

View file

@ -73,5 +73,7 @@ pub fn new_thread_encryptor(
} }
}); });
rx rx
} }

View file

@ -18,7 +18,7 @@ use std::error::Error;
use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::mpsc::UnboundedReceiver;
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 {
@ -34,7 +34,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,
@ -846,7 +846,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)
@ -1098,11 +1098,11 @@ impl NetMDInterface {
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
} }
}; };
@ -1156,11 +1156,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)
} }
}; };
@ -1418,9 +1418,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(),
@ -1438,14 +1439,14 @@ 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,

File diff suppressed because it is too large Load diff

View file

@ -2,12 +2,15 @@ use crate::netmd::mappings::{ALLOWED_HW_KANA, MAPPINGS_DE, MAPPINGS_HW, MAPPINGS
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;
use byteorder::{LittleEndian, WriteBytesExt};
extern crate kana; extern crate kana;
use kana::*; use kana::*;
use super::{interface::DiscFormat, mappings::{HW_TO_FW_RANGE_MAP, MULTI_BYTE_CHARS}};
/// Sleep for a specified [Duration] on any platform /// 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"))]
@ -19,13 +22,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 +49,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', ''),
('1', ''),
('2', ''),
('3', ''),
('4', ''),
('5', ''),
('6', ''),
('7', ''),
('8', ''),
('9', ''),
('-', ''),
('/', ''),
(';', ''),
]);
range range
.chars() .chars()
.map(|char| mappings.get(&char).unwrap()) .map(|char| HW_TO_FW_RANGE_MAP.get(&char).unwrap())
.collect() .collect()
} }
@ -99,11 +86,15 @@ fn check(string: String) -> Option<String> {
None None
} }
fn half_width_title_length(title: &str) { 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) -> Vec<u8> { 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);
@ -118,17 +109,11 @@ 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_sjis(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(|c| c.to_string())
@ -155,14 +140,15 @@ pub fn sanitize_full_width_title(title: &str, just_remap: bool) -> Vec<u8> {
}) })
.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_sjis(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()
@ -179,6 +165,97 @@ pub fn agressive_sanitize_title(title: &str) -> String {
.into() .into()
} }
pub struct AeaOptions<'a> {
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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct RawTime { pub struct RawTime {
pub hours: u64, pub hours: u64,