mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2024-11-21 21:49:15 +01:00
sc64deployer done
This commit is contained in:
parent
70d287c855
commit
8479c80ebe
@ -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.
|
||||
|
322
sw/deployer/src/disk.rs
Normal file
322
sw/deployer/src/disk.rs
Normal file
@ -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<usize, Mapping>,
|
||||
}
|
||||
|
||||
impl Disk {
|
||||
pub fn get_format(&self) -> &Format {
|
||||
&self.format
|
||||
}
|
||||
|
||||
pub fn get_lba(&self, track: u32, head: u32, block: u32) -> Option<usize> {
|
||||
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<Option<Vec<u8>>, 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<Option<()>, 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<Disk, Error> {
|
||||
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<Vec<Disk>, Error> {
|
||||
let mut disks: Vec<Disk> = 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<usize, Mapping>), Error> {
|
||||
let mut disk_format: Option<Format> = None;
|
||||
let mut disk_type: u8 = 0;
|
||||
let mut sys_data = vec![0u8; SYSTEM_SECTOR_LENGTH];
|
||||
let mut bad_lba: Vec<usize> = 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<usize>> = Vec::new();
|
||||
|
||||
for (zone, info) in DISK_ZONES.iter().enumerate() {
|
||||
let mut bad_tracks: Vec<usize> = 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<Vec<u8>, 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
|
||||
}
|
@ -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<String>,
|
||||
|
||||
/// 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<TvType>,
|
||||
}
|
||||
|
||||
@ -117,19 +117,32 @@ struct DownloadArgs {
|
||||
|
||||
#[derive(Args)]
|
||||
struct _64DDArgs {
|
||||
/// Path to the ROM file
|
||||
#[arg(short, long)]
|
||||
rom: Option<PathBuf>,
|
||||
|
||||
/// 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<PathBuf>,
|
||||
|
||||
/// Path to the ROM file
|
||||
#[arg(short, long)]
|
||||
rom: Option<PathBuf>,
|
||||
|
||||
/// Path to the save file
|
||||
#[arg(short, long, requires = "rom")]
|
||||
save: Option<PathBuf>,
|
||||
|
||||
/// Override autodetected save type
|
||||
#[arg(short = 't', long, requires = "rom")]
|
||||
save_type: Option<SaveType>,
|
||||
|
||||
/// Use direct boot mode (skip bootloader)
|
||||
#[arg(short, long)]
|
||||
direct: bool,
|
||||
|
||||
/// Force TV type
|
||||
#[arg(long, conflicts_with = "direct")]
|
||||
tv: Option<TvType>,
|
||||
}
|
||||
|
||||
#[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<String> = args
|
||||
.disk
|
||||
.iter()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.collect();
|
||||
let disk_names: Vec<String> = 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(),
|
||||
|
@ -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<DiskPacket>) -> 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 {
|
||||
|
@ -628,9 +628,14 @@ impl TryFrom<Vec<u8>> 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<Vec<u8>> for DiskPacket {
|
||||
@ -650,8 +655,14 @@ impl TryFrom<Vec<u8>> 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")),
|
||||
})
|
||||
}
|
||||
|
1
sw/tools/.gitignore
vendored
1
sw/tools/.gitignore
vendored
@ -1 +0,0 @@
|
||||
*.bin
|
Loading…
Reference in New Issue
Block a user