From 8479c80ebe7a9e52158451dad59d16bf6eac4ebb Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Sat, 11 Mar 2023 18:36:30 +0100 Subject: [PATCH] sc64deployer done --- docs/06_manufacturing_guidelines.md | 6 +- sw/deployer/src/disk.rs | 322 ++++++++++++++++++++++++++++ sw/deployer/src/main.rs | 225 ++++++++++++++++--- sw/deployer/src/sc64/mod.rs | 12 +- sw/deployer/src/sc64/types.rs | 21 +- sw/tools/.gitignore | 1 - 6 files changed, 541 insertions(+), 46 deletions(-) create mode 100644 sw/deployer/src/disk.rs delete mode 100644 sw/tools/.gitignore diff --git a/docs/06_manufacturing_guidelines.md b/docs/06_manufacturing_guidelines.md index 4880f60..d5058ff 100644 --- a/docs/06_manufacturing_guidelines.md +++ b/docs/06_manufacturing_guidelines.md @@ -90,9 +90,9 @@ Second, program FPGA, microcontroller and bootloader: 4. Check in device manager which port number `COMx` is assigned to serial adapter 5. Connect SC64 board to the PC with USB-C cable (***IMPORTANT:*** connect it to the same computer as serial adapter) 6. Locate `primer.py` script in root folder - 7. Make sure these files are located in the same folder as `primer.py` script: `requirements.txt`, `sc64-firmware.bin` + 7. Make sure these files are located in the same folder as `primer.py` script: `requirements.txt`, `sc64-firmware-{version}.bin` 8. Run `pip3 install -r requirements.txt` to install required python packages - 9. Run `python3 primer.py COMx sc64-firmware.bin` (replace `COMx` with port located in step **4**) + 9. Run `python3 primer.py COMx sc64-firmware-{version}.bin` (replace `COMx` with port located in step **4**) 10. Follow the instructions on the screen 11. Wait until programming process has finished (**DO NOT STOP PROGRAMMING PROCESS OR DISCONNECT SC64 BOARD FROM PC**, doing so might irrecoverably break programming through UART header and you would need to program FPGA and/or microcontroller with separate dedicated programming interfaces through *Tag-Connect* connector on the PCB) @@ -107,4 +107,4 @@ Congratulations! Your SC64 flashcart should be ready for use! This issue can be attributed to incorrectly programmed FT232H EEPROM in the first programming step. Check again in `FT_PROG` application if device was configured properly. Once FPGA and microcontroller has been programmed successfully `primer.py` script needs to be run in special mode. -Please use command `python3 primer.py COMx sc64-firmware.bin --bootloader-only` to try programming bootloader again. +Please use command `python3 primer.py COMx sc64-firmware-{version}.bin --bootloader-only` to try programming bootloader again. diff --git a/sw/deployer/src/disk.rs b/sw/deployer/src/disk.rs new file mode 100644 index 0000000..a8ec0c7 --- /dev/null +++ b/sw/deployer/src/disk.rs @@ -0,0 +1,322 @@ +use crate::sc64::Error; +use std::{ + collections::HashMap, + fs::File, + io::{Read, Seek, SeekFrom, Write}, +}; + +const BLOCKS_PER_TRACK: usize = 2; +const SECTORS_PER_BLOCK: usize = 85; +const SYSTEM_SECTOR_LENGTH: usize = 232; +const BAD_TRACKS_PER_ZONE: usize = 12; + +#[derive(Clone, Copy, PartialEq)] +pub enum Format { + Retail, + Development, +} + +struct SystemAreaInfo<'a> { + format: Format, + sector_length: usize, + sys_lba: &'a [usize], + bad_lba: &'a [usize], +} + +const SYSTEM_AREA: [SystemAreaInfo; 2] = [ + SystemAreaInfo { + format: Format::Retail, + sector_length: 232, + sys_lba: &[9, 8, 1, 0], + bad_lba: &[2, 3, 10, 11, 12, 16, 17, 18, 19, 20, 21, 22, 23], + }, + SystemAreaInfo { + format: Format::Development, + sector_length: 192, + sys_lba: &[11, 10, 3, 2], + bad_lba: &[0, 1, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23], + }, +]; + +const ID_LBAS: [usize; 2] = [15, 14]; + +struct DiskZone { + head: usize, + sector_length: usize, + tracks: usize, + track_offset: usize, +} + +macro_rules! zone { + ($h:expr, $l:expr, $t:expr, $o:expr) => { + DiskZone { + head: $h, + sector_length: $l, + tracks: $t, + track_offset: $o, + } + }; +} + +const DISK_ZONES: [DiskZone; 16] = [ + zone!(0, 232, 158, 0), + zone!(0, 216, 158, 158), + zone!(0, 208, 149, 316), + zone!(0, 192, 149, 465), + zone!(0, 176, 149, 614), + zone!(0, 160, 149, 763), + zone!(0, 144, 149, 912), + zone!(0, 128, 114, 1061), + zone!(1, 216, 158, 157), + zone!(1, 208, 158, 315), + zone!(1, 192, 149, 464), + zone!(1, 176, 149, 613), + zone!(1, 160, 149, 762), + zone!(1, 144, 149, 911), + zone!(1, 128, 149, 1060), + zone!(1, 112, 114, 1174), +]; + +const DISK_VZONE_TO_PZONE: [[usize; 16]; 7] = [ + [0, 1, 2, 9, 8, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10], + [0, 1, 2, 3, 10, 9, 8, 4, 5, 6, 7, 15, 14, 13, 12, 11], + [0, 1, 2, 3, 4, 11, 10, 9, 8, 5, 6, 7, 15, 14, 13, 12], + [0, 1, 2, 3, 4, 5, 12, 11, 10, 9, 8, 6, 7, 15, 14, 13], + [0, 1, 2, 3, 4, 5, 6, 13, 12, 11, 10, 9, 8, 7, 15, 14], + [0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8, 15], + [0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8], +]; + +struct Mapping { + lba: usize, + offset: usize, + length: usize, + writable: bool, +} + +pub struct Disk { + file: File, + format: Format, + mapping: HashMap, +} + +impl Disk { + pub fn get_format(&self) -> &Format { + &self.format + } + + pub fn get_lba(&self, track: u32, head: u32, block: u32) -> Option { + if head == 0 && track < 12 { + return Some((track << 1 | block ^ (track % 2)) as usize); + } + let location = track << 2 | head << 1 | block; + self.mapping + .get(&(location as usize)) + .map(|block| block.lba) + } + + pub fn read_block( + &mut self, + track: u32, + head: u32, + block: u32, + ) -> Result>, Error> { + let location = track << 2 | head << 1 | block; + if let Some(block) = self.mapping.get(&(location as usize)) { + let mut data = vec![0u8; block.length]; + self.file.seek(SeekFrom::Start(block.offset as u64))?; + self.file.read_exact(&mut data)?; + return Ok(Some(data)); + } + Ok(None) + } + + pub fn write_block( + &mut self, + track: u32, + head: u32, + block: u32, + data: &[u8], + ) -> Result, Error> { + let location = track << 2 | head << 1 | block; + if let Some(block) = self.mapping.get(&(location as usize)) { + if block.length == data.len() && block.writable { + self.file.seek(SeekFrom::Start(block.offset as u64))?; + self.file.write_all(data)?; + return Ok(Some(())); + } + } + Ok(None) + } +} + +pub fn open(path: &str) -> Result { + let mut file = File::options().read(true).write(true).open(path)?; + let (format, mapping) = load_ndd(&mut file)?; + Ok(Disk { + file, + format, + mapping, + }) +} + +pub fn open_multiple(paths: &[String]) -> Result, Error> { + let mut disks: Vec = Vec::new(); + + for path in paths { + let disk = open(path)?; + disks.push(disk); + } + + if !disks.windows(2).all(|d| d[0].format == d[1].format) { + return Err(Error::new("Disk format mismatch")); + } + + Ok(disks) +} + +fn load_ndd(file: &mut File) -> Result<(Format, HashMap), Error> { + let mut disk_format: Option = None; + let mut disk_type: u8 = 0; + let mut sys_data = vec![0u8; SYSTEM_SECTOR_LENGTH]; + let mut bad_lba: Vec = Vec::new(); + + for info in SYSTEM_AREA { + bad_lba.clear(); + for &lba in info.sys_lba { + let data = load_sys_lba(file, lba)?; + if verify_sys_lba(&data, info.sector_length) { + if (data[4] != 0x10) || ((data[5] & 0xF0) != 0x10) { + bad_lba.push(lba); + } else { + disk_format = Some(info.format); + disk_type = data[5] & 0x0F; + sys_data = data[0..SYSTEM_SECTOR_LENGTH].to_vec(); + } + } else { + bad_lba.push(lba); + } + } + if disk_format.is_some() { + bad_lba.append(&mut info.bad_lba.to_vec()); + break; + } + } + + if disk_format.is_none() { + return Err(Error::new("Provided 64DD disk file is not valid")); + } + if disk_type >= DISK_VZONE_TO_PZONE.len() as u8 { + return Err(Error::new("Unknown disk type")); + } + + let mut id_lba_valid = false; + for lba in ID_LBAS { + let data = load_sys_lba(file, lba)?; + let valid = verify_sys_lba(&data, SYSTEM_SECTOR_LENGTH); + if !valid { + bad_lba.push(lba); + } + id_lba_valid |= valid; + } + + if !id_lba_valid { + return Err(Error::new("No valid ID LBA found")); + } + + let mut zone_bad_tracks: Vec> = Vec::new(); + + for (zone, info) in DISK_ZONES.iter().enumerate() { + let mut bad_tracks: Vec = Vec::new(); + let start = if zone == 0 { 0 } else { sys_data[0x07 + zone] }; + let stop = sys_data[0x07 + zone + 1]; + for offset in start..stop { + bad_tracks.push(sys_data[0x20 + offset as usize] as usize); + } + for track in 0..(BAD_TRACKS_PER_ZONE - bad_tracks.len()) { + bad_tracks.push(info.tracks - track - 1); + } + zone_bad_tracks.push(bad_tracks); + } + + let mut mapping = HashMap::new(); + + let mut current_lba: usize = 0; + let mut starting_block: usize = 0; + let mut file_offset: usize = 0; + + for zone in DISK_VZONE_TO_PZONE[disk_type as usize] { + let DiskZone { + head, + sector_length, + tracks, + track_offset, + } = DISK_ZONES[zone]; + + let mut track = track_offset; + + for zone_track in 0..tracks { + let current_zone_track = if head == 0 { + zone_track + } else { + (tracks - 1) - zone_track + }; + + if zone_bad_tracks[zone].contains(¤t_zone_track) { + track = if head == 0 { + track + 1 + } else { + track.saturating_sub(1) + }; + continue; + } + + for block in 0..BLOCKS_PER_TRACK { + let location = track << 2 | head << 1 | (starting_block ^ block); + let length = sector_length * SECTORS_PER_BLOCK; + if !bad_lba.contains(¤t_lba) { + mapping.insert( + location, + Mapping { + lba: current_lba, + offset: file_offset, + length, + writable: true, + }, + ); + } + file_offset += length; + current_lba += 1; + } + + track = if head == 0 { + track + 1 + } else { + track.saturating_sub(1) + }; + starting_block ^= 1; + } + } + + Ok((disk_format.unwrap(), mapping)) +} + +fn load_sys_lba(file: &mut File, lba: usize) -> Result, Error> { + let length = SYSTEM_SECTOR_LENGTH * SECTORS_PER_BLOCK; + file.seek(SeekFrom::Start((lba * length) as u64))?; + let mut data = vec![0u8; length]; + file.read_exact(&mut data)?; + Ok(data) +} + +fn verify_sys_lba(data: &[u8], sector_length: usize) -> bool { + let sys_data = &data[0..sector_length]; + for sector in 1..SECTORS_PER_BLOCK { + let offset = sector * sector_length; + let verify_data = &data[offset..(offset + sector_length)]; + if sys_data != verify_data { + return false; + } + } + true +} diff --git a/sw/deployer/src/main.rs b/sw/deployer/src/main.rs index 6fb4863..3188d9d 100644 --- a/sw/deployer/src/main.rs +++ b/sw/deployer/src/main.rs @@ -1,13 +1,13 @@ mod debug; +mod disk; mod n64; -pub mod sc64; +mod sc64; use chrono::Local; use clap::{Args, Parser, Subcommand, ValueEnum}; use clap_num::maybe_hex_range; use colored::Colorize; use panic_message::panic_message; -use sc64::ServerEvent; use std::{ fs::File, io::{stdin, stdout, Read, Write}, @@ -27,7 +27,7 @@ struct Cli { command: Commands, /// Connect to SC64 device on provided serial port - #[arg(short, long, conflicts_with = "remote")] + #[arg(short, long)] port: Option, /// Connect to SC64 device on provided remote address @@ -49,7 +49,7 @@ enum Commands { command: DownloadCommands, }, - /// Upload ROM, 64DD IPL and run disk server + /// Upload ROM (and save), 64DD IPL then run disk server _64DD(_64DDArgs), /// Enter debug mode @@ -58,7 +58,7 @@ enum Commands { /// Dump data from arbitrary location in SC64 memory space Dump(DumpArgs), - /// Print information about SC64 device + /// Print information about connected SC64 device Info, /// Update persistent settings on SC64 device @@ -98,8 +98,8 @@ struct UploadArgs { #[arg(short, long)] no_shadow: bool, - /// Force TV type (ignored when used in conjunction with direct boot mode) - #[arg(long)] + /// Force TV type + #[arg(long, conflicts_with = "direct")] tv: Option, } @@ -117,19 +117,32 @@ struct DownloadArgs { #[derive(Args)] struct _64DDArgs { - /// Path to the ROM file - #[arg(short, long)] - rom: Option, - /// Path to the 64DD IPL file ddipl: PathBuf, /// Path to the 64DD disk file (.ndd format, can be specified multiple times) + #[arg(required = true)] disk: Vec, + /// Path to the ROM file + #[arg(short, long)] + rom: Option, + + /// Path to the save file + #[arg(short, long, requires = "rom")] + save: Option, + + /// Override autodetected save type + #[arg(short = 't', long, requires = "rom")] + save_type: Option, + /// Use direct boot mode (skip bootloader) #[arg(short, long)] direct: bool, + + /// Force TV type + #[arg(long, conflicts_with = "direct")] + tv: Option, } #[derive(Args)] @@ -308,10 +321,10 @@ fn handle_list_command() -> Result<(), sc64::Error> { fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<(), sc64::Error> { let mut sc64 = init_sc64(connection, true)?; - let (mut rom_file, rom_name, rom_length) = open_file(&args.rom)?; - sc64.reset_state()?; + let (mut rom_file, rom_name, rom_length) = open_file(&args.rom)?; + log_wait(format!("Uploading ROM [{rom_name}]"), || { sc64.upload_rom(&mut rom_file, rom_length, args.no_shadow) })?; @@ -325,7 +338,6 @@ fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<() }; save_type.into() }; - let save_type: sc64::SaveType = save.into(); println!("Save type set to [{save_type}]"); sc64.set_save_type(save_type)?; @@ -347,13 +359,9 @@ fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<() sc64.set_boot_mode(boot_mode)?; if let Some(tv) = args.tv.clone() { - if args.direct { - println!("TV type ignored due to direct boot mode being enabled"); - } else { - let tv_type: sc64::TvType = tv.into(); - println!("TV type set to [{tv_type}]"); - sc64.set_tv_type(tv_type)?; - } + let tv_type: sc64::TvType = tv.into(); + println!("TV type set to [{tv_type}]"); + sc64.set_tv_type(tv_type)?; } sc64.calculate_cic_parameters()?; @@ -381,26 +389,179 @@ fn handle_download_command( } fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), sc64::Error> { - let _ = (connection, args); + const MAX_ROM_LENGTH: usize = 32 * 1024 * 1024; - // TODO: handle 64DD stuff + let mut sc64 = init_sc64(connection, true)?; - // TODO: print BIG warning to not use this mode together with real 64DD + println!( + "{} {} {}", + "[WARNING]:".bold().bright_yellow(), + "Do not use this mode when real 64DD accessory is connected to the N64.".bright_yellow(), + "This might permanently damage either 64DD or SC64.".bright_yellow() + ); - println!("{}", "Sorry nothing".yellow()); - println!("{}", "64DD emulation not implemented yet".red()); + sc64.reset_state()?; + + if let Some(rom) = &args.rom { + let (mut rom_file, rom_name, rom_length) = open_file(rom)?; + if rom_length > MAX_ROM_LENGTH { + return Err(sc64::Error::new("ROM file size too big for 64DD mode")); + } + log_wait(format!("Uploading ROM [{rom_name}]"), || { + sc64.upload_rom(&mut rom_file, rom_length, false) + })?; + + let save: SaveType = if let Some(save_type) = args.save_type.clone() { + save_type + } else { + let (save_type, title) = n64::guess_save_type(&mut rom_file)?; + if let Some(title) = title { + println!("ROM title: {title}"); + }; + save_type.into() + }; + let save_type: sc64::SaveType = save.into(); + println!("Save type set to [{save_type}]"); + sc64.set_save_type(save_type)?; + + if args.save.is_some() { + let (mut save_file, save_name, save_length) = open_file(&args.save.as_ref().unwrap())?; + + log_wait(format!("Uploading save [{save_name}]"), || { + sc64.upload_save(&mut save_file, save_length) + })?; + } + } + + let (mut ddipl_file, ddipl_name, ddipl_length) = open_file(&args.ddipl)?; + + log_wait(format!("Uploading DDIPL [{ddipl_name}]"), || { + sc64.upload_ddipl(&mut ddipl_file, ddipl_length) + })?; + + let boot_mode = if args.rom.is_some() { + if args.direct { + sc64::BootMode::DirectRom + } else { + sc64::BootMode::Rom + } + } else { + if args.direct { + sc64::BootMode::DirectDdIpl + } else { + sc64::BootMode::DdIpl + } + }; + println!("Boot mode set to [{boot_mode}]"); + sc64.set_boot_mode(boot_mode)?; + + if let Some(tv) = args.tv.clone() { + let tv_type: sc64::TvType = tv.into(); + println!("TV type set to [{tv_type}]"); + sc64.set_tv_type(tv_type)?; + } + + sc64.calculate_cic_parameters()?; + + let disk_paths: Vec = args + .disk + .iter() + .map(|path| path.to_string_lossy().to_string()) + .collect(); + let disk_names: Vec = args + .disk + .iter() + .map(|path| path.file_name().unwrap().to_string_lossy().to_string()) + .collect(); + + let mut disks = disk::open_multiple(&disk_paths)?; + let mut selected_disk_index: usize = 0; + let mut selected_disk: Option<&mut disk::Disk> = None; + + let drive_type = match disks[0].get_format() { + disk::Format::Retail => sc64::DdDriveType::Retail, + disk::Format::Development => sc64::DdDriveType::Development, + }; + + sc64.configure_64dd(sc64::DdMode::Full, drive_type)?; + + println!( + "{}", + "Press button on the SC64 device to cycle through provided disks".bold() + ); + + let exit = setup_exit_flag(); + while !exit.load(Ordering::Relaxed) { + if let Some(data_packet) = sc64.receive_data_packet()? { + match data_packet { + sc64::DataPacket::Disk(mut packet) => { + let track = packet.info.track; + let head = packet.info.head; + let block = packet.info.block; + if let Some(ref mut disk) = selected_disk { + let reply_packet = match packet.kind { + sc64::DiskPacketKind::Read => { + print!("[R]"); + disk.read_block(track, head, block)?.map(|data| { + packet.info.set_data(&data); + packet + }) + } + sc64::DiskPacketKind::Write => { + print!("[W]"); + let data = &packet.info.data; + disk.write_block(track, head, block, data)?.map(|_| packet) + } + }; + let lba = if let Some(lba) = disk.get_lba(track, head, block) { + format!("{lba}") + } else { + "Invalid".to_string() + }; + let message = format!(" {track:4}:{head}:{block} / LBA: {lba}"); + if reply_packet.is_some() { + println!("{}", message.green()); + } else { + println!("{}", message.red()); + } + sc64.reply_disk_packet(reply_packet)?; + } else { + sc64.reply_disk_packet(None)?; + } + } + sc64::DataPacket::Button => { + if selected_disk.is_some() { + sc64.set_64dd_disk_state(sc64::DdDiskState::Ejected)?; + selected_disk = None; + println!("64DD disk ejected [{}]", disk_names[selected_disk_index]); + } else { + selected_disk_index += 1; + if selected_disk_index >= disks.len() { + selected_disk_index = 0; + } + selected_disk = Some(&mut disks[selected_disk_index]); + sc64.set_64dd_disk_state(sc64::DdDiskState::Inserted)?; + println!("64DD disk inserted [{}]", disk_names[selected_disk_index]); + } + } + _ => {} + } + } else { + thread::sleep(Duration::from_millis(1)); + } + } Ok(()) } fn handle_debug_command(connection: Connection, args: &DebugArgs) -> Result<(), sc64::Error> { + let mut sc64 = init_sc64(connection, true)?; + let mut debug_handler = debug::new(args.gdb)?; if let Some(port) = args.gdb { println!("GDB TCP socket listening at [0.0.0.0:{port}]"); } - let mut sc64 = init_sc64(connection, true)?; - if args.isv.is_some() { sc64.configure_is_viewer_64(args.isv)?; println!( @@ -600,16 +761,16 @@ fn handle_server_command(connection: Connection, args: &ServerArgs) -> Result<() }; sc64::run_server(port, args.address.clone(), |event| match event { - ServerEvent::Listening(address) => { + sc64::ServerEvent::Listening(address) => { println!("{}: Listening on address [{}]", "[Server]".bold(), address) } - ServerEvent::Connection(peer) => { + sc64::ServerEvent::Connection(peer) => { println!("{}: New connection from [{}]", "[Server]".bold(), peer); } - ServerEvent::Disconnected(peer) => { + sc64::ServerEvent::Disconnected(peer) => { println!("{}: Client disconnected [{}]", "[Server]".bold(), peer); } - ServerEvent::Err(error) => { + sc64::ServerEvent::Err(error) => { println!( "{}: Client disconnected with error: {}", "[Server]".bold(), diff --git a/sw/deployer/src/sc64/mod.rs b/sw/deployer/src/sc64/mod.rs index 4d0b405..39c6592 100644 --- a/sw/deployer/src/sc64/mod.rs +++ b/sw/deployer/src/sc64/mod.rs @@ -10,7 +10,8 @@ pub use self::{ link::{list_local_devices, ServerEvent}, types::{ BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState, DdDriveType, DdMode, - DebugPacket, DiskPacket, FpgaDebugData, McuStackUsage, SaveType, Switch, TvType, + DebugPacket, DiskPacket, DiskPacketKind, FpgaDebugData, McuStackUsage, SaveType, Switch, + TvType, }, }; @@ -550,6 +551,7 @@ impl SC64 { self.command_config_set(Config::DdSdEnable(Switch::Off))?; self.command_config_set(Config::DdDriveType(drive_type))?; self.command_config_set(Config::DdDiskState(DdDiskState::Ejected))?; + self.command_config_set(Config::ButtonMode(ButtonMode::UsbPacket))?; Ok(()) } @@ -587,11 +589,11 @@ impl SC64 { pub fn reply_disk_packet(&mut self, disk_packet: Option) -> Result<(), Error> { if let Some(packet) = disk_packet { - match packet { - DiskPacket::ReadBlock(disk_block) => { - self.command_memory_write(disk_block.address, &disk_block.data)?; + match packet.kind { + DiskPacketKind::Read => { + self.command_memory_write(packet.info.address, &packet.info.data)?; } - DiskPacket::WriteBlock(_) => {} + DiskPacketKind::Write => {} } self.command_dd_set_block_ready(false)?; } else { diff --git a/sw/deployer/src/sc64/types.rs b/sw/deployer/src/sc64/types.rs index 0877aa3..2b9e625 100644 --- a/sw/deployer/src/sc64/types.rs +++ b/sw/deployer/src/sc64/types.rs @@ -628,9 +628,14 @@ impl TryFrom> for DebugPacket { } } -pub enum DiskPacket { - ReadBlock(DiskBlock), - WriteBlock(DiskBlock), +pub enum DiskPacketKind { + Read, + Write, +} + +pub struct DiskPacket { + pub kind: DiskPacketKind, + pub info: DiskBlock, } impl TryFrom> for DiskPacket { @@ -650,8 +655,14 @@ impl TryFrom> for DiskPacket { data: value[12..].to_vec(), }; Ok(match command { - 1 => Self::ReadBlock(disk_block), - 2 => Self::WriteBlock(disk_block), + 1 => DiskPacket { + kind: DiskPacketKind::Read, + info: disk_block, + }, + 2 => DiskPacket { + kind: DiskPacketKind::Write, + info: disk_block, + }, _ => return Err(Error::new("Unknown disk packet command code")), }) } diff --git a/sw/tools/.gitignore b/sw/tools/.gitignore deleted file mode 100644 index a8a0dce..0000000 --- a/sw/tools/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.bin