From e83c6feeb00a6d2f1db647de70b377c8312bd2d4 Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Fri, 3 Mar 2023 00:26:33 +0100 Subject: [PATCH] progress --- sw/deployer/Cargo.lock | 402 ++++++++++++++++++++++++++++++- sw/deployer/Cargo.toml | 1 + sw/deployer/src/debug.rs | 213 ++++++++++++++-- sw/deployer/src/main.rs | 353 ++++++++++++++------------- sw/deployer/src/n64.rs | 70 ++++++ sw/deployer/src/sc64/firmware.rs | 0 sw/deployer/src/sc64/link.rs | 41 ++-- sw/deployer/src/sc64/mod.rs | 364 ++++++++++++++-------------- sw/deployer/src/sc64/types.rs | 357 +++++++++++++++++++++------ sw/deployer/src/sc64/utils.rs | 12 - 10 files changed, 1331 insertions(+), 482 deletions(-) delete mode 100644 sw/deployer/src/sc64/firmware.rs diff --git a/sw/deployer/Cargo.lock b/sw/deployer/Cargo.lock index 0cddc35..cb9adfa 100644 --- a/sw/deployer/Cargo.lock +++ b/sw/deployer/Cargo.lock @@ -23,6 +23,12 @@ dependencies = [ "mach", ] +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "0.7.20" @@ -58,6 +64,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -70,6 +82,18 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "cc" version = "1.0.79" @@ -153,6 +177,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colored" version = "2.0.0" @@ -179,6 +209,55 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "ctrlc" version = "3.2.5" @@ -233,6 +312,12 @@ dependencies = [ "syn", ] +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -263,6 +348,89 @@ dependencies = [ "libc", ] +[[package]] +name = "exr" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8af5ef47e2ed89d23d0ecbc1b681b30390069de70260937877514377fc24feb" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "smallvec", + "threadpool", + "zune-inflate", +] + +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin", +] + +[[package]] +name = "futures-core" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" + +[[package]] +name = "futures-sink" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + [[package]] name = "heck" version = "0.4.1" @@ -278,6 +446,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.1" @@ -308,6 +485,25 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "image" +version = "0.24.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "scoped_threadpool", + "tiff", +] + [[package]] name = "io-lifetimes" version = "1.0.5" @@ -330,6 +526,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.61" @@ -345,6 +550,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.139" @@ -386,6 +597,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.17" @@ -419,6 +640,33 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + [[package]] name = "nix" version = "0.26.2" @@ -441,6 +689,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -450,6 +709,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + [[package]] name = "once_cell" version = "1.17.1" @@ -468,12 +737,44 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384e52fd8fbd4cbe3c317e8216260c21a0f9134de108cea8a4dd4e7e152c472d" +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pkg-config" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "png" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +dependencies = [ + "bitflags", + "crc32fast", + "flate2", + "miniz_oxide", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -516,6 +817,28 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + [[package]] name = "regex" version = "1.7.1" @@ -548,7 +871,7 @@ dependencies = [ ] [[package]] -name = "sc64loader" +name = "sc64deployer" version = "2.12.2" dependencies = [ "chrono", @@ -558,10 +881,23 @@ dependencies = [ "crc32fast", "ctrlc", "encoding_rs", + "image", "panic-message", "serialport", ] +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "scratch" version = "1.0.3" @@ -584,6 +920,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "simd-adler32" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a5df39617d7c8558154693a1bb8157a4aab8179209540cc0b10e5dc24e0b18" + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "spin" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dccf47db1b41fa1573ed27ccf5e08e3ca771cb994f776668c5ebda893b248fc" +dependencies = [ + "lock_api", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -616,6 +973,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tiff" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.1.45" @@ -623,7 +1000,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] @@ -651,6 +1028,12 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.84" @@ -705,6 +1088,12 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "winapi" version = "0.3.9" @@ -801,3 +1190,12 @@ name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "zune-inflate" +version = "0.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589245df6230839c305984dcc0a8385cc72af1fd223f360ffd5d65efa4216d40" +dependencies = [ + "simd-adler32", +] diff --git a/sw/deployer/Cargo.toml b/sw/deployer/Cargo.toml index 01115be..dae9025 100644 --- a/sw/deployer/Cargo.toml +++ b/sw/deployer/Cargo.toml @@ -14,5 +14,6 @@ colored = "2.0.0" crc32fast = "1.3.2" ctrlc = "3.2.5" encoding_rs = "0.8.32" +image = "0.24.5" panic-message = "0.3.0" serialport = { git = "https://github.com/serialport/serialport-rs", branch = "main" } diff --git a/sw/deployer/src/debug.rs b/sw/deployer/src/debug.rs index 7caf292..60c57a7 100644 --- a/sw/deployer/src/debug.rs +++ b/sw/deployer/src/debug.rs @@ -1,27 +1,206 @@ -use crate::sc64::DebugPacket; +use crate::sc64; +use chrono::Local; +use std::{ + io::{ErrorKind, Read, Write}, + net::{TcpListener, TcpStream}, + sync::mpsc::{channel, Receiver, Sender}, + thread::{sleep, spawn}, + time::Duration, +}; -pub fn handle_debug_packet(debug_packet: DebugPacket) { - let DebugPacket { datatype, data } = debug_packet; - match datatype { - 0x01 => handle_datatype_text(&data), - // 0x02 => handle_datatype_raw_binary(&data), - // 0x03 => handle_datatype_header(&data), - // 0x04 => handle_datatype_screenshot(&data), - // 0xDB => handle_datatype_gdb(&data), - _ => {} +pub struct Handler { + header: Option>, + gdb_tx: Sender>, + gdb_rx: Receiver>, +} + +enum DataType { + Text, + RawBinary, + Header, + Screenshot, + GDB, + Unknown, +} + +impl From for DataType { + fn from(value: u8) -> Self { + match value { + 0x01 => Self::Text, + 0x02 => Self::RawBinary, + 0x03 => Self::Header, + 0x04 => Self::Screenshot, + 0xDB => Self::GDB, + _ => Self::Unknown, + } } } -fn handle_datatype_text(data: &[u8]) { - if let Ok(message) = std::str::from_utf8(data) { - print!("{message}"); +impl From for u8 { + fn from(value: DataType) -> Self { + match value { + DataType::Text => 0x01, + DataType::RawBinary => 0x02, + DataType::Header => 0x03, + DataType::Screenshot => 0x04, + DataType::GDB => 0xDB, + DataType::Unknown => 0xFF, + } } } -// fn handle_datatype_raw_binary(data: &[u8]) {} +impl Handler { + pub fn handle_debug_packet( + &mut self, + debug_packet: sc64::DebugPacket, + ) -> Result<(), sc64::Error> { + let sc64::DebugPacket { datatype, data } = debug_packet; + match datatype.into() { + DataType::Text => self.handle_datatype_text(&data), + DataType::RawBinary => self.handle_datatype_raw_binary(&data), + DataType::Header => self.handle_datatype_header(&data), + DataType::Screenshot => self.handle_datatype_screenshot(&data), + DataType::GDB => self.handle_datatype_gdb(&data), + _ => { + println!("Unknown debug packet datatype: 0x{datatype:02X}"); + Ok(()) + } + } + } -// fn handle_datatype_header(data: &[u8]) {} + fn handle_datatype_text(&self, data: &[u8]) -> Result<(), sc64::Error> { + print!("{}", String::from_utf8_lossy(data)); + Ok(()) + } -// fn handle_datatype_screenshot(data: &[u8]) {} + fn handle_datatype_raw_binary(&self, data: &[u8]) -> Result<(), sc64::Error> { + let filename = &self.generate_filename("binaryout", "bin"); + let mut file = std::fs::File::create(filename)?; + file.write_all(data)?; + println!("Wrote {} bytes to {}", data.len(), filename); + Ok(()) + } -// fn handle_datatype_gdb(data: &[u8]) {} + fn handle_datatype_header(&mut self, data: &[u8]) -> Result<(), sc64::Error> { + self.header = Some(data.to_vec()); + Ok(()) + } + + fn handle_datatype_screenshot(&mut self, _data: &[u8]) -> Result<(), sc64::Error> { + if let Some(header) = self.header.take() { + // TODO: support screenshot datatype + println!("Screenshot datatype not supported yet {:?}", header); + } else { + println!("Got screenshot packet without header data"); + } + Ok(()) + } + + fn handle_datatype_gdb(&self, data: &[u8]) -> Result<(), sc64::Error> { + self.gdb_tx.send(data.to_vec()).ok(); + Ok(()) + } + + fn generate_filename(&self, prefix: &str, extension: &str) -> String { + format!( + "{prefix}-{}.{extension}", + Local::now().format("%y%m%d%H%M%S.%f") + ) + } + + pub fn receive_gdb_packet(&self) -> Option { + if let Some(data) = self.gdb_rx.try_recv().ok() { + Some(sc64::DebugPacket { + datatype: DataType::GDB.into(), + data, + }) + } else { + None + } + } +} + +pub fn new(gdb_port: Option) -> Result { + let (gdb_tx, gdb_loop_rx) = channel::>(); + let (gdb_loop_tx, gdb_rx) = channel::>(); + + if let Some(port) = gdb_port { + let listener = TcpListener::bind(format!("0.0.0.0:{port}")) + .map_err(|_| sc64::Error::new("Couldn't open GDB TCP socket port"))?; + listener.set_nonblocking(true).map_err(|_| { + sc64::Error::new("Couldn't set GDB TCP socket listener as non-blocking") + })?; + spawn(move || gdb_loop(listener, gdb_loop_tx, gdb_loop_rx)); + } + + Ok(Handler { + header: None, + gdb_tx, + gdb_rx, + }) +} + +fn gdb_loop(listener: TcpListener, gdb_tx: Sender>, gdb_rx: Receiver>) { + for tcp_stream in listener.incoming() { + match tcp_stream { + Ok(mut stream) => { + handle_gdb_connection(&mut stream, &gdb_tx, &gdb_rx); + } + Err(error) => { + if error.kind() == ErrorKind::WouldBlock { + sleep(Duration::from_millis(1)); + } else { + panic!("{error}"); + } + } + } + } +} + +fn handle_gdb_connection( + stream: &mut TcpStream, + gdb_tx: &Sender>, + gdb_rx: &Receiver>, +) { + const GDB_DATA_BUFFER: usize = 8 * 1024; + + let mut buffer = vec![0u8; GDB_DATA_BUFFER]; + + let peer = stream.peer_addr().unwrap(); + + println!("[GDB]: New connection ({peer})"); + + loop { + match stream.read(&mut buffer) { + Ok(length) => { + if length > 0 { + gdb_tx.send(buffer[0..length].to_vec()).ok(); + continue; + } else { + println!("[GDB]: Connection closed ({peer})"); + break; + } + } + Err(e) => { + if e.kind() != ErrorKind::WouldBlock { + println!("[GDB]: Connection closed ({peer}), read IO error: {e}"); + break; + } + } + } + + if let Ok(data) = gdb_rx.try_recv() { + match stream.write_all(&data) { + Ok(()) => { + continue; + } + Err(e) => { + println!("[GDB]: Connection closed ({peer}), write IO error: {e}"); + break; + } + } + } + + sleep(Duration::from_millis(1)); + } +} diff --git a/sw/deployer/src/main.rs b/sw/deployer/src/main.rs index d9ca77d..3457640 100644 --- a/sw/deployer/src/main.rs +++ b/sw/deployer/src/main.rs @@ -1,19 +1,23 @@ mod debug; mod n64; -mod sc64; +pub mod sc64; // TODO: delete pub use chrono::Local; use clap::{Args, Parser, Subcommand, ValueEnum}; use clap_num::maybe_hex_range; use colored::Colorize; -use debug::handle_debug_packet; use panic_message::panic_message; -use std::io::{Read, Write}; -use std::path::PathBuf; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::time::Duration; -use std::{panic, process, thread}; +use std::{ + fs::File, + io::{self, Read, Write}, + path::PathBuf, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, + {panic, process, thread}, +}; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -167,6 +171,32 @@ enum SaveType { Flashram, } +impl From for SaveType { + fn from(value: n64::SaveType) -> Self { + match value { + n64::SaveType::None => Self::None, + n64::SaveType::Eeprom4k => Self::Eeprom4k, + n64::SaveType::Eeprom16k => Self::Eeprom16k, + n64::SaveType::Sram => Self::Sram, + n64::SaveType::SramBanked => Self::SramBanked, + n64::SaveType::Flashram => Self::Flashram, + } + } +} + +impl From for sc64::SaveType { + fn from(value: SaveType) -> Self { + match value { + SaveType::None => Self::None, + SaveType::Eeprom4k => Self::Eeprom4k, + SaveType::Eeprom16k => Self::Eeprom16k, + SaveType::Sram => Self::Sram, + SaveType::SramBanked => Self::SramBanked, + SaveType::Flashram => Self::Flashram, + } + } +} + #[derive(Clone, Debug, ValueEnum)] enum TvType { PAL, @@ -174,6 +204,16 @@ enum TvType { MPAL, } +impl From for sc64::TvType { + fn from(value: TvType) -> Self { + match value { + TvType::PAL => Self::PAL, + TvType::NTSC => Self::NTSC, + TvType::MPAL => Self::MPAL, + } + } +} + fn main() { let cli = Cli::parse(); @@ -188,28 +228,6 @@ fn main() { } } -fn init_sc64(sn: Option, check_firmware: bool) -> Result { - let mut sc64 = sc64::new(sn)?; - - if check_firmware { - sc64.check_firmware_version()?; - } - - Ok(sc64) -} - -fn setup_exit_flag() -> Arc { - let exit_flag = Arc::new(AtomicBool::new(false)); - let handler_exit_flag = exit_flag.clone(); - - ctrlc::set_handler(move || { - handler_exit_flag.store(true, Ordering::Relaxed); - }) - .unwrap(); - - exit_flag -} - fn handle_command(command: &Commands, sn: Option) { let result = match command { Commands::Upload(args) => handle_upload_command(sn, args), @@ -232,62 +250,45 @@ fn handle_upload_command(sn: Option, args: &UploadArgs) -> Result<(), sc sc64.reset_state()?; - let rom_path = args.rom.to_str().unwrap(); + let (mut rom_file, rom_name, rom_length) = open_file(&args.rom)?; - print!( - "Uploading ROM [{}]... ", - args.rom.file_name().unwrap().to_str().unwrap() - ); - std::io::stdout().flush().unwrap(); - sc64.upload_rom(rom_path, args.no_shadow)?; - println!("done"); + log_wait(format!("Uploading ROM [{rom_name}]"), || { + sc64.upload_rom(&mut rom_file, rom_length, args.no_shadow) + })?; - // TODO: autodetect save - - let args_save_type = args.save_type.as_ref().unwrap_or(&SaveType::None); - let save_type = match args_save_type { - SaveType::None => sc64::SaveType::None, - SaveType::Eeprom4k => sc64::SaveType::Eeprom4k, - SaveType::Eeprom16k => sc64::SaveType::Eeprom16k, - SaveType::Sram => sc64::SaveType::Sram, - SaveType::SramBanked => sc64::SaveType::SramBanked, - SaveType::Flashram => sc64::SaveType::Flashram, + let save: SaveType = if let Some(save_type) = args.save_type.clone() { + save_type + } else { + n64::guess_save_type(&mut rom_file)?.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 save = args.save.as_ref().unwrap(); + let (mut save_file, save_name, save_length) = open_file(&args.save.as_ref().unwrap())?; - print!( - "Uploading save [{}]... ", - save.file_name().unwrap().to_str().unwrap() - ); - std::io::stdout().flush().unwrap(); - sc64.upload_save(save.to_str().unwrap())?; - println!("done"); + log_wait(format!("Uploading save [{save_name}]"), || { + sc64.upload_save(&mut save_file, save_length) + })?; } - println!("Save type set to [{args_save_type:?}]"); - let boot_mode = if args.direct { sc64::BootMode::DirectRom } else { sc64::BootMode::Rom }; - + println!("Boot mode set to [{boot_mode}]"); sc64.set_boot_mode(boot_mode)?; - println!("Boot mode set to [{:?}]", boot_mode); - if let Some(tv) = args.tv.as_ref() { + if let Some(tv) = args.tv.clone() { if args.direct { - println!("TV type ignored because direct boot mode is enabled"); + println!("TV type ignored due to direct boot mode being enabled"); } else { - sc64.set_tv_type(match tv { - TvType::PAL => sc64::TvType::PAL, - TvType::NTSC => sc64::TvType::NTSC, - TvType::MPAL => sc64::TvType::MPAL, - })?; - println!("TV type set to [{tv:?}]"); + let tv_type: sc64::TvType = tv.into(); + println!("TV type set to [{tv_type}]"); + sc64.set_tv_type(tv_type)?; } } @@ -297,73 +298,47 @@ fn handle_upload_command(sn: Option, args: &UploadArgs) -> Result<(), sc } fn handle_64dd_command(sn: Option, args: &_64DDArgs) -> Result<(), sc64::Error> { - let mut sc64 = init_sc64(sn, true)?; + let _ = (sn, args); - // TODO: parse 64DD disk files + // TODO: handle 64DD stuff - sc64.reset_state()?; - - let ddipl_path = args.ddipl.to_str().unwrap(); - - print!( - "Uploading DDIPL [{}]... ", - args.ddipl.file_name().unwrap().to_str().unwrap() - ); - std::io::stdout().flush().unwrap(); - sc64.upload_ddipl(ddipl_path)?; - println!("done"); - - // TODO: upload other stuff - - // TODO: set boot mode - - sc64.calculate_cic_parameters()?; - - 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(_disk_packet) => { - // TODO: handle 64DD packet - } - _ => {} - } - } else { - thread::sleep(Duration::from_millis(1)); - } - } + println!("{}", "Sorry nothing".yellow()); Ok(()) } fn handle_dump_command(sn: Option, args: &DumpArgs) -> Result<(), sc64::Error> { - let dump_path = &args.path; - - let mut file = std::fs::File::create(dump_path)?; - let mut sc64 = init_sc64(sn, true)?; - print!( - "Dumping from [0x{:08X}] length [0x{:X}] to [{}]... ", - args.address, - args.length, - dump_path.file_name().unwrap().to_str().unwrap() - ); - std::io::stdout().flush().unwrap(); - let data = sc64.dump_memory(args.address, args.length)?; + let (mut dump_file, dump_name) = create_file(&args.path)?; - file.write(&data)?; + let data = log_wait( + format!( + "Dumping from [0x{:08X}] length [0x{:X}] to [{dump_name}]", + args.address, args.length + ), + || sc64.dump_memory(args.address, args.length), + )?; - println!("done"); + dump_file.write(&data)?; Ok(()) } fn handle_debug_command(sn: Option, args: &DebugArgs) -> Result<(), sc64::Error> { + 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(sn, true)?; if args.isv.is_some() { - sc64.configure_isviewer64(args.isv)?; + sc64.configure_is_viewer_64(args.isv)?; + println!( + "IS-Viewer configured and listening at offset [0x{:08X}]", + args.isv.unwrap() + ); } println!("{}", "Debug mode started".bold()); @@ -372,10 +347,16 @@ fn handle_debug_command(sn: Option, args: &DebugArgs) -> Result<(), sc64 while !exit.load(Ordering::Relaxed) { if let Some(data_packet) = sc64.receive_data_packet()? { match data_packet { - sc64::DataPacket::IsViewer(message) => print!("{}", message), - sc64::DataPacket::Debug(debug_packet) => handle_debug_packet(debug_packet), + sc64::DataPacket::IsViewer(message) => { + print!("{message}") + } + sc64::DataPacket::Debug(debug_packet) => { + debug_handler.handle_debug_packet(debug_packet)? + } _ => {} } + } else if let Some(gdb_packet) = debug_handler.receive_gdb_packet() { + sc64.send_debug_packet(gdb_packet)?; } else { thread::sleep(Duration::from_millis(1)); } @@ -384,7 +365,7 @@ fn handle_debug_command(sn: Option, args: &DebugArgs) -> Result<(), sc64 println!("{}", "Debug mode ended".bold()); if args.isv.is_some() { - sc64.configure_isviewer64(None)?; + sc64.configure_is_viewer_64(None)?; } Ok(()) @@ -398,24 +379,24 @@ fn handle_info_command(sn: Option) -> Result<(), sc64::Error> { let datetime = state.datetime.format("%Y-%m-%d %H:%M:%S %Z"); println!("{}", "SC64 information and current state:".bold()); - println!(" Firmware version: {major}.{minor}"); - println!(" RTC datetime: {}", datetime); - println!(" LED blink enabled: {}", state.led_enable); - println!(" Bootloader switch: {}", state.bootloader_switch); - println!(" ROM write enabled: {}", state.rom_write_enable); - println!(" ROM shadow enabled: {}", state.rom_shadow_enable); - println!(" ROM extended enabled: {}", state.rom_extended_enable); - println!(" Boot mode: {:?}", state.boot_mode); - println!(" Save type: {:?}", state.save_type); - println!(" CIC seed: {:?}", state.cic_seed); - println!(" TV type: {:?}", state.tv_type); - println!(" 64DD mode: {:?}", state.dd_mode); - println!(" 64DD SD card mode: {}", state.dd_sd_enable); - println!(" 64DD drive type: {:?}", state.dd_drive_type); - println!(" 64DD disk state: {:?}", state.dd_disk_state); - println!(" Button mode: {:?}", state.button_mode); - println!(" Button state: {}", state.button_state); - println!(" IS-Viewer 64 offset: 0x{:08X}", state.isv_address); + println!(" Firmware version: {}.{}", major, minor); + println!(" RTC datetime: {}", datetime); + println!(" Boot mode: {}", state.boot_mode); + println!(" Save type: {}", state.save_type); + println!(" CIC seed: {}", state.cic_seed); + println!(" TV type: {}", state.tv_type); + println!(" Bootloader switch: {}", state.bootloader_switch); + println!(" ROM write: {}", state.rom_write_enable); + println!(" ROM shadow: {}", state.rom_shadow_enable); + println!(" ROM extended: {}", state.rom_extended_enable); + println!(" IS-Viewer 64 offset: 0x{:08X}", state.isv_address); + println!(" 64DD mode: {}", state.dd_mode); + println!(" 64DD SD card mode: {}", state.dd_sd_enable); + println!(" 64DD drive type: {}", state.dd_drive_type); + println!(" 64DD disk state: {}", state.dd_disk_state); + println!(" Button mode: {}", state.button_mode); + println!(" Button state: {}", state.button_state); + println!(" LED blink: {}", state.led_enable); Ok(()) } @@ -456,56 +437,49 @@ fn handle_firmware_command( ) -> Result<(), sc64::Error> { match command { FirmwareCommands::Info(args) => { - let firmware_path = &args.firmware; + let (mut firmware_file, _, firmware_length) = open_file(&args.firmware)?; - let mut file = std::fs::File::open(firmware_path)?; - let length = file.metadata()?.len(); - let mut buffer = vec![0u8; length as usize]; - file.read_exact(&mut buffer)?; + let mut firmware = vec![0u8; firmware_length as usize]; + firmware_file.read_exact(&mut firmware)?; // TODO: print firmware metadata + println!("{}", "Sorry nothing".yellow()); + Ok(()) } FirmwareCommands::Backup(args) => { - let backup_path = &args.firmware; - - let mut file = std::fs::File::create(backup_path)?; - let mut sc64 = init_sc64(sn, false)?; - print!( - "Generating firmware backup, this might take a while [{}]... ", - backup_path.file_name().unwrap().to_str().unwrap() - ); - std::io::stdout().flush().unwrap(); - let data = sc64.backup_firmware()?; - file.write(&data)?; - println!("done"); + let (mut backup_file, backup_name) = create_file(&args.firmware)?; + + let firmware = log_wait( + format!("Generating firmware backup, this might take a while [{backup_name}]"), + || sc64.backup_firmware(), + )?; + + // TODO: print firmware metadata + + backup_file.write(&firmware)?; Ok(()) } FirmwareCommands::Update(args) => { - let update_path = &args.firmware; + let mut sc64 = init_sc64(sn, false)?; - let mut file = std::fs::File::open(update_path)?; - let length = file.metadata()?.len(); - let mut buffer = vec![0u8; length as usize]; - file.read_exact(&mut buffer)?; + let (mut update_file, update_name, update_length) = open_file(&args.firmware)?; + + let mut fimware = vec![0u8; update_length as usize]; + update_file.read_exact(&mut fimware)?; // TODO: print firmware metadata - let mut sc64 = init_sc64(sn, false)?; - - print!( - "Updating firmware, this might take a while [{}]... ", - update_path.file_name().unwrap().to_str().unwrap() - ); - std::io::stdout().flush().unwrap(); - sc64.update_firmware(&buffer)?; - println!("done"); + log_wait( + format!("Updating firmware, this might take a while [{update_name}]"), + || sc64.update_firmware(&fimware), + )?; Ok(()) } @@ -522,3 +496,46 @@ fn handle_list_command() -> Result<(), sc64::Error> { Ok(()) } + +fn init_sc64(sn: Option, check_firmware: bool) -> Result { + let mut sc64 = sc64::new(sn)?; + + if check_firmware { + sc64.check_firmware_version()?; + } + + Ok(sc64) +} + +fn log_wait Result, T, E>(message: String, operation: F) -> Result { + print!("{}... ", message); + io::stdout().flush().unwrap(); + let result = operation(); + println!("done"); + result +} + +fn open_file(path: &PathBuf) -> Result<(File, String, usize), sc64::Error> { + let name: String = path.file_name().unwrap().to_string_lossy().to_string(); + let file = File::open(path)?; + let length = file.metadata()?.len() as usize; + Ok((file, name, length)) +} + +fn create_file(path: &PathBuf) -> Result<(File, String), sc64::Error> { + let name: String = path.file_name().unwrap().to_string_lossy().to_string(); + let file = File::create(path)?; + Ok((file, name)) +} + +fn setup_exit_flag() -> Arc { + let exit_flag = Arc::new(AtomicBool::new(false)); + let handler_exit_flag = exit_flag.clone(); + + ctrlc::set_handler(move || { + handler_exit_flag.store(true, Ordering::Relaxed); + }) + .unwrap(); + + exit_flag +} diff --git a/sw/deployer/src/n64.rs b/sw/deployer/src/n64.rs index e69de29..6d02352 100644 --- a/sw/deployer/src/n64.rs +++ b/sw/deployer/src/n64.rs @@ -0,0 +1,70 @@ +use std::io::{Error, Read, Seek}; + +pub enum SaveType { + None, + Eeprom4k, + Eeprom16k, + Sram, + SramBanked, + Flashram, +} + +pub fn guess_save_type(reader: &mut T) -> Result { + let mut header = vec![0u8; 0x40]; + + reader.rewind()?; + reader.read_exact(&mut header)?; + + let pi_config = &header[0..4]; + match pi_config { + [0x37, 0x80, 0x40, 0x12] => { + header.chunks_exact_mut(2).for_each(|c| c.swap(0, 1)); + } + [0x40, 0x12, 0x37, 0x80] => { + header.chunks_exact_mut(4).for_each(|c| { + c.swap(0, 3); + c.swap(1, 2); + }); + } + _ => {} + } + + let rom_id = &header[0x3B..0x3E]; + // let region = header[0x3E]; + // let revision = header[0x3F]; + + // TODO: fix this mess + + if let Some(save_type) = match rom_id { + b"NTW" | b"NHF" | b"NOS" | b"NTC" | b"NER" | b"NAG" | b"NAB" | b"NS3" | b"NTN" | b"NBN" + | b"NBK" | b"NFH" | b"NMU" | b"NBC" | b"NBH" | b"NHA" | b"NBM" | b"NBV" | b"NBD" + | b"NCT" | b"NCH" | b"NCG" | b"NP2" | b"NXO" | b"NCU" | b"NCX" | b"NDY" | b"NDQ" + | b"NDR" | b"NN6" | b"NDU" | b"NJM" | b"NFW" | b"NF2" | b"NKA" | b"NFG" | b"NGL" + | b"NGV" | b"NGE" | b"NHP" | b"NPG" | b"NIJ" | b"NIC" | b"NFY" | b"NKI" | b"NLL" + | b"NLR" | b"NKT" | b"CLB" | b"NLB" | b"NMW" | b"NML" | b"NTM" | b"NMI" | b"NMG" + | b"NMO" | b"NMS" | b"NMR" | b"NCR" | b"NEA" | b"NPW" | b"NPY" | b"NPT" | b"NRA" + | b"NWQ" | b"NSU" | b"NSN" | b"NK2" | b"NSV" | b"NFX" | b"NFP" | b"NS6" | b"NNA" + | b"NRS" | b"NSW" | b"NSC" | b"NSA" | b"NB6" | b"NSS" | b"NTX" | b"NT6" | b"NTP" + | b"NTJ" | b"NRC" | b"NTR" | b"NTB" | b"NGU" | b"NIR" | b"NVL" | b"NVY" | b"NWC" + | b"NAD" | b"NWU" | b"NYK" | b"NMZ" | b"NSM" | b"NWR" => Some(SaveType::Eeprom4k), + b"NB7" | b"NGT" | b"NFU" | b"NCW" | b"NCZ" | b"ND6" | b"NDO" | b"ND2" | b"N3D" | b"NMX" + | b"NGC" | b"NIM" | b"NNB" | b"NMV" | b"NM8" | b"NEV" | b"NPP" | b"NUB" | b"NPD" + | b"NRZ" | b"NR7" | b"NEP" | b"NYS" => Some(SaveType::Eeprom16k), + b"NTE" | b"NVB" | b"NB5" | b"CFZ" | b"NFZ" | b"NSI" | b"NG6" | b"NGP" | b"NYW" | b"NHY" + | b"NIB" | b"NPS" | b"NPA" | b"NP4" | b"NJ5" | b"NP6" | b"NPE" | b"NJG" | b"CZL" + | b"NZL" | b"NKG" | b"NMF" | b"NRI" | b"NUT" | b"NUM" | b"NOB" | b"CPS" | b"NPM" + | b"NRE" | b"NAL" | b"NT3" | b"NS4" | b"NA2" | b"NVP" | b"NWL" | b"NW2" | b"NWX" => { + Some(SaveType::Sram) + } + b"CDZ" => Some(SaveType::SramBanked), + b"NCC" | b"NDA" | b"NAF" | b"NJF" | b"NKJ" | b"NZS" | b"NM6" | b"NCK" | b"NMQ" | b"NPN" + | b"NPF" | b"NPO" | b"CP2" | b"NP3" | b"NRH" | b"NSQ" | b"NT9" | b"NW4" | b"NDP" => { + Some(SaveType::Flashram) + } + _ => None, + } { + return Ok(save_type); + } + + Ok(SaveType::None) +} diff --git a/sw/deployer/src/sc64/firmware.rs b/sw/deployer/src/sc64/firmware.rs deleted file mode 100644 index e69de29..0000000 diff --git a/sw/deployer/src/sc64/link.rs b/sw/deployer/src/sc64/link.rs index 9c54013..d7e0a8f 100644 --- a/sw/deployer/src/sc64/link.rs +++ b/sw/deployer/src/sc64/link.rs @@ -33,10 +33,10 @@ enum DataType { } pub trait Link { - fn execute_command(&mut self, command: &Command) -> Result, Error>; + fn execute_command(&mut self, command: &mut Command) -> Result, Error>; fn execute_command_raw( &mut self, - command: &Command, + command: &mut Command, timeout: Duration, no_response: bool, ignore_error: bool, @@ -83,15 +83,17 @@ impl SerialLink { Ok(()) } - fn serial_send_command(&mut self, command: &Command, timeout: Duration) -> Result<(), Error> { - let Command { id, args, mut data } = command.clone(); - + fn serial_send_command( + &mut self, + command: &mut Command, + timeout: Duration, + ) -> Result<(), Error> { let mut packet: Vec = Vec::new(); packet.append(&mut b"CMD".to_vec()); - packet.append(&mut [id].to_vec()); - packet.append(&mut args[0].to_be_bytes().to_vec()); - packet.append(&mut args[1].to_be_bytes().to_vec()); - packet.append(&mut data); + packet.append(&mut [command.id].to_vec()); + packet.append(&mut command.args[0].to_be_bytes().to_vec()); + packet.append(&mut command.args[1].to_be_bytes().to_vec()); + packet.append(&mut command.data); self.serial.set_timeout(timeout)?; self.serial.write_all(&packet)?; @@ -140,7 +142,7 @@ impl SerialLink { Ok(None) } - fn send_command(&mut self, command: &Command) -> Result<(), Error> { + fn send_command(&mut self, command: &mut Command) -> Result<(), Error> { self.serial_send_command(command, COMMAND_TIMEOUT) } @@ -153,13 +155,13 @@ impl SerialLink { } impl Link for SerialLink { - fn execute_command(&mut self, command: &Command) -> Result, Error> { + fn execute_command(&mut self, command: &mut Command) -> Result, Error> { self.execute_command_raw(command, COMMAND_TIMEOUT, false, false) } fn execute_command_raw( &mut self, - command: &Command, + command: &mut Command, timeout: Duration, no_response: bool, ignore_error: bool, @@ -169,7 +171,9 @@ impl Link for SerialLink { return Ok(vec![]); } let response = self.receive_response(timeout)?; - compare_id(&command, &response)?; + if command.id != response.id { + return Err(Error::new("Command response ID didn't match")); + } if !ignore_error && response.error { return Err(Error::new("Command response error")); } @@ -184,13 +188,6 @@ impl Link for SerialLink { } } -fn compare_id(command: &Command, response: &Response) -> Result<(), Error> { - if command.id != response.id { - return Err(Error::new("Command response ID didn't match")); - } - Ok(()) -} - pub fn list_serial_devices() -> Result, Error> { const SC64_VID: u16 = 0x0403; const SC64_PID: u16 = 0x6014; @@ -219,9 +216,7 @@ pub fn list_serial_devices() -> Result, Error> { pub fn new_serial(port: &str) -> Result, Error> { let mut link = SerialLink { - serial: serialport::new(port, 115_200) - .timeout(COMMAND_TIMEOUT) - .open()?, + serial: serialport::new(port, 115_200).open()?, packets: VecDeque::new(), }; diff --git a/sw/deployer/src/sc64/mod.rs b/sw/deployer/src/sc64/mod.rs index c7d4e1b..35e89fb 100644 --- a/sw/deployer/src/sc64/mod.rs +++ b/sw/deployer/src/sc64/mod.rs @@ -4,54 +4,57 @@ mod link; mod types; mod utils; -use crate::sc64::cic::IPL3_OFFSET; - -use self::cic::{calculate_ipl3_checksum, guess_ipl3_seed, IPL3_LENGTH}; -pub use self::link::list_serial_devices; -pub use self::types::{ - BootMode, DataPacket, DdDiskState, DdDriveType, DdMode, DebugPacket, DiskPacket, - FirmwareStatus, SaveType, TvType, -}; -use self::types::{ButtonMode, CicSeed, UpdateStatus}; -use self::{ - link::{Command, Link}, - types::{get_config, get_setting, Config, ConfigId, Setting, SettingId}, - utils::{ - args_from_vec, datetime_from_vec, file_open_and_check_length, u32_from_vec, - vec_from_datetime, +pub use self::{ + error::Error, + link::list_serial_devices, + types::{ + BootMode, DataPacket, DdDiskState, DdDriveType, DdMode, DebugPacket, DiskPacket, SaveType, + TvType, }, }; + +use self::{ + cic::{calculate_ipl3_checksum, guess_ipl3_seed, IPL3_LENGTH, IPL3_OFFSET}, + link::{Command, Link}, + types::{ + get_config, get_setting, ButtonMode, CicSeed, Config, ConfigId, FirmwareStatus, Setting, + SettingId, UpdateStatus, Switch, ButtonState, + }, + utils::{args_from_vec, datetime_from_vec, u32_from_vec, vec_from_datetime}, +}; use chrono::{DateTime, Local}; -pub use error::Error; -use std::io::{Read, Seek}; -use std::time::Instant; -use std::{cmp::min, time::Duration}; +use std::{ + io::{Read, Seek}, + time::Instant, + {cmp::min, time::Duration}, +}; pub struct SC64 { link: Box, } -#[derive(Debug)] pub struct DeviceState { - pub bootloader_switch: bool, - pub rom_write_enable: bool, - pub rom_shadow_enable: bool, + pub bootloader_switch: Switch, + pub rom_write_enable: Switch, + pub rom_shadow_enable: Switch, pub dd_mode: DdMode, pub isv_address: u32, pub boot_mode: BootMode, pub save_type: SaveType, pub cic_seed: CicSeed, pub tv_type: TvType, - pub dd_sd_enable: bool, + pub dd_sd_enable: Switch, pub dd_drive_type: DdDriveType, pub dd_disk_state: DdDiskState, - pub button_state: bool, + pub button_state: ButtonState, pub button_mode: ButtonMode, - pub rom_extended_enable: bool, - pub led_enable: bool, + pub rom_extended_enable: Switch, + pub led_enable: Switch, pub datetime: DateTime, } +const SC64_V2_IDENTIFIER: &[u8; 4] = b"SCv2"; + const SUPPORTED_MAJOR_VERSION: u16 = 2; const SUPPORTED_MINOR_VERSION: u16 = 12; @@ -91,9 +94,11 @@ const ISV_BUFFER_LENGTH: usize = 64 * 1024; pub const MEMORY_LENGTH: usize = 0x0500_2980; +const MEMORY_WRITE_CHUNK_SIZE: usize = 1 * 1024 * 1024; + impl SC64 { fn command_identifier_get(&mut self) -> Result, Error> { - let identifier = self.link.execute_command(&Command { + let identifier = self.link.execute_command(&mut Command { id: b'v', args: [0, 0], data: vec![], @@ -102,7 +107,7 @@ impl SC64 { } fn command_version_get(&mut self) -> Result<(u16, u16), Error> { - let version = self.link.execute_command(&Command { + let version = self.link.execute_command(&mut Command { id: b'V', args: [0, 0], data: vec![], @@ -113,7 +118,7 @@ impl SC64 { } fn command_state_reset(&mut self) -> Result<(), Error> { - self.link.execute_command(&Command { + self.link.execute_command(&mut Command { id: b'R', args: [0, 0], data: vec![], @@ -130,7 +135,7 @@ impl SC64 { let mut params: Vec = vec![]; params.append(&mut [(disable as u8) << 0, seed].to_vec()); params.append(&mut checksum.to_vec()); - self.link.execute_command(&Command { + self.link.execute_command(&mut Command { id: b'B', args: args_from_vec(¶ms[0..8])?, data: vec![], @@ -139,7 +144,7 @@ impl SC64 { } fn command_config_get(&mut self, config_id: ConfigId) -> Result { - let data = self.link.execute_command(&Command { + let data = self.link.execute_command(&mut Command { id: b'c', args: [config_id.into(), 0], data: vec![], @@ -149,7 +154,7 @@ impl SC64 { } fn command_config_set(&mut self, config: Config) -> Result<(), Error> { - self.link.execute_command(&Command { + self.link.execute_command(&mut Command { id: b'C', args: config.into(), data: vec![], @@ -158,7 +163,7 @@ impl SC64 { } fn command_setting_get(&mut self, setting_id: SettingId) -> Result { - let data = self.link.execute_command(&Command { + let data = self.link.execute_command(&mut Command { id: b'a', args: [setting_id.into(), 0], data: vec![], @@ -168,7 +173,7 @@ impl SC64 { } fn command_setting_set(&mut self, setting: Setting) -> Result<(), Error> { - self.link.execute_command(&Command { + self.link.execute_command(&mut Command { id: b'A', args: setting.into(), data: vec![], @@ -177,7 +182,7 @@ impl SC64 { } fn command_time_get(&mut self) -> Result, Error> { - let data = self.link.execute_command(&Command { + let data = self.link.execute_command(&mut Command { id: b't', args: [0, 0], data: vec![], @@ -186,7 +191,7 @@ impl SC64 { } fn command_time_set(&mut self, datetime: DateTime) -> Result<(), Error> { - self.link.execute_command(&Command { + self.link.execute_command(&mut Command { id: b'T', args: args_from_vec(&vec_from_datetime(datetime)?[0..8])?, data: vec![], @@ -195,7 +200,7 @@ impl SC64 { } fn command_memory_read(&mut self, address: u32, length: usize) -> Result, Error> { - let data = self.link.execute_command(&Command { + let data = self.link.execute_command(&mut Command { id: b'm', args: [address, length as u32], data: vec![], @@ -204,7 +209,7 @@ impl SC64 { } fn command_memory_write(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { - self.link.execute_command(&Command { + self.link.execute_command(&mut Command { id: b'M', args: [address, data.len() as u32], data: data.to_vec(), @@ -214,7 +219,7 @@ impl SC64 { fn command_usb_write(&mut self, datatype: u8, data: &[u8]) -> Result<(), Error> { self.link.execute_command_raw( - &Command { + &mut Command { id: b'U', args: [datatype as u32, data.len() as u32], data: data.to_vec(), @@ -227,7 +232,7 @@ impl SC64 { } fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> { - self.link.execute_command(&Command { + self.link.execute_command(&mut Command { id: b'D', args: [error as u32, 0], data: vec![], @@ -236,7 +241,7 @@ impl SC64 { } fn command_flash_wait_busy(&mut self, wait: bool) -> Result { - let erase_block_size = self.link.execute_command(&Command { + let erase_block_size = self.link.execute_command(&mut Command { id: b'p', args: [wait as u32, 0], data: vec![], @@ -245,7 +250,7 @@ impl SC64 { } fn command_flash_erase_block(&mut self, address: u32) -> Result<(), Error> { - self.link.execute_command(&Command { + self.link.execute_command(&mut Command { id: b'P', args: [address, 0], data: vec![], @@ -255,7 +260,7 @@ impl SC64 { fn command_firmware_backup(&mut self, address: u32) -> Result<(FirmwareStatus, u32), Error> { let data = self.link.execute_command_raw( - &Command { + &mut Command { id: b'f', args: [address, 0], data: vec![], @@ -275,7 +280,7 @@ impl SC64 { length: usize, ) -> Result { let data = self.link.execute_command_raw( - &Command { + &mut Command { id: b'F', args: [address, length as u32], data: vec![], @@ -286,91 +291,34 @@ impl SC64 { )?; Ok(FirmwareStatus::try_from(utils::u32_from_vec(&data[0..4])?)?) } - - fn flash_erase(&mut self, address: u32, length: usize) -> Result<(), Error> { - let erase_block_size = self.command_flash_wait_busy(false)?; - for offset in (0..length as u32).step_by(erase_block_size as usize) { - self.command_flash_erase_block(address + offset)?; - } - Ok(()) - } - - fn flash_program(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { - let current_data = self.command_memory_read(address, data.len())?; - if data == current_data { - return Ok(()); - } - self.flash_erase(address, data.len())?; - self.command_memory_write(address, data)?; - self.command_flash_wait_busy(true)?; - Ok(()) - } - - fn flash_program_shadow(&mut self, data: &[u8]) -> Result<(), Error> { - if data.len() > ROM_SHADOW_LENGTH { - return Err(Error::new( - "Invalid data length for program ROM shadow operation", - )); - } - self.flash_program(ROM_SHADOW_ADDRESS, data) - } - - fn flash_program_extended(&mut self, data: &[u8]) -> Result<(), Error> { - if data.len() > ROM_EXTENDED_LENGTH { - return Err(Error::new( - "Invalid data length for program ROM extended operation", - )); - } - self.flash_program(ROM_EXTENDED_ADDRESS, data) - } - - #[allow(dead_code)] - fn flash_program_bootloader(&mut self, data: &[u8]) -> Result<(), Error> { - if data.len() > BOOTLOADER_LENGTH { - return Err(Error::new( - "Invalid data length for program bootloader operation", - )); - } - self.flash_program(BOOTLOADER_ADDRESS, data) - } } impl SC64 { - pub fn check_firmware_version(&mut self) -> Result<(u16, u16), Error> { - let (major, minor) = self - .command_version_get() - .map_err(|_| Error::new("Outdated SC64 firmware version, please update firmware"))?; - if major != SUPPORTED_MAJOR_VERSION || minor < SUPPORTED_MINOR_VERSION { - return Err(Error::new( - "Unsupported SC64 firmware version, please update firmware", - )); + pub fn upload_rom( + &mut self, + reader: &mut T, + length: usize, + no_shadow: bool, + ) -> Result<(), Error> { + if length > MAX_ROM_LENGTH { + return Err(Error::new("ROM length too big")); } - Ok((major, minor)) - } - pub fn reset_state(&mut self) -> Result<(), Error> { - self.command_state_reset()?; - Ok(()) - } + let mut pi_config = vec![0u8; 4]; + reader.read(&mut pi_config)?; + reader.rewind()?; - pub fn upload_rom(&mut self, path: &str, no_shadow: bool) -> Result<(), Error> { - const BUFFER_SIZE: usize = 1 * 1024 * 1024; - - let (mut file, length) = file_open_and_check_length(path, MAX_ROM_LENGTH)?; - - let mut endian_check = vec![0u8; 4]; - file.read(&mut endian_check)?; - file.rewind()?; - - let endian_swapper = match u32_from_vec(&endian_check[0..4])? { - 0x37804012 => |b: &mut [u8]| b.chunks_exact_mut(2).for_each(|c| c.swap(0, 1)), - 0x40123780 => |b: &mut [u8]| { + let endian_swapper = match &pi_config[0..4] { + [0x37, 0x80, 0x40, 0x12] => { + |b: &mut [u8]| b.chunks_exact_mut(2).for_each(|c| c.swap(0, 1)) + } + [0x40, 0x12, 0x37, 0x80] => |b: &mut [u8]| { b.chunks_exact_mut(4).for_each(|c| { c.swap(0, 3); c.swap(1, 2) }) }, - _ => |_b: &mut [u8]| {}, + _ => |_: &mut [u8]| {}, }; let rom_shadow_enabled = !no_shadow && length > (SDRAM_LENGTH - ROM_SHADOW_LENGTH); @@ -382,68 +330,54 @@ impl SC64 { min(length, SDRAM_LENGTH) }; - let mut buffer = vec![0u8; BUFFER_SIZE]; + self.memory_write_chunked(reader, SDRAM_ADDRESS, sdram_length, Some(endian_swapper))?; - for offset in (0..sdram_length as u32).step_by(buffer.len()) { - let chunk = file.read(&mut buffer)?; - endian_swapper(&mut buffer); - self.command_memory_write(SDRAM_ADDRESS + offset, &buffer[0..chunk])?; - } - - self.command_config_set(Config::RomShadowEnable(rom_shadow_enabled))?; + self.command_config_set(Config::RomShadowEnable(rom_shadow_enabled.into()))?; if rom_shadow_enabled { - let mut buffer = vec![0u8; ROM_SHADOW_LENGTH]; - let chunk = file.read(&mut buffer)?; - endian_swapper(&mut buffer); - self.flash_program_shadow(&buffer[0..chunk])?; + let rom_shadow_length = min(length - sdram_length, ROM_SHADOW_LENGTH); + self.flash_program( + reader, + ROM_SHADOW_ADDRESS, + rom_shadow_length, + Some(endian_swapper), + )?; } - self.command_config_set(Config::RomExtendedEnable(rom_extended_enabled))?; + self.command_config_set(Config::RomExtendedEnable(rom_extended_enabled.into()))?; if rom_extended_enabled { - let mut buffer = vec![0u8; ROM_EXTENDED_LENGTH]; - let chunk = file.read(&mut buffer)?; - endian_swapper(&mut buffer); - self.flash_program_extended(&buffer[0..chunk])?; + let rom_extended_length = min(length - SDRAM_LENGTH, ROM_EXTENDED_LENGTH); + self.flash_program( + reader, + ROM_EXTENDED_ADDRESS, + rom_extended_length, + Some(endian_swapper), + )?; } Ok(()) } - pub fn upload_ddipl(&mut self, path: &str) -> Result<(), Error> { - let (mut file, length) = file_open_and_check_length(path, DDIPL_LENGTH)?; - - let mut buffer = vec![0u8; length]; - let chunk = file.read(&mut buffer)?; - - self.command_memory_write(DDIPL_ADDRESS, &buffer[0..chunk]) - } - - pub fn upload_save(&mut self, path: &str) -> Result<(), Error> { - let save_type = get_config!(self, SaveType)?; - - if matches!(save_type, SaveType::None) { - return Err(Error::new("No save type is enabled")); + pub fn upload_ddipl(&mut self, reader: &mut T, length: usize) -> Result<(), Error> { + if length > DDIPL_LENGTH { + return Err(Error::new("DDIPL length too big")); } - let address = match save_type { - SaveType::None => 0, - SaveType::Eeprom4k => EEPROM_ADDRESS, - SaveType::Eeprom16k => EEPROM_ADDRESS, - SaveType::Sram => SAVE_ADDRESS, - SaveType::SramBanked => SAVE_ADDRESS, - SaveType::Flashram => SAVE_ADDRESS, - }; + self.memory_write_chunked(reader, DDIPL_ADDRESS, length, None) + } - let save_length = match save_type { - SaveType::None => 0, - SaveType::Eeprom4k => EEPROM_4K_LENGTH, - SaveType::Eeprom16k => EEPROM_16K_LENGTH, - SaveType::Sram => SRAM_LENGTH, - SaveType::SramBanked => SRAM_BANKED_LENGTH, - SaveType::Flashram => FLASHRAM_LENGTH, - }; + pub fn upload_save(&mut self, reader: &mut T, length: usize) -> Result<(), Error> { + let save_type = get_config!(self, SaveType)?; - let (mut file, length) = file_open_and_check_length(path, save_length)?; + let (address, save_length) = match save_type { + SaveType::None => { + return Err(Error::new("No save type is enabled")); + } + SaveType::Eeprom4k => (EEPROM_ADDRESS, EEPROM_4K_LENGTH), + SaveType::Eeprom16k => (EEPROM_ADDRESS, EEPROM_16K_LENGTH), + SaveType::Sram => (SAVE_ADDRESS, SRAM_LENGTH), + SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH), + SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH), + }; if length != save_length { return Err(Error::new( @@ -451,10 +385,7 @@ impl SC64 { )); } - let mut buffer = vec![0u8; length]; - file.read(&mut buffer)?; - - self.command_memory_write(address, &buffer) + self.memory_write_chunked(reader, address, save_length, None) } pub fn dump_memory(&mut self, address: u32, length: usize) -> Result, Error> { @@ -498,7 +429,7 @@ impl SC64 { } pub fn set_led_blink(&mut self, enabled: bool) -> Result<(), Error> { - self.command_setting_set(Setting::LedEnable(enabled)) + self.command_setting_set(Setting::LedEnable(enabled.into())) } pub fn get_device_state(&mut self) -> Result { @@ -529,7 +460,7 @@ impl SC64 { drive_type: DdDriveType, ) -> Result<(), Error> { self.command_config_set(Config::DdMode(dd_mode))?; - self.command_config_set(Config::DdSdEnable(false))?; + self.command_config_set(Config::DdSdEnable(Switch::Off))?; self.command_config_set(Config::DdDriveType(drive_type))?; self.command_config_set(Config::DdDiskState(DdDiskState::Ejected))?; Ok(()) @@ -539,22 +470,22 @@ impl SC64 { self.command_config_set(Config::DdDiskState(disk_state)) } - pub fn configure_isviewer64(&mut self, offset: Option) -> Result<(), Error> { - if let Some(off) = offset { - if get_config!(self, RomShadowEnable)? { - if off > (SAVE_ADDRESS - ISV_BUFFER_LENGTH as u32) { + pub fn configure_is_viewer_64(&mut self, offset: Option) -> Result<(), Error> { + if let Some(offset) = offset { + if get_config!(self, RomShadowEnable)?.into() { + if offset > (SAVE_ADDRESS - ISV_BUFFER_LENGTH as u32) { return Err(Error::new( format!( - "ROM shadow is enabled, IS-Viewer 64 at offset 0x{off:08X} won't work" + "ROM shadow is enabled, IS-Viewer 64 at offset 0x{offset:08X} won't work" ) .as_str(), )); } } - self.command_config_set(Config::RomWriteEnable(true))?; - self.command_config_set(Config::IsvAddress(off))?; + self.command_config_set(Config::RomWriteEnable(Switch::On))?; + self.command_config_set(Config::IsvAddress(offset))?; } else { - self.command_config_set(Config::RomWriteEnable(false))?; + self.command_config_set(Config::RomWriteEnable(Switch::Off))?; self.command_config_set(Config::IsvAddress(0))?; } Ok(()) @@ -586,12 +517,29 @@ impl SC64 { self.command_usb_write(debug_packet.datatype, &debug_packet.data) } + pub fn check_firmware_version(&mut self) -> Result<(u16, u16), Error> { + let (major, minor) = self + .command_version_get() + .map_err(|_| Error::new("Outdated SC64 firmware version, please update firmware"))?; + if major != SUPPORTED_MAJOR_VERSION || minor < SUPPORTED_MINOR_VERSION { + return Err(Error::new( + "Unsupported SC64 firmware version, please update firmware", + )); + } + Ok((major, minor)) + } + + pub fn reset_state(&mut self) -> Result<(), Error> { + self.command_state_reset()?; + Ok(()) + } + pub fn backup_firmware(&mut self) -> Result, Error> { self.command_state_reset()?; let (status, length) = self.command_firmware_backup(FIRMWARE_ADDRESS)?; if !matches!(status, FirmwareStatus::Ok) { return Err(Error::new( - format!("Firmware backup error: {:?}", status).as_str(), + format!("Firmware backup error: {}", status).as_str(), )); } self.command_memory_read(FIRMWARE_ADDRESS, length as usize) @@ -603,7 +551,7 @@ impl SC64 { let status = self.command_firmware_update(FIRMWARE_ADDRESS, data.len())?; if !matches!(status, FirmwareStatus::Ok) { return Err(Error::new( - format!("Firmware update verify error: {:?}", status).as_str(), + format!("Firmware update verify error: {}", status).as_str(), )); } let timeout = Instant::now(); @@ -619,7 +567,7 @@ impl SC64 { UpdateStatus::Err => { return Err(Error::new( format!( - "Firmware update error on step {:?}, device is most likely bricked", + "Firmware update error on step {}, device is most likely bricked", last_update_status ) .as_str(), @@ -641,6 +589,52 @@ impl SC64 { std::thread::sleep(Duration::from_millis(1)); } } + + pub fn update_bootloader(&mut self, reader: &mut dyn Read, length: usize) -> Result<(), Error> { + if length > BOOTLOADER_LENGTH { + return Err(Error::new("Bootloader length too big")); + } + self.flash_program(reader, BOOTLOADER_ADDRESS, length, None) + } + + fn memory_write_chunked( + &mut self, + reader: &mut dyn Read, + address: u32, + length: usize, + transform: Option, + ) -> Result<(), Error> { + let mut data: Vec = vec![0u8; MEMORY_WRITE_CHUNK_SIZE]; + for offset in (0..length).step_by(MEMORY_WRITE_CHUNK_SIZE) { + let chunk = reader.read(&mut data)?; + if let Some(transform) = transform { + transform(&mut data); + } + self.command_memory_write(address + offset as u32, &data[0..chunk])?; + } + Ok(()) + } + + fn flash_erase(&mut self, address: u32, length: usize) -> Result<(), Error> { + let erase_block_size = self.command_flash_wait_busy(false)?; + for offset in (0..length as u32).step_by(erase_block_size as usize) { + self.command_flash_erase_block(address + offset)?; + } + Ok(()) + } + + fn flash_program( + &mut self, + reader: &mut dyn Read, + address: u32, + length: usize, + transform: Option, + ) -> Result<(), Error> { + self.flash_erase(address, length)?; + self.memory_write_chunked(reader, address, length, transform)?; + self.command_flash_wait_busy(true)?; + Ok(()) + } } pub fn new(sn: Option) -> Result { @@ -664,7 +658,7 @@ pub fn new(sn: Option) -> Result { .command_identifier_get() .map_err(|_| Error::new("Couldn't get SC64 device identifier"))?; - if identifier != b"SCv2" { + if identifier != SC64_V2_IDENTIFIER { return Err(Error::new("Unknown identifier received, not a SC64 device")); } diff --git a/sw/deployer/src/sc64/types.rs b/sw/deployer/src/sc64/types.rs index 4d46d82..1edff6f 100644 --- a/sw/deployer/src/sc64/types.rs +++ b/sw/deployer/src/sc64/types.rs @@ -1,5 +1,6 @@ use super::{link::Packet, utils::u32_from_vec, Error}; use encoding_rs::EUC_JP; +use std::fmt::Display; #[derive(Clone, Copy)] pub enum ConfigId { @@ -21,21 +22,21 @@ pub enum ConfigId { } pub enum Config { - BootloaderSwitch(bool), - RomWriteEnable(bool), - RomShadowEnable(bool), + BootloaderSwitch(Switch), + RomWriteEnable(Switch), + RomShadowEnable(Switch), DdMode(DdMode), IsvAddress(u32), BootMode(BootMode), SaveType(SaveType), CicSeed(CicSeed), TvType(TvType), - DdSdEnable(bool), + DdSdEnable(Switch), DdDriveType(DdDriveType), DdDiskState(DdDiskState), - ButtonState(bool), + ButtonState(ButtonState), ButtonMode(ButtonMode), - RomExtendedEnable(bool), + RomExtendedEnable(Switch), } impl From for u32 { @@ -65,21 +66,21 @@ impl TryFrom<(ConfigId, u32)> for Config { fn try_from(value: (ConfigId, u32)) -> Result { let (id, config) = value; Ok(match id { - ConfigId::BootloaderSwitch => Config::BootloaderSwitch(config != 0), - ConfigId::RomWriteEnable => Config::RomWriteEnable(config != 0), - ConfigId::RomShadowEnable => Config::RomShadowEnable(config != 0), - ConfigId::DdMode => Config::DdMode(config.try_into()?), - ConfigId::IsvAddress => Config::IsvAddress(config), - ConfigId::BootMode => Config::BootMode(config.try_into()?), - ConfigId::SaveType => Config::SaveType(config.try_into()?), - ConfigId::CicSeed => Config::CicSeed(config.try_into()?), - ConfigId::TvType => Config::TvType(config.try_into()?), - ConfigId::DdSdEnable => Config::DdSdEnable(config != 0), - ConfigId::DdDriveType => Config::DdDriveType(config.try_into()?), - ConfigId::DdDiskState => Config::DdDiskState(config.try_into()?), - ConfigId::ButtonState => Config::ButtonState(config != 0), - ConfigId::ButtonMode => Config::ButtonMode(config.try_into()?), - ConfigId::RomExtendedEnable => Config::RomExtendedEnable(config != 0), + ConfigId::BootloaderSwitch => Self::BootloaderSwitch(config.try_into()?), + ConfigId::RomWriteEnable => Self::RomWriteEnable(config.try_into()?), + ConfigId::RomShadowEnable => Self::RomShadowEnable(config.try_into()?), + ConfigId::DdMode => Self::DdMode(config.try_into()?), + ConfigId::IsvAddress => Self::IsvAddress(config), + ConfigId::BootMode => Self::BootMode(config.try_into()?), + ConfigId::SaveType => Self::SaveType(config.try_into()?), + ConfigId::CicSeed => Self::CicSeed(config.try_into()?), + ConfigId::TvType => Self::TvType(config.try_into()?), + ConfigId::DdSdEnable => Self::DdSdEnable(config.try_into()?), + ConfigId::DdDriveType => Self::DdDriveType(config.try_into()?), + ConfigId::DdDiskState => Self::DdDiskState(config.try_into()?), + ConfigId::ButtonState => Self::ButtonState(config.try_into()?), + ConfigId::ButtonMode => Self::ButtonMode(config.try_into()?), + ConfigId::RomExtendedEnable => Self::RomExtendedEnable(config.try_into()?), }) } } @@ -106,7 +107,58 @@ impl From for [u32; 2] { } } -#[derive(Debug)] +#[derive(Copy, Clone)] +pub enum Switch { + Off, + On, +} + +impl Display for Switch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Switch::Off => "Disabled", + Switch::On => "Enabled", + }) + } +} + +impl TryFrom for Switch { + type Error = Error; + fn try_from(value: u32) -> Result { + Ok(match value { + 0 => Self::Off, + _ => Self::On, + }) + } +} + +impl From for u32 { + fn from(value: Switch) -> Self { + match value { + Switch::Off => 0, + Switch::On => 1, + } + } +} + +impl From for Switch { + fn from(value: bool) -> Self { + match value { + false => Self::Off, + true => Self::On, + } + } +} + +impl From for bool { + fn from(value: Switch) -> Self { + match value { + Switch::Off => false, + Switch::On => true, + } + } +} + pub enum DdMode { None, Regs, @@ -114,14 +166,25 @@ pub enum DdMode { Full, } +impl Display for DdMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + DdMode::None => "Disabled", + DdMode::Regs => "Only registers", + DdMode::DdIpl => "Only 64DD IPL", + DdMode::Full => "Registers + 64DD IPL", + }) + } +} + impl TryFrom for DdMode { type Error = Error; fn try_from(value: u32) -> Result { Ok(match value { - 0 => DdMode::None, - 1 => DdMode::Regs, - 2 => DdMode::DdIpl, - 3 => DdMode::Full, + 0 => Self::None, + 1 => Self::Regs, + 2 => Self::DdIpl, + 3 => Self::Full, _ => return Err(Error::new("Unknown 64DD mode code")), }) } @@ -138,7 +201,6 @@ impl From for u32 { } } -#[derive(Copy, Clone, Debug)] pub enum BootMode { Menu, Rom, @@ -147,15 +209,27 @@ pub enum BootMode { DirectDdIpl, } +impl Display for BootMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Menu => "Menu", + Self::Rom => "Bootloader -> ROM", + Self::DdIpl => "Bootloader -> 64DD IPL", + Self::DirectRom => "ROM (direct)", + Self::DirectDdIpl => "64DD IPL (direct)", + }) + } +} + impl TryFrom for BootMode { type Error = Error; fn try_from(value: u32) -> Result { Ok(match value { - 0 => BootMode::Menu, - 1 => BootMode::Rom, - 2 => BootMode::DdIpl, - 3 => BootMode::DirectRom, - 4 => BootMode::DirectDdIpl, + 0 => Self::Menu, + 1 => Self::Rom, + 2 => Self::DdIpl, + 3 => Self::DirectRom, + 4 => Self::DirectDdIpl, _ => return Err(Error::new("Unknown boot mode code")), }) } @@ -173,7 +247,6 @@ impl From for u32 { } } -#[derive(Debug)] pub enum SaveType { None, Eeprom4k, @@ -183,16 +256,29 @@ pub enum SaveType { SramBanked, } +impl Display for SaveType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::None => "None", + Self::Eeprom4k => "EEPROM 4k", + Self::Eeprom16k => "EEPROM 16k", + Self::Sram => "SRAM", + Self::SramBanked => "SRAM banked", + Self::Flashram => "FlashRAM", + }) + } +} + impl TryFrom for SaveType { type Error = Error; fn try_from(value: u32) -> Result { Ok(match value { - 0 => SaveType::None, - 1 => SaveType::Eeprom4k, - 2 => SaveType::Eeprom16k, - 3 => SaveType::Sram, - 4 => SaveType::Flashram, - 5 => SaveType::SramBanked, + 0 => Self::None, + 1 => Self::Eeprom4k, + 2 => Self::Eeprom16k, + 3 => Self::Sram, + 4 => Self::Flashram, + 5 => Self::SramBanked, _ => return Err(Error::new("Unknown save type code")), }) } @@ -211,19 +297,28 @@ impl From for u32 { } } -#[derive(Debug)] pub enum CicSeed { Seed(u8), Auto, } +impl Display for CicSeed { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let CicSeed::Seed(seed) = self { + f.write_fmt(format_args!("0x{seed:02X}")) + } else { + f.write_str("Auto") + } + } +} + impl TryFrom for CicSeed { type Error = Error; fn try_from(value: u32) -> Result { Ok(if value <= 0xFF { - CicSeed::Seed(value as u8) + Self::Seed(value as u8) } else if value == 0xFFFF { - CicSeed::Auto + Self::Auto } else { return Err(Error::new("Unknown CIC seed code")); }) @@ -239,7 +334,6 @@ impl From for u32 { } } -#[derive(Debug)] pub enum TvType { PAL, NTSC, @@ -247,14 +341,25 @@ pub enum TvType { Auto, } +impl Display for TvType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::PAL => "PAL", + Self::NTSC => "NTSC", + Self::MPAL => "MPAL", + Self::Auto => "Auto", + }) + } +} + impl TryFrom for TvType { type Error = Error; fn try_from(value: u32) -> Result { Ok(match value { - 0 => TvType::PAL, - 1 => TvType::NTSC, - 2 => TvType::MPAL, - 3 => TvType::Auto, + 0 => Self::PAL, + 1 => Self::NTSC, + 2 => Self::MPAL, + 3 => Self::Auto, _ => return Err(Error::new("Unknown TV type code")), }) } @@ -271,18 +376,26 @@ impl From for u32 { } } -#[derive(Debug)] pub enum DdDriveType { Retail, Development, } +impl Display for DdDriveType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + DdDriveType::Retail => "Retail", + DdDriveType::Development => "Development", + }) + } +} + impl TryFrom for DdDriveType { type Error = Error; fn try_from(value: u32) -> Result { Ok(match value { - 0 => DdDriveType::Retail, - 1 => DdDriveType::Development, + 0 => Self::Retail, + 1 => Self::Development, _ => return Err(Error::new("Unknown 64DD drive type code")), }) } @@ -297,20 +410,29 @@ impl From for u32 { } } -#[derive(Debug)] pub enum DdDiskState { Ejected, Inserted, Changed, } +impl Display for DdDiskState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + DdDiskState::Ejected => "Ejected", + DdDiskState::Inserted => "Inserted", + DdDiskState::Changed => "Changed", + }) + } +} + impl TryFrom for DdDiskState { type Error = Error; fn try_from(value: u32) -> Result { Ok(match value { - 0 => DdDiskState::Ejected, - 1 => DdDiskState::Inserted, - 2 => DdDiskState::Changed, + 0 => Self::Ejected, + 1 => Self::Inserted, + 2 => Self::Changed, _ => return Err(Error::new("Unknown 64DD disk state code")), }) } @@ -326,7 +448,57 @@ impl From for u32 { } } -#[derive(Debug)] +pub enum ButtonState { + NotPressed, + Pressed, +} + +impl Display for ButtonState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + ButtonState::NotPressed => "Not pressed", + ButtonState::Pressed => "Pressed", + }) + } +} + +impl TryFrom for ButtonState { + type Error = Error; + fn try_from(value: u32) -> Result { + Ok(match value { + 0 => Self::NotPressed, + _ => Self::Pressed, + }) + } +} + +impl From for u32 { + fn from(value: ButtonState) -> Self { + match value { + ButtonState::NotPressed => 0, + ButtonState::Pressed => 1, + } + } +} + +impl From for ButtonState { + fn from(value: bool) -> Self { + match value { + false => Self::NotPressed, + true => Self::Pressed, + } + } +} + +impl From for bool { + fn from(value: ButtonState) -> Self { + match value { + ButtonState::NotPressed => false, + ButtonState::Pressed => true, + } + } +} + pub enum ButtonMode { None, N64Irq, @@ -334,14 +506,25 @@ pub enum ButtonMode { DdDiskSwap, } +impl Display for ButtonMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + ButtonMode::None => "User", + ButtonMode::N64Irq => "N64 IRQ trigger", + ButtonMode::UsbPacket => "Send USB packet", + ButtonMode::DdDiskSwap => "Swap 64DD disk", + }) + } +} + impl TryFrom for ButtonMode { type Error = Error; fn try_from(value: u32) -> Result { Ok(match value { - 0 => ButtonMode::None, - 1 => ButtonMode::N64Irq, - 2 => ButtonMode::UsbPacket, - 3 => ButtonMode::DdDiskSwap, + 0 => Self::None, + 1 => Self::N64Irq, + 2 => Self::UsbPacket, + 3 => Self::DdDiskSwap, _ => return Err(Error::new("Unknown button mode code")), }) } @@ -364,7 +547,7 @@ pub enum SettingId { } pub enum Setting { - LedEnable(bool), + LedEnable(Switch), } impl From for u32 { @@ -380,7 +563,7 @@ impl TryFrom<(SettingId, u32)> for Setting { fn try_from(value: (SettingId, u32)) -> Result { let (id, setting) = value; Ok(match id { - SettingId::LedEnable => Setting::LedEnable(setting != 0), + SettingId::LedEnable => Self::LedEnable(setting.try_into()?), }) } } @@ -480,7 +663,6 @@ impl DiskBlock { } } -#[derive(Debug)] pub enum FirmwareStatus { Ok, ErrToken, @@ -490,22 +672,34 @@ pub enum FirmwareStatus { ErrRead, } +impl Display for FirmwareStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + FirmwareStatus::Ok => "OK", + FirmwareStatus::ErrToken => "Invalid firmware header", + FirmwareStatus::ErrChecksum => "Invalid chunk checksum", + FirmwareStatus::ErrSize => "Invalid firmware size", + FirmwareStatus::ErrUnknownChunk => "Unknown chunk in firmware", + FirmwareStatus::ErrRead => "Firmware read error", + }) + } +} + impl TryFrom for FirmwareStatus { type Error = Error; fn try_from(value: u32) -> Result { Ok(match value { - 0 => FirmwareStatus::Ok, - 1 => FirmwareStatus::ErrToken, - 2 => FirmwareStatus::ErrChecksum, - 3 => FirmwareStatus::ErrSize, - 4 => FirmwareStatus::ErrUnknownChunk, - 5 => FirmwareStatus::ErrRead, + 0 => Self::Ok, + 1 => Self::ErrToken, + 2 => Self::ErrChecksum, + 3 => Self::ErrSize, + 4 => Self::ErrUnknownChunk, + 5 => Self::ErrRead, _ => return Err(Error::new("Unknown firmware status code")), }) } } -#[derive(Debug)] pub enum UpdateStatus { MCU, FPGA, @@ -514,15 +708,27 @@ pub enum UpdateStatus { Err, } +impl Display for UpdateStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + UpdateStatus::MCU => "Microcontroller", + UpdateStatus::FPGA => "FPGA", + UpdateStatus::Bootloader => "Bootloader", + UpdateStatus::Done => "Done", + UpdateStatus::Err => "Error", + }) + } +} + impl TryFrom for UpdateStatus { type Error = Error; fn try_from(value: u32) -> Result { Ok(match value { - 1 => UpdateStatus::MCU, - 2 => UpdateStatus::FPGA, - 3 => UpdateStatus::Bootloader, - 0x80 => UpdateStatus::Done, - 0xFF => UpdateStatus::Err, + 1 => Self::MCU, + 2 => Self::FPGA, + 3 => Self::Bootloader, + 0x80 => Self::Done, + 0xFF => Self::Err, _ => return Err(Error::new("Unknown update status code")), }) } @@ -540,7 +746,8 @@ macro_rules! get_config { macro_rules! get_setting { ($sc64:ident, $setting:ident) => {{ - #[allow(irrefutable_let_patterns)] // TODO: is there another way to ignore this warning? + // Note: remove 'allow(irrefutable_let_patterns)' below when more settings are added + #[allow(irrefutable_let_patterns)] if let Setting::$setting(value) = $sc64.command_setting_get(SettingId::$setting)? { Ok(value) } else { diff --git a/sw/deployer/src/sc64/utils.rs b/sw/deployer/src/sc64/utils.rs index 91e7b86..d3f152b 100644 --- a/sw/deployer/src/sc64/utils.rs +++ b/sw/deployer/src/sc64/utils.rs @@ -61,15 +61,3 @@ pub fn vec_from_datetime(datetime: DateTime) -> Result, Error> { let day = bcd_from_u8(datetime.day() as u8); Ok(vec![weekday, hour, minute, second, 0, year, month, day]) } - -pub fn file_open_and_check_length( - path: &str, - max_length: usize, -) -> Result<(std::fs::File, usize), Error> { - let file = std::fs::File::open(path)?; - let length = file.metadata()?.len() as usize; - if length > max_length { - return Err(Error::new("File size is too big")); - } - Ok((file, length)) -}