This commit is contained in:
Mateusz Faderewski 2023-03-03 00:26:33 +01:00
parent 19159b094e
commit e83c6feeb0
10 changed files with 1331 additions and 482 deletions

402
sw/deployer/Cargo.lock generated
View File

@ -23,6 +23,12 @@ dependencies = [
"mach", "mach",
] ]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.20" version = "0.7.20"
@ -58,6 +64,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bit_field"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -70,6 +82,18 @@ version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 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]] [[package]]
name = "cc" name = "cc"
version = "1.0.79" version = "1.0.79"
@ -153,6 +177,12 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]] [[package]]
name = "colored" name = "colored"
version = "2.0.0" version = "2.0.0"
@ -179,6 +209,55 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "ctrlc" name = "ctrlc"
version = "3.2.5" version = "3.2.5"
@ -233,6 +312,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.32" version = "0.8.32"
@ -263,6 +348,89 @@ dependencies = [
"libc", "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]] [[package]]
name = "heck" name = "heck"
version = "0.4.1" version = "0.4.1"
@ -278,6 +446,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.1" version = "0.3.1"
@ -308,6 +485,25 @@ dependencies = [
"cxx-build", "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]] [[package]]
name = "io-lifetimes" name = "io-lifetimes"
version = "1.0.5" version = "1.0.5"
@ -330,6 +526,15 @@ dependencies = [
"windows-sys", "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]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.61" version = "0.3.61"
@ -345,6 +550,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lebe"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.139" version = "0.2.139"
@ -386,6 +597,16 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 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]] [[package]]
name = "log" name = "log"
version = "0.4.17" version = "0.4.17"
@ -419,6 +640,33 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 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]] [[package]]
name = "nix" name = "nix"
version = "0.26.2" version = "0.26.2"
@ -441,6 +689,17 @@ dependencies = [
"num-traits", "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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.15" version = "0.2.15"
@ -450,6 +709,16 @@ dependencies = [
"autocfg", "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]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.17.1" version = "1.17.1"
@ -468,12 +737,44 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384e52fd8fbd4cbe3c317e8216260c21a0f9134de108cea8a4dd4e7e152c472d" 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]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.26" version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 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]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -516,6 +817,28 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "regex" name = "regex"
version = "1.7.1" version = "1.7.1"
@ -548,7 +871,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "sc64loader" name = "sc64deployer"
version = "2.12.2" version = "2.12.2"
dependencies = [ dependencies = [
"chrono", "chrono",
@ -558,10 +881,23 @@ dependencies = [
"crc32fast", "crc32fast",
"ctrlc", "ctrlc",
"encoding_rs", "encoding_rs",
"image",
"panic-message", "panic-message",
"serialport", "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]] [[package]]
name = "scratch" name = "scratch"
version = "1.0.3" version = "1.0.3"
@ -584,6 +920,27 @@ dependencies = [
"winapi", "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]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"
@ -616,6 +973,26 @@ dependencies = [
"winapi-util", "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]] [[package]]
name = "time" name = "time"
version = "0.1.45" version = "0.1.45"
@ -623,7 +1000,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi 0.10.0+wasi-snapshot-preview1",
"winapi", "winapi",
] ]
@ -651,6 +1028,12 @@ version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 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]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.84" version = "0.2.84"
@ -705,6 +1088,12 @@ version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "weezl"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -801,3 +1190,12 @@ name = "windows_x86_64_msvc"
version = "0.42.1" version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 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",
]

View File

@ -14,5 +14,6 @@ colored = "2.0.0"
crc32fast = "1.3.2" crc32fast = "1.3.2"
ctrlc = "3.2.5" ctrlc = "3.2.5"
encoding_rs = "0.8.32" encoding_rs = "0.8.32"
image = "0.24.5"
panic-message = "0.3.0" panic-message = "0.3.0"
serialport = { git = "https://github.com/serialport/serialport-rs", branch = "main" } serialport = { git = "https://github.com/serialport/serialport-rs", branch = "main" }

View File

@ -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) { pub struct Handler {
let DebugPacket { datatype, data } = debug_packet; header: Option<Vec<u8>>,
match datatype { gdb_tx: Sender<Vec<u8>>,
0x01 => handle_datatype_text(&data), gdb_rx: Receiver<Vec<u8>>,
// 0x02 => handle_datatype_raw_binary(&data), }
// 0x03 => handle_datatype_header(&data),
// 0x04 => handle_datatype_screenshot(&data), enum DataType {
// 0xDB => handle_datatype_gdb(&data), Text,
_ => {} RawBinary,
Header,
Screenshot,
GDB,
Unknown,
}
impl From<u8> 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]) { impl From<DataType> for u8 {
if let Ok(message) = std::str::from_utf8(data) { fn from(value: DataType) -> Self {
print!("{message}"); 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<sc64::DebugPacket> {
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<u16>) -> Result<Handler, sc64::Error> {
let (gdb_tx, gdb_loop_rx) = channel::<Vec<u8>>();
let (gdb_loop_tx, gdb_rx) = channel::<Vec<u8>>();
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<Vec<u8>>, gdb_rx: Receiver<Vec<u8>>) {
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<Vec<u8>>,
gdb_rx: &Receiver<Vec<u8>>,
) {
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));
}
}

View File

@ -1,19 +1,23 @@
mod debug; mod debug;
mod n64; mod n64;
mod sc64; pub mod sc64; // TODO: delete pub
use chrono::Local; use chrono::Local;
use clap::{Args, Parser, Subcommand, ValueEnum}; use clap::{Args, Parser, Subcommand, ValueEnum};
use clap_num::maybe_hex_range; use clap_num::maybe_hex_range;
use colored::Colorize; use colored::Colorize;
use debug::handle_debug_packet;
use panic_message::panic_message; use panic_message::panic_message;
use std::io::{Read, Write}; use std::{
use std::path::PathBuf; fs::File,
use std::sync::atomic::{AtomicBool, Ordering}; io::{self, Read, Write},
use std::sync::Arc; path::PathBuf,
use std::time::Duration; sync::{
use std::{panic, process, thread}; atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
{panic, process, thread},
};
#[derive(Parser)] #[derive(Parser)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
@ -167,6 +171,32 @@ enum SaveType {
Flashram, Flashram,
} }
impl From<n64::SaveType> 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<SaveType> 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)] #[derive(Clone, Debug, ValueEnum)]
enum TvType { enum TvType {
PAL, PAL,
@ -174,6 +204,16 @@ enum TvType {
MPAL, MPAL,
} }
impl From<TvType> for sc64::TvType {
fn from(value: TvType) -> Self {
match value {
TvType::PAL => Self::PAL,
TvType::NTSC => Self::NTSC,
TvType::MPAL => Self::MPAL,
}
}
}
fn main() { fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
@ -188,28 +228,6 @@ fn main() {
} }
} }
fn init_sc64(sn: Option<String>, check_firmware: bool) -> Result<sc64::SC64, sc64::Error> {
let mut sc64 = sc64::new(sn)?;
if check_firmware {
sc64.check_firmware_version()?;
}
Ok(sc64)
}
fn setup_exit_flag() -> Arc<AtomicBool> {
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<String>) { fn handle_command(command: &Commands, sn: Option<String>) {
let result = match command { let result = match command {
Commands::Upload(args) => handle_upload_command(sn, args), Commands::Upload(args) => handle_upload_command(sn, args),
@ -232,62 +250,45 @@ fn handle_upload_command(sn: Option<String>, args: &UploadArgs) -> Result<(), sc
sc64.reset_state()?; sc64.reset_state()?;
let rom_path = args.rom.to_str().unwrap(); let (mut rom_file, rom_name, rom_length) = open_file(&args.rom)?;
print!( log_wait(format!("Uploading ROM [{rom_name}]"), || {
"Uploading ROM [{}]... ", sc64.upload_rom(&mut rom_file, rom_length, args.no_shadow)
args.rom.file_name().unwrap().to_str().unwrap() })?;
);
std::io::stdout().flush().unwrap();
sc64.upload_rom(rom_path, args.no_shadow)?;
println!("done");
// TODO: autodetect save let save: SaveType = if let Some(save_type) = args.save_type.clone() {
save_type
let args_save_type = args.save_type.as_ref().unwrap_or(&SaveType::None); } else {
let save_type = match args_save_type { n64::guess_save_type(&mut rom_file)?.into()
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_type: sc64::SaveType = save.into();
println!("Save type set to [{save_type}]");
sc64.set_save_type(save_type)?; sc64.set_save_type(save_type)?;
if args.save.is_some() { 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!( log_wait(format!("Uploading save [{save_name}]"), || {
"Uploading save [{}]... ", sc64.upload_save(&mut save_file, save_length)
save.file_name().unwrap().to_str().unwrap() })?;
);
std::io::stdout().flush().unwrap();
sc64.upload_save(save.to_str().unwrap())?;
println!("done");
} }
println!("Save type set to [{args_save_type:?}]");
let boot_mode = if args.direct { let boot_mode = if args.direct {
sc64::BootMode::DirectRom sc64::BootMode::DirectRom
} else { } else {
sc64::BootMode::Rom sc64::BootMode::Rom
}; };
println!("Boot mode set to [{boot_mode}]");
sc64.set_boot_mode(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 { 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 { } else {
sc64.set_tv_type(match tv { let tv_type: sc64::TvType = tv.into();
TvType::PAL => sc64::TvType::PAL, println!("TV type set to [{tv_type}]");
TvType::NTSC => sc64::TvType::NTSC, sc64.set_tv_type(tv_type)?;
TvType::MPAL => sc64::TvType::MPAL,
})?;
println!("TV type set to [{tv:?}]");
} }
} }
@ -297,73 +298,47 @@ fn handle_upload_command(sn: Option<String>, args: &UploadArgs) -> Result<(), sc
} }
fn handle_64dd_command(sn: Option<String>, args: &_64DDArgs) -> Result<(), sc64::Error> { fn handle_64dd_command(sn: Option<String>, 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()?; println!("{}", "Sorry nothing".yellow());
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));
}
}
Ok(()) Ok(())
} }
fn handle_dump_command(sn: Option<String>, args: &DumpArgs) -> Result<(), sc64::Error> { fn handle_dump_command(sn: Option<String>, 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)?; let mut sc64 = init_sc64(sn, true)?;
print!( let (mut dump_file, dump_name) = create_file(&args.path)?;
"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)?;
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(()) Ok(())
} }
fn handle_debug_command(sn: Option<String>, args: &DebugArgs) -> Result<(), sc64::Error> { fn handle_debug_command(sn: Option<String>, 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)?; let mut sc64 = init_sc64(sn, true)?;
if args.isv.is_some() { 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()); println!("{}", "Debug mode started".bold());
@ -372,10 +347,16 @@ fn handle_debug_command(sn: Option<String>, args: &DebugArgs) -> Result<(), sc64
while !exit.load(Ordering::Relaxed) { while !exit.load(Ordering::Relaxed) {
if let Some(data_packet) = sc64.receive_data_packet()? { if let Some(data_packet) = sc64.receive_data_packet()? {
match data_packet { match data_packet {
sc64::DataPacket::IsViewer(message) => print!("{}", message), sc64::DataPacket::IsViewer(message) => {
sc64::DataPacket::Debug(debug_packet) => handle_debug_packet(debug_packet), 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 { } else {
thread::sleep(Duration::from_millis(1)); thread::sleep(Duration::from_millis(1));
} }
@ -384,7 +365,7 @@ fn handle_debug_command(sn: Option<String>, args: &DebugArgs) -> Result<(), sc64
println!("{}", "Debug mode ended".bold()); println!("{}", "Debug mode ended".bold());
if args.isv.is_some() { if args.isv.is_some() {
sc64.configure_isviewer64(None)?; sc64.configure_is_viewer_64(None)?;
} }
Ok(()) Ok(())
@ -398,24 +379,24 @@ fn handle_info_command(sn: Option<String>) -> Result<(), sc64::Error> {
let datetime = state.datetime.format("%Y-%m-%d %H:%M:%S %Z"); let datetime = state.datetime.format("%Y-%m-%d %H:%M:%S %Z");
println!("{}", "SC64 information and current state:".bold()); println!("{}", "SC64 information and current state:".bold());
println!(" Firmware version: {major}.{minor}"); println!(" Firmware version: {}.{}", major, minor);
println!(" RTC datetime: {}", datetime); println!(" RTC datetime: {}", datetime);
println!(" LED blink enabled: {}", state.led_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!(" Bootloader switch: {}", state.bootloader_switch); println!(" Bootloader switch: {}", state.bootloader_switch);
println!(" ROM write enabled: {}", state.rom_write_enable); println!(" ROM write: {}", state.rom_write_enable);
println!(" ROM shadow enabled: {}", state.rom_shadow_enable); println!(" ROM shadow: {}", state.rom_shadow_enable);
println!(" ROM extended enabled: {}", state.rom_extended_enable); println!(" ROM extended: {}", 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!(" 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(()) Ok(())
} }
@ -456,56 +437,49 @@ fn handle_firmware_command(
) -> Result<(), sc64::Error> { ) -> Result<(), sc64::Error> {
match command { match command {
FirmwareCommands::Info(args) => { 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 mut firmware = vec![0u8; firmware_length as usize];
let length = file.metadata()?.len(); firmware_file.read_exact(&mut firmware)?;
let mut buffer = vec![0u8; length as usize];
file.read_exact(&mut buffer)?;
// TODO: print firmware metadata // TODO: print firmware metadata
println!("{}", "Sorry nothing".yellow());
Ok(()) Ok(())
} }
FirmwareCommands::Backup(args) => { FirmwareCommands::Backup(args) => {
let backup_path = &args.firmware;
let mut file = std::fs::File::create(backup_path)?;
let mut sc64 = init_sc64(sn, false)?; let mut sc64 = init_sc64(sn, false)?;
print!( let (mut backup_file, backup_name) = create_file(&args.firmware)?;
"Generating firmware backup, this might take a while [{}]... ",
backup_path.file_name().unwrap().to_str().unwrap() let firmware = log_wait(
); format!("Generating firmware backup, this might take a while [{backup_name}]"),
std::io::stdout().flush().unwrap(); || sc64.backup_firmware(),
let data = sc64.backup_firmware()?; )?;
file.write(&data)?;
println!("done"); // TODO: print firmware metadata
backup_file.write(&firmware)?;
Ok(()) Ok(())
} }
FirmwareCommands::Update(args) => { 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 (mut update_file, update_name, update_length) = open_file(&args.firmware)?;
let length = file.metadata()?.len();
let mut buffer = vec![0u8; length as usize]; let mut fimware = vec![0u8; update_length as usize];
file.read_exact(&mut buffer)?; update_file.read_exact(&mut fimware)?;
// TODO: print firmware metadata // TODO: print firmware metadata
let mut sc64 = init_sc64(sn, false)?; log_wait(
format!("Updating firmware, this might take a while [{update_name}]"),
print!( || sc64.update_firmware(&fimware),
"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");
Ok(()) Ok(())
} }
@ -522,3 +496,46 @@ fn handle_list_command() -> Result<(), sc64::Error> {
Ok(()) Ok(())
} }
fn init_sc64(sn: Option<String>, check_firmware: bool) -> Result<sc64::SC64, sc64::Error> {
let mut sc64 = sc64::new(sn)?;
if check_firmware {
sc64.check_firmware_version()?;
}
Ok(sc64)
}
fn log_wait<F: FnOnce() -> Result<T, E>, T, E>(message: String, operation: F) -> Result<T, E> {
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<AtomicBool> {
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
}

View File

@ -0,0 +1,70 @@
use std::io::{Error, Read, Seek};
pub enum SaveType {
None,
Eeprom4k,
Eeprom16k,
Sram,
SramBanked,
Flashram,
}
pub fn guess_save_type<T: Read + Seek>(reader: &mut T) -> Result<SaveType, Error> {
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)
}

View File

@ -33,10 +33,10 @@ enum DataType {
} }
pub trait Link { pub trait Link {
fn execute_command(&mut self, command: &Command) -> Result<Vec<u8>, Error>; fn execute_command(&mut self, command: &mut Command) -> Result<Vec<u8>, Error>;
fn execute_command_raw( fn execute_command_raw(
&mut self, &mut self,
command: &Command, command: &mut Command,
timeout: Duration, timeout: Duration,
no_response: bool, no_response: bool,
ignore_error: bool, ignore_error: bool,
@ -83,15 +83,17 @@ impl SerialLink {
Ok(()) Ok(())
} }
fn serial_send_command(&mut self, command: &Command, timeout: Duration) -> Result<(), Error> { fn serial_send_command(
let Command { id, args, mut data } = command.clone(); &mut self,
command: &mut Command,
timeout: Duration,
) -> Result<(), Error> {
let mut packet: Vec<u8> = Vec::new(); let mut packet: Vec<u8> = Vec::new();
packet.append(&mut b"CMD".to_vec()); packet.append(&mut b"CMD".to_vec());
packet.append(&mut [id].to_vec()); packet.append(&mut [command.id].to_vec());
packet.append(&mut args[0].to_be_bytes().to_vec()); packet.append(&mut command.args[0].to_be_bytes().to_vec());
packet.append(&mut args[1].to_be_bytes().to_vec()); packet.append(&mut command.args[1].to_be_bytes().to_vec());
packet.append(&mut data); packet.append(&mut command.data);
self.serial.set_timeout(timeout)?; self.serial.set_timeout(timeout)?;
self.serial.write_all(&packet)?; self.serial.write_all(&packet)?;
@ -140,7 +142,7 @@ impl SerialLink {
Ok(None) 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) self.serial_send_command(command, COMMAND_TIMEOUT)
} }
@ -153,13 +155,13 @@ impl SerialLink {
} }
impl Link for SerialLink { impl Link for SerialLink {
fn execute_command(&mut self, command: &Command) -> Result<Vec<u8>, Error> { fn execute_command(&mut self, command: &mut Command) -> Result<Vec<u8>, Error> {
self.execute_command_raw(command, COMMAND_TIMEOUT, false, false) self.execute_command_raw(command, COMMAND_TIMEOUT, false, false)
} }
fn execute_command_raw( fn execute_command_raw(
&mut self, &mut self,
command: &Command, command: &mut Command,
timeout: Duration, timeout: Duration,
no_response: bool, no_response: bool,
ignore_error: bool, ignore_error: bool,
@ -169,7 +171,9 @@ impl Link for SerialLink {
return Ok(vec![]); return Ok(vec![]);
} }
let response = self.receive_response(timeout)?; 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 { if !ignore_error && response.error {
return Err(Error::new("Command 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<Vec<Device>, Error> { pub fn list_serial_devices() -> Result<Vec<Device>, Error> {
const SC64_VID: u16 = 0x0403; const SC64_VID: u16 = 0x0403;
const SC64_PID: u16 = 0x6014; const SC64_PID: u16 = 0x6014;
@ -219,9 +216,7 @@ pub fn list_serial_devices() -> Result<Vec<Device>, Error> {
pub fn new_serial(port: &str) -> Result<Box<dyn Link>, Error> { pub fn new_serial(port: &str) -> Result<Box<dyn Link>, Error> {
let mut link = SerialLink { let mut link = SerialLink {
serial: serialport::new(port, 115_200) serial: serialport::new(port, 115_200).open()?,
.timeout(COMMAND_TIMEOUT)
.open()?,
packets: VecDeque::new(), packets: VecDeque::new(),
}; };

View File

@ -4,54 +4,57 @@ mod link;
mod types; mod types;
mod utils; mod utils;
use crate::sc64::cic::IPL3_OFFSET; pub use self::{
error::Error,
use self::cic::{calculate_ipl3_checksum, guess_ipl3_seed, IPL3_LENGTH}; link::list_serial_devices,
pub use self::link::list_serial_devices; types::{
pub use self::types::{ BootMode, DataPacket, DdDiskState, DdDriveType, DdMode, DebugPacket, DiskPacket, SaveType,
BootMode, DataPacket, DdDiskState, DdDriveType, DdMode, DebugPacket, DiskPacket, TvType,
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,
}, },
}; };
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}; use chrono::{DateTime, Local};
pub use error::Error; use std::{
use std::io::{Read, Seek}; io::{Read, Seek},
use std::time::Instant; time::Instant,
use std::{cmp::min, time::Duration}; {cmp::min, time::Duration},
};
pub struct SC64 { pub struct SC64 {
link: Box<dyn Link>, link: Box<dyn Link>,
} }
#[derive(Debug)]
pub struct DeviceState { pub struct DeviceState {
pub bootloader_switch: bool, pub bootloader_switch: Switch,
pub rom_write_enable: bool, pub rom_write_enable: Switch,
pub rom_shadow_enable: bool, pub rom_shadow_enable: Switch,
pub dd_mode: DdMode, pub dd_mode: DdMode,
pub isv_address: u32, pub isv_address: u32,
pub boot_mode: BootMode, pub boot_mode: BootMode,
pub save_type: SaveType, pub save_type: SaveType,
pub cic_seed: CicSeed, pub cic_seed: CicSeed,
pub tv_type: TvType, pub tv_type: TvType,
pub dd_sd_enable: bool, pub dd_sd_enable: Switch,
pub dd_drive_type: DdDriveType, pub dd_drive_type: DdDriveType,
pub dd_disk_state: DdDiskState, pub dd_disk_state: DdDiskState,
pub button_state: bool, pub button_state: ButtonState,
pub button_mode: ButtonMode, pub button_mode: ButtonMode,
pub rom_extended_enable: bool, pub rom_extended_enable: Switch,
pub led_enable: bool, pub led_enable: Switch,
pub datetime: DateTime<Local>, pub datetime: DateTime<Local>,
} }
const SC64_V2_IDENTIFIER: &[u8; 4] = b"SCv2";
const SUPPORTED_MAJOR_VERSION: u16 = 2; const SUPPORTED_MAJOR_VERSION: u16 = 2;
const SUPPORTED_MINOR_VERSION: u16 = 12; const SUPPORTED_MINOR_VERSION: u16 = 12;
@ -91,9 +94,11 @@ const ISV_BUFFER_LENGTH: usize = 64 * 1024;
pub const MEMORY_LENGTH: usize = 0x0500_2980; pub const MEMORY_LENGTH: usize = 0x0500_2980;
const MEMORY_WRITE_CHUNK_SIZE: usize = 1 * 1024 * 1024;
impl SC64 { impl SC64 {
fn command_identifier_get(&mut self) -> Result<Vec<u8>, Error> { fn command_identifier_get(&mut self) -> Result<Vec<u8>, Error> {
let identifier = self.link.execute_command(&Command { let identifier = self.link.execute_command(&mut Command {
id: b'v', id: b'v',
args: [0, 0], args: [0, 0],
data: vec![], data: vec![],
@ -102,7 +107,7 @@ impl SC64 {
} }
fn command_version_get(&mut self) -> Result<(u16, u16), Error> { 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', id: b'V',
args: [0, 0], args: [0, 0],
data: vec![], data: vec![],
@ -113,7 +118,7 @@ impl SC64 {
} }
fn command_state_reset(&mut self) -> Result<(), Error> { fn command_state_reset(&mut self) -> Result<(), Error> {
self.link.execute_command(&Command { self.link.execute_command(&mut Command {
id: b'R', id: b'R',
args: [0, 0], args: [0, 0],
data: vec![], data: vec![],
@ -130,7 +135,7 @@ impl SC64 {
let mut params: Vec<u8> = vec![]; let mut params: Vec<u8> = vec![];
params.append(&mut [(disable as u8) << 0, seed].to_vec()); params.append(&mut [(disable as u8) << 0, seed].to_vec());
params.append(&mut checksum.to_vec()); params.append(&mut checksum.to_vec());
self.link.execute_command(&Command { self.link.execute_command(&mut Command {
id: b'B', id: b'B',
args: args_from_vec(&params[0..8])?, args: args_from_vec(&params[0..8])?,
data: vec![], data: vec![],
@ -139,7 +144,7 @@ impl SC64 {
} }
fn command_config_get(&mut self, config_id: ConfigId) -> Result<Config, Error> { fn command_config_get(&mut self, config_id: ConfigId) -> Result<Config, Error> {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&mut Command {
id: b'c', id: b'c',
args: [config_id.into(), 0], args: [config_id.into(), 0],
data: vec![], data: vec![],
@ -149,7 +154,7 @@ impl SC64 {
} }
fn command_config_set(&mut self, config: Config) -> Result<(), Error> { fn command_config_set(&mut self, config: Config) -> Result<(), Error> {
self.link.execute_command(&Command { self.link.execute_command(&mut Command {
id: b'C', id: b'C',
args: config.into(), args: config.into(),
data: vec![], data: vec![],
@ -158,7 +163,7 @@ impl SC64 {
} }
fn command_setting_get(&mut self, setting_id: SettingId) -> Result<Setting, Error> { fn command_setting_get(&mut self, setting_id: SettingId) -> Result<Setting, Error> {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&mut Command {
id: b'a', id: b'a',
args: [setting_id.into(), 0], args: [setting_id.into(), 0],
data: vec![], data: vec![],
@ -168,7 +173,7 @@ impl SC64 {
} }
fn command_setting_set(&mut self, setting: Setting) -> Result<(), Error> { fn command_setting_set(&mut self, setting: Setting) -> Result<(), Error> {
self.link.execute_command(&Command { self.link.execute_command(&mut Command {
id: b'A', id: b'A',
args: setting.into(), args: setting.into(),
data: vec![], data: vec![],
@ -177,7 +182,7 @@ impl SC64 {
} }
fn command_time_get(&mut self) -> Result<DateTime<Local>, Error> { fn command_time_get(&mut self) -> Result<DateTime<Local>, Error> {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&mut Command {
id: b't', id: b't',
args: [0, 0], args: [0, 0],
data: vec![], data: vec![],
@ -186,7 +191,7 @@ impl SC64 {
} }
fn command_time_set(&mut self, datetime: DateTime<Local>) -> Result<(), Error> { fn command_time_set(&mut self, datetime: DateTime<Local>) -> Result<(), Error> {
self.link.execute_command(&Command { self.link.execute_command(&mut Command {
id: b'T', id: b'T',
args: args_from_vec(&vec_from_datetime(datetime)?[0..8])?, args: args_from_vec(&vec_from_datetime(datetime)?[0..8])?,
data: vec![], data: vec![],
@ -195,7 +200,7 @@ impl SC64 {
} }
fn command_memory_read(&mut self, address: u32, length: usize) -> Result<Vec<u8>, Error> { fn command_memory_read(&mut self, address: u32, length: usize) -> Result<Vec<u8>, Error> {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&mut Command {
id: b'm', id: b'm',
args: [address, length as u32], args: [address, length as u32],
data: vec![], data: vec![],
@ -204,7 +209,7 @@ impl SC64 {
} }
fn command_memory_write(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { 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', id: b'M',
args: [address, data.len() as u32], args: [address, data.len() as u32],
data: data.to_vec(), data: data.to_vec(),
@ -214,7 +219,7 @@ impl SC64 {
fn command_usb_write(&mut self, datatype: u8, data: &[u8]) -> Result<(), Error> { fn command_usb_write(&mut self, datatype: u8, data: &[u8]) -> Result<(), Error> {
self.link.execute_command_raw( self.link.execute_command_raw(
&Command { &mut Command {
id: b'U', id: b'U',
args: [datatype as u32, data.len() as u32], args: [datatype as u32, data.len() as u32],
data: data.to_vec(), data: data.to_vec(),
@ -227,7 +232,7 @@ impl SC64 {
} }
fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> { 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', id: b'D',
args: [error as u32, 0], args: [error as u32, 0],
data: vec![], data: vec![],
@ -236,7 +241,7 @@ impl SC64 {
} }
fn command_flash_wait_busy(&mut self, wait: bool) -> Result<u32, Error> { fn command_flash_wait_busy(&mut self, wait: bool) -> Result<u32, Error> {
let erase_block_size = self.link.execute_command(&Command { let erase_block_size = self.link.execute_command(&mut Command {
id: b'p', id: b'p',
args: [wait as u32, 0], args: [wait as u32, 0],
data: vec![], data: vec![],
@ -245,7 +250,7 @@ impl SC64 {
} }
fn command_flash_erase_block(&mut self, address: u32) -> Result<(), Error> { 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', id: b'P',
args: [address, 0], args: [address, 0],
data: vec![], data: vec![],
@ -255,7 +260,7 @@ impl SC64 {
fn command_firmware_backup(&mut self, address: u32) -> Result<(FirmwareStatus, u32), Error> { fn command_firmware_backup(&mut self, address: u32) -> Result<(FirmwareStatus, u32), Error> {
let data = self.link.execute_command_raw( let data = self.link.execute_command_raw(
&Command { &mut Command {
id: b'f', id: b'f',
args: [address, 0], args: [address, 0],
data: vec![], data: vec![],
@ -275,7 +280,7 @@ impl SC64 {
length: usize, length: usize,
) -> Result<FirmwareStatus, Error> { ) -> Result<FirmwareStatus, Error> {
let data = self.link.execute_command_raw( let data = self.link.execute_command_raw(
&Command { &mut Command {
id: b'F', id: b'F',
args: [address, length as u32], args: [address, length as u32],
data: vec![], data: vec![],
@ -286,91 +291,34 @@ impl SC64 {
)?; )?;
Ok(FirmwareStatus::try_from(utils::u32_from_vec(&data[0..4])?)?) 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 { impl SC64 {
pub fn check_firmware_version(&mut self) -> Result<(u16, u16), Error> { pub fn upload_rom<T: Read + Seek>(
let (major, minor) = self &mut self,
.command_version_get() reader: &mut T,
.map_err(|_| Error::new("Outdated SC64 firmware version, please update firmware"))?; length: usize,
if major != SUPPORTED_MAJOR_VERSION || minor < SUPPORTED_MINOR_VERSION { no_shadow: bool,
return Err(Error::new( ) -> Result<(), Error> {
"Unsupported SC64 firmware version, please update firmware", if length > MAX_ROM_LENGTH {
)); return Err(Error::new("ROM length too big"));
}
Ok((major, minor))
} }
pub fn reset_state(&mut self) -> Result<(), Error> { let mut pi_config = vec![0u8; 4];
self.command_state_reset()?; reader.read(&mut pi_config)?;
Ok(()) reader.rewind()?;
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]| {
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]| {
b.chunks_exact_mut(4).for_each(|c| { b.chunks_exact_mut(4).for_each(|c| {
c.swap(0, 3); c.swap(0, 3);
c.swap(1, 2) c.swap(1, 2)
}) })
}, },
_ => |_b: &mut [u8]| {}, _ => |_: &mut [u8]| {},
}; };
let rom_shadow_enabled = !no_shadow && length > (SDRAM_LENGTH - ROM_SHADOW_LENGTH); let rom_shadow_enabled = !no_shadow && length > (SDRAM_LENGTH - ROM_SHADOW_LENGTH);
@ -382,79 +330,62 @@ impl SC64 {
min(length, SDRAM_LENGTH) 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()) { self.command_config_set(Config::RomShadowEnable(rom_shadow_enabled.into()))?;
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))?;
if rom_shadow_enabled { if rom_shadow_enabled {
let mut buffer = vec![0u8; ROM_SHADOW_LENGTH]; let rom_shadow_length = min(length - sdram_length, ROM_SHADOW_LENGTH);
let chunk = file.read(&mut buffer)?; self.flash_program(
endian_swapper(&mut buffer); reader,
self.flash_program_shadow(&buffer[0..chunk])?; 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 { if rom_extended_enabled {
let mut buffer = vec![0u8; ROM_EXTENDED_LENGTH]; let rom_extended_length = min(length - SDRAM_LENGTH, ROM_EXTENDED_LENGTH);
let chunk = file.read(&mut buffer)?; self.flash_program(
endian_swapper(&mut buffer); reader,
self.flash_program_extended(&buffer[0..chunk])?; ROM_EXTENDED_ADDRESS,
rom_extended_length,
Some(endian_swapper),
)?;
} }
Ok(()) Ok(())
} }
pub fn upload_ddipl(&mut self, path: &str) -> Result<(), Error> { pub fn upload_ddipl<T: Read>(&mut self, reader: &mut T, length: usize) -> Result<(), Error> {
let (mut file, length) = file_open_and_check_length(path, DDIPL_LENGTH)?; if length > DDIPL_LENGTH {
return Err(Error::new("DDIPL length too big"));
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> { self.memory_write_chunked(reader, DDIPL_ADDRESS, length, None)
}
pub fn upload_save<T: Read>(&mut self, reader: &mut T, length: usize) -> Result<(), Error> {
let save_type = get_config!(self, SaveType)?; let save_type = get_config!(self, SaveType)?;
if matches!(save_type, SaveType::None) { let (address, save_length) = match save_type {
SaveType::None => {
return Err(Error::new("No save type is enabled")); return Err(Error::new("No save type is enabled"));
} }
SaveType::Eeprom4k => (EEPROM_ADDRESS, EEPROM_4K_LENGTH),
let address = match save_type { SaveType::Eeprom16k => (EEPROM_ADDRESS, EEPROM_16K_LENGTH),
SaveType::None => 0, SaveType::Sram => (SAVE_ADDRESS, SRAM_LENGTH),
SaveType::Eeprom4k => EEPROM_ADDRESS, SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
SaveType::Eeprom16k => EEPROM_ADDRESS, SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
SaveType::Sram => SAVE_ADDRESS,
SaveType::SramBanked => SAVE_ADDRESS,
SaveType::Flashram => SAVE_ADDRESS,
}; };
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,
};
let (mut file, length) = file_open_and_check_length(path, save_length)?;
if length != save_length { if length != save_length {
return Err(Error::new( return Err(Error::new(
"Save file size did not match currently enabled save type", "Save file size did not match currently enabled save type",
)); ));
} }
let mut buffer = vec![0u8; length]; self.memory_write_chunked(reader, address, save_length, None)
file.read(&mut buffer)?;
self.command_memory_write(address, &buffer)
} }
pub fn dump_memory(&mut self, address: u32, length: usize) -> Result<Vec<u8>, Error> { pub fn dump_memory(&mut self, address: u32, length: usize) -> Result<Vec<u8>, Error> {
@ -498,7 +429,7 @@ impl SC64 {
} }
pub fn set_led_blink(&mut self, enabled: bool) -> Result<(), Error> { 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<DeviceState, Error> { pub fn get_device_state(&mut self) -> Result<DeviceState, Error> {
@ -529,7 +460,7 @@ impl SC64 {
drive_type: DdDriveType, drive_type: DdDriveType,
) -> Result<(), Error> { ) -> Result<(), Error> {
self.command_config_set(Config::DdMode(dd_mode))?; 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::DdDriveType(drive_type))?;
self.command_config_set(Config::DdDiskState(DdDiskState::Ejected))?; self.command_config_set(Config::DdDiskState(DdDiskState::Ejected))?;
Ok(()) Ok(())
@ -539,22 +470,22 @@ impl SC64 {
self.command_config_set(Config::DdDiskState(disk_state)) self.command_config_set(Config::DdDiskState(disk_state))
} }
pub fn configure_isviewer64(&mut self, offset: Option<u32>) -> Result<(), Error> { pub fn configure_is_viewer_64(&mut self, offset: Option<u32>) -> Result<(), Error> {
if let Some(off) = offset { if let Some(offset) = offset {
if get_config!(self, RomShadowEnable)? { if get_config!(self, RomShadowEnable)?.into() {
if off > (SAVE_ADDRESS - ISV_BUFFER_LENGTH as u32) { if offset > (SAVE_ADDRESS - ISV_BUFFER_LENGTH as u32) {
return Err(Error::new( return Err(Error::new(
format!( 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(), .as_str(),
)); ));
} }
} }
self.command_config_set(Config::RomWriteEnable(true))?; self.command_config_set(Config::RomWriteEnable(Switch::On))?;
self.command_config_set(Config::IsvAddress(off))?; self.command_config_set(Config::IsvAddress(offset))?;
} else { } else {
self.command_config_set(Config::RomWriteEnable(false))?; self.command_config_set(Config::RomWriteEnable(Switch::Off))?;
self.command_config_set(Config::IsvAddress(0))?; self.command_config_set(Config::IsvAddress(0))?;
} }
Ok(()) Ok(())
@ -586,12 +517,29 @@ impl SC64 {
self.command_usb_write(debug_packet.datatype, &debug_packet.data) 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<Vec<u8>, Error> { pub fn backup_firmware(&mut self) -> Result<Vec<u8>, Error> {
self.command_state_reset()?; self.command_state_reset()?;
let (status, length) = self.command_firmware_backup(FIRMWARE_ADDRESS)?; let (status, length) = self.command_firmware_backup(FIRMWARE_ADDRESS)?;
if !matches!(status, FirmwareStatus::Ok) { if !matches!(status, FirmwareStatus::Ok) {
return Err(Error::new( 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) 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())?; let status = self.command_firmware_update(FIRMWARE_ADDRESS, data.len())?;
if !matches!(status, FirmwareStatus::Ok) { if !matches!(status, FirmwareStatus::Ok) {
return Err(Error::new( return Err(Error::new(
format!("Firmware update verify error: {:?}", status).as_str(), format!("Firmware update verify error: {}", status).as_str(),
)); ));
} }
let timeout = Instant::now(); let timeout = Instant::now();
@ -619,7 +567,7 @@ impl SC64 {
UpdateStatus::Err => { UpdateStatus::Err => {
return Err(Error::new( return Err(Error::new(
format!( format!(
"Firmware update error on step {:?}, device is most likely bricked", "Firmware update error on step {}, device is most likely bricked",
last_update_status last_update_status
) )
.as_str(), .as_str(),
@ -641,6 +589,52 @@ impl SC64 {
std::thread::sleep(Duration::from_millis(1)); 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<fn(&mut [u8])>,
) -> Result<(), Error> {
let mut data: Vec<u8> = 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<fn(&mut [u8])>,
) -> 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<String>) -> Result<SC64, Error> { pub fn new(sn: Option<String>) -> Result<SC64, Error> {
@ -664,7 +658,7 @@ pub fn new(sn: Option<String>) -> Result<SC64, Error> {
.command_identifier_get() .command_identifier_get()
.map_err(|_| Error::new("Couldn't get SC64 device identifier"))?; .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")); return Err(Error::new("Unknown identifier received, not a SC64 device"));
} }

View File

@ -1,5 +1,6 @@
use super::{link::Packet, utils::u32_from_vec, Error}; use super::{link::Packet, utils::u32_from_vec, Error};
use encoding_rs::EUC_JP; use encoding_rs::EUC_JP;
use std::fmt::Display;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum ConfigId { pub enum ConfigId {
@ -21,21 +22,21 @@ pub enum ConfigId {
} }
pub enum Config { pub enum Config {
BootloaderSwitch(bool), BootloaderSwitch(Switch),
RomWriteEnable(bool), RomWriteEnable(Switch),
RomShadowEnable(bool), RomShadowEnable(Switch),
DdMode(DdMode), DdMode(DdMode),
IsvAddress(u32), IsvAddress(u32),
BootMode(BootMode), BootMode(BootMode),
SaveType(SaveType), SaveType(SaveType),
CicSeed(CicSeed), CicSeed(CicSeed),
TvType(TvType), TvType(TvType),
DdSdEnable(bool), DdSdEnable(Switch),
DdDriveType(DdDriveType), DdDriveType(DdDriveType),
DdDiskState(DdDiskState), DdDiskState(DdDiskState),
ButtonState(bool), ButtonState(ButtonState),
ButtonMode(ButtonMode), ButtonMode(ButtonMode),
RomExtendedEnable(bool), RomExtendedEnable(Switch),
} }
impl From<ConfigId> for u32 { impl From<ConfigId> for u32 {
@ -65,21 +66,21 @@ impl TryFrom<(ConfigId, u32)> for Config {
fn try_from(value: (ConfigId, u32)) -> Result<Self, Self::Error> { fn try_from(value: (ConfigId, u32)) -> Result<Self, Self::Error> {
let (id, config) = value; let (id, config) = value;
Ok(match id { Ok(match id {
ConfigId::BootloaderSwitch => Config::BootloaderSwitch(config != 0), ConfigId::BootloaderSwitch => Self::BootloaderSwitch(config.try_into()?),
ConfigId::RomWriteEnable => Config::RomWriteEnable(config != 0), ConfigId::RomWriteEnable => Self::RomWriteEnable(config.try_into()?),
ConfigId::RomShadowEnable => Config::RomShadowEnable(config != 0), ConfigId::RomShadowEnable => Self::RomShadowEnable(config.try_into()?),
ConfigId::DdMode => Config::DdMode(config.try_into()?), ConfigId::DdMode => Self::DdMode(config.try_into()?),
ConfigId::IsvAddress => Config::IsvAddress(config), ConfigId::IsvAddress => Self::IsvAddress(config),
ConfigId::BootMode => Config::BootMode(config.try_into()?), ConfigId::BootMode => Self::BootMode(config.try_into()?),
ConfigId::SaveType => Config::SaveType(config.try_into()?), ConfigId::SaveType => Self::SaveType(config.try_into()?),
ConfigId::CicSeed => Config::CicSeed(config.try_into()?), ConfigId::CicSeed => Self::CicSeed(config.try_into()?),
ConfigId::TvType => Config::TvType(config.try_into()?), ConfigId::TvType => Self::TvType(config.try_into()?),
ConfigId::DdSdEnable => Config::DdSdEnable(config != 0), ConfigId::DdSdEnable => Self::DdSdEnable(config.try_into()?),
ConfigId::DdDriveType => Config::DdDriveType(config.try_into()?), ConfigId::DdDriveType => Self::DdDriveType(config.try_into()?),
ConfigId::DdDiskState => Config::DdDiskState(config.try_into()?), ConfigId::DdDiskState => Self::DdDiskState(config.try_into()?),
ConfigId::ButtonState => Config::ButtonState(config != 0), ConfigId::ButtonState => Self::ButtonState(config.try_into()?),
ConfigId::ButtonMode => Config::ButtonMode(config.try_into()?), ConfigId::ButtonMode => Self::ButtonMode(config.try_into()?),
ConfigId::RomExtendedEnable => Config::RomExtendedEnable(config != 0), ConfigId::RomExtendedEnable => Self::RomExtendedEnable(config.try_into()?),
}) })
} }
} }
@ -106,7 +107,58 @@ impl From<Config> 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<u32> for Switch {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0 => Self::Off,
_ => Self::On,
})
}
}
impl From<Switch> for u32 {
fn from(value: Switch) -> Self {
match value {
Switch::Off => 0,
Switch::On => 1,
}
}
}
impl From<bool> for Switch {
fn from(value: bool) -> Self {
match value {
false => Self::Off,
true => Self::On,
}
}
}
impl From<Switch> for bool {
fn from(value: Switch) -> Self {
match value {
Switch::Off => false,
Switch::On => true,
}
}
}
pub enum DdMode { pub enum DdMode {
None, None,
Regs, Regs,
@ -114,14 +166,25 @@ pub enum DdMode {
Full, 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<u32> for DdMode { impl TryFrom<u32> for DdMode {
type Error = Error; type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> { fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value { Ok(match value {
0 => DdMode::None, 0 => Self::None,
1 => DdMode::Regs, 1 => Self::Regs,
2 => DdMode::DdIpl, 2 => Self::DdIpl,
3 => DdMode::Full, 3 => Self::Full,
_ => return Err(Error::new("Unknown 64DD mode code")), _ => return Err(Error::new("Unknown 64DD mode code")),
}) })
} }
@ -138,7 +201,6 @@ impl From<DdMode> for u32 {
} }
} }
#[derive(Copy, Clone, Debug)]
pub enum BootMode { pub enum BootMode {
Menu, Menu,
Rom, Rom,
@ -147,15 +209,27 @@ pub enum BootMode {
DirectDdIpl, 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<u32> for BootMode { impl TryFrom<u32> for BootMode {
type Error = Error; type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> { fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value { Ok(match value {
0 => BootMode::Menu, 0 => Self::Menu,
1 => BootMode::Rom, 1 => Self::Rom,
2 => BootMode::DdIpl, 2 => Self::DdIpl,
3 => BootMode::DirectRom, 3 => Self::DirectRom,
4 => BootMode::DirectDdIpl, 4 => Self::DirectDdIpl,
_ => return Err(Error::new("Unknown boot mode code")), _ => return Err(Error::new("Unknown boot mode code")),
}) })
} }
@ -173,7 +247,6 @@ impl From<BootMode> for u32 {
} }
} }
#[derive(Debug)]
pub enum SaveType { pub enum SaveType {
None, None,
Eeprom4k, Eeprom4k,
@ -183,16 +256,29 @@ pub enum SaveType {
SramBanked, 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<u32> for SaveType { impl TryFrom<u32> for SaveType {
type Error = Error; type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> { fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value { Ok(match value {
0 => SaveType::None, 0 => Self::None,
1 => SaveType::Eeprom4k, 1 => Self::Eeprom4k,
2 => SaveType::Eeprom16k, 2 => Self::Eeprom16k,
3 => SaveType::Sram, 3 => Self::Sram,
4 => SaveType::Flashram, 4 => Self::Flashram,
5 => SaveType::SramBanked, 5 => Self::SramBanked,
_ => return Err(Error::new("Unknown save type code")), _ => return Err(Error::new("Unknown save type code")),
}) })
} }
@ -211,19 +297,28 @@ impl From<SaveType> for u32 {
} }
} }
#[derive(Debug)]
pub enum CicSeed { pub enum CicSeed {
Seed(u8), Seed(u8),
Auto, 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<u32> for CicSeed { impl TryFrom<u32> for CicSeed {
type Error = Error; type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> { fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(if value <= 0xFF { Ok(if value <= 0xFF {
CicSeed::Seed(value as u8) Self::Seed(value as u8)
} else if value == 0xFFFF { } else if value == 0xFFFF {
CicSeed::Auto Self::Auto
} else { } else {
return Err(Error::new("Unknown CIC seed code")); return Err(Error::new("Unknown CIC seed code"));
}) })
@ -239,7 +334,6 @@ impl From<CicSeed> for u32 {
} }
} }
#[derive(Debug)]
pub enum TvType { pub enum TvType {
PAL, PAL,
NTSC, NTSC,
@ -247,14 +341,25 @@ pub enum TvType {
Auto, 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<u32> for TvType { impl TryFrom<u32> for TvType {
type Error = Error; type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> { fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value { Ok(match value {
0 => TvType::PAL, 0 => Self::PAL,
1 => TvType::NTSC, 1 => Self::NTSC,
2 => TvType::MPAL, 2 => Self::MPAL,
3 => TvType::Auto, 3 => Self::Auto,
_ => return Err(Error::new("Unknown TV type code")), _ => return Err(Error::new("Unknown TV type code")),
}) })
} }
@ -271,18 +376,26 @@ impl From<TvType> for u32 {
} }
} }
#[derive(Debug)]
pub enum DdDriveType { pub enum DdDriveType {
Retail, Retail,
Development, 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<u32> for DdDriveType { impl TryFrom<u32> for DdDriveType {
type Error = Error; type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> { fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value { Ok(match value {
0 => DdDriveType::Retail, 0 => Self::Retail,
1 => DdDriveType::Development, 1 => Self::Development,
_ => return Err(Error::new("Unknown 64DD drive type code")), _ => return Err(Error::new("Unknown 64DD drive type code")),
}) })
} }
@ -297,20 +410,29 @@ impl From<DdDriveType> for u32 {
} }
} }
#[derive(Debug)]
pub enum DdDiskState { pub enum DdDiskState {
Ejected, Ejected,
Inserted, Inserted,
Changed, 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<u32> for DdDiskState { impl TryFrom<u32> for DdDiskState {
type Error = Error; type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> { fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value { Ok(match value {
0 => DdDiskState::Ejected, 0 => Self::Ejected,
1 => DdDiskState::Inserted, 1 => Self::Inserted,
2 => DdDiskState::Changed, 2 => Self::Changed,
_ => return Err(Error::new("Unknown 64DD disk state code")), _ => return Err(Error::new("Unknown 64DD disk state code")),
}) })
} }
@ -326,7 +448,57 @@ impl From<DdDiskState> 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<u32> for ButtonState {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0 => Self::NotPressed,
_ => Self::Pressed,
})
}
}
impl From<ButtonState> for u32 {
fn from(value: ButtonState) -> Self {
match value {
ButtonState::NotPressed => 0,
ButtonState::Pressed => 1,
}
}
}
impl From<bool> for ButtonState {
fn from(value: bool) -> Self {
match value {
false => Self::NotPressed,
true => Self::Pressed,
}
}
}
impl From<ButtonState> for bool {
fn from(value: ButtonState) -> Self {
match value {
ButtonState::NotPressed => false,
ButtonState::Pressed => true,
}
}
}
pub enum ButtonMode { pub enum ButtonMode {
None, None,
N64Irq, N64Irq,
@ -334,14 +506,25 @@ pub enum ButtonMode {
DdDiskSwap, 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<u32> for ButtonMode { impl TryFrom<u32> for ButtonMode {
type Error = Error; type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> { fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value { Ok(match value {
0 => ButtonMode::None, 0 => Self::None,
1 => ButtonMode::N64Irq, 1 => Self::N64Irq,
2 => ButtonMode::UsbPacket, 2 => Self::UsbPacket,
3 => ButtonMode::DdDiskSwap, 3 => Self::DdDiskSwap,
_ => return Err(Error::new("Unknown button mode code")), _ => return Err(Error::new("Unknown button mode code")),
}) })
} }
@ -364,7 +547,7 @@ pub enum SettingId {
} }
pub enum Setting { pub enum Setting {
LedEnable(bool), LedEnable(Switch),
} }
impl From<SettingId> for u32 { impl From<SettingId> for u32 {
@ -380,7 +563,7 @@ impl TryFrom<(SettingId, u32)> for Setting {
fn try_from(value: (SettingId, u32)) -> Result<Self, Self::Error> { fn try_from(value: (SettingId, u32)) -> Result<Self, Self::Error> {
let (id, setting) = value; let (id, setting) = value;
Ok(match id { 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 { pub enum FirmwareStatus {
Ok, Ok,
ErrToken, ErrToken,
@ -490,22 +672,34 @@ pub enum FirmwareStatus {
ErrRead, 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<u32> for FirmwareStatus { impl TryFrom<u32> for FirmwareStatus {
type Error = Error; type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> { fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value { Ok(match value {
0 => FirmwareStatus::Ok, 0 => Self::Ok,
1 => FirmwareStatus::ErrToken, 1 => Self::ErrToken,
2 => FirmwareStatus::ErrChecksum, 2 => Self::ErrChecksum,
3 => FirmwareStatus::ErrSize, 3 => Self::ErrSize,
4 => FirmwareStatus::ErrUnknownChunk, 4 => Self::ErrUnknownChunk,
5 => FirmwareStatus::ErrRead, 5 => Self::ErrRead,
_ => return Err(Error::new("Unknown firmware status code")), _ => return Err(Error::new("Unknown firmware status code")),
}) })
} }
} }
#[derive(Debug)]
pub enum UpdateStatus { pub enum UpdateStatus {
MCU, MCU,
FPGA, FPGA,
@ -514,15 +708,27 @@ pub enum UpdateStatus {
Err, 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<u32> for UpdateStatus { impl TryFrom<u32> for UpdateStatus {
type Error = Error; type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> { fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value { Ok(match value {
1 => UpdateStatus::MCU, 1 => Self::MCU,
2 => UpdateStatus::FPGA, 2 => Self::FPGA,
3 => UpdateStatus::Bootloader, 3 => Self::Bootloader,
0x80 => UpdateStatus::Done, 0x80 => Self::Done,
0xFF => UpdateStatus::Err, 0xFF => Self::Err,
_ => return Err(Error::new("Unknown update status code")), _ => return Err(Error::new("Unknown update status code")),
}) })
} }
@ -540,7 +746,8 @@ macro_rules! get_config {
macro_rules! get_setting { macro_rules! get_setting {
($sc64:ident, $setting:ident) => {{ ($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)? { if let Setting::$setting(value) = $sc64.command_setting_get(SettingId::$setting)? {
Ok(value) Ok(value)
} else { } else {

View File

@ -61,15 +61,3 @@ pub fn vec_from_datetime(datetime: DateTime<Local>) -> Result<Vec<u8>, Error> {
let day = bcd_from_u8(datetime.day() as u8); let day = bcd_from_u8(datetime.day() as u8);
Ok(vec![weekday, hour, minute, second, 0, year, month, day]) 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))
}