alpha rust deployer

This commit is contained in:
Mateusz Faderewski 2023-03-02 00:48:01 +01:00
parent a3b2819803
commit 06f1799a21
14 changed files with 3216 additions and 83 deletions

View File

@ -13,65 +13,68 @@ on:
workflow_dispatch:
jobs:
build-fw-hw-sw:
runs-on: ubuntu-latest
# build-fw-hw-sw:
# runs-on: ubuntu-latest
steps:
- name: Download SummerCart64 repository
uses: actions/checkout@v3
# steps:
# - name: Download SummerCart64 repository
# uses: actions/checkout@v3
- name: Set SC64 version
uses: frabert/replace-string-action@v2
id: sc64version
with:
pattern: '\/'
string: '-${{ github.ref_name }}'
replace-with: '-'
# - name: Set SC64 version
# uses: frabert/replace-string-action@v2
# id: sc64version
# with:
# pattern: '\/'
# string: '-${{ github.ref_name }}'
# replace-with: '-'
- name: Build everything
run: ./docker_build.sh release --force-clean
env:
SC64_VERSION: ${{ steps.sc64version.outputs.replaced }}
# - name: Build everything
# run: ./docker_build.sh release --force-clean
# env:
# SC64_VERSION: ${{ steps.sc64version.outputs.replaced }}
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: sc64-pkg${{ steps.sc64version.outputs.replaced }}
path: |
sc64-extra${{ steps.sc64version.outputs.replaced }}.zip
sc64-firmware${{ steps.sc64version.outputs.replaced }}.bin
# - name: Upload artifact
# uses: actions/upload-artifact@v3
# with:
# name: sc64-pkg${{ steps.sc64version.outputs.replaced }}
# path: |
# sc64-extra${{ steps.sc64version.outputs.replaced }}.zip
# sc64-firmware${{ steps.sc64version.outputs.replaced }}.bin
- name: Upload release assets
if: github.event_name == 'release' && github.event.action == 'created'
uses: softprops/action-gh-release@v0.1.15
with:
files: |
sc64-extra${{ steps.sc64version.outputs.replaced }}.zip
sc64-firmware${{ steps.sc64version.outputs.replaced }}.bin
# - name: Upload release assets
# if: github.event_name == 'release' && github.event.action == 'created'
# uses: softprops/action-gh-release@v0.1.15
# with:
# files: |
# sc64-extra${{ steps.sc64version.outputs.replaced }}.zip
# sc64-firmware${{ steps.sc64version.outputs.replaced }}.bin
build-sc64-py:
build-deployer:
strategy:
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
include:
- os: windows-latest
pyinstaller-build-options: --target-arch=64bit
pyinstaller-options: --onefile --console --icon ../../assets/sc64_logo_256_256.png
package-name: sc64-windows
package-options: -c -a -f
package-extension: zip
# pyinstaller-build-options: --target-arch=64bit
# pyinstaller-options: --onefile --console --icon ../../assets/sc64_logo_256_256.png
executable-name: sc64deployer.exe
package-name: sc64deployer-windows
# package-options: -c -a -f
# package-extension: zip
- os: ubuntu-latest
pyinstaller-options: --onefile
package-name: sc64-linux
package-options: -czf
package-extension: tar.gz
# pyinstaller-options: --onefile
executable-name: sc64deployer
package-name: sc64deployer-linux
# package-options: -czf
# package-extension: tar.gz
- os: macos-latest
pyinstaller-options: --onedir --console --icon ../../assets/sc64_logo_256_256.png
package-name: sc64-macos
package-options: -czf
package-extension: tgz
# pyinstaller-options: --onedir --console --icon ../../assets/sc64_logo_256_256.png
executable-name: sc64deployer
package-name: sc64deployer-macos
# package-options: -czf
# package-extension: tgz
runs-on: ${{ matrix.os }}
@ -87,53 +90,58 @@ jobs:
string: '-${{ github.ref_name }}'
replace-with: '-'
- name: Setup python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Build deployer
run: cargo b -r
working-directory: sw/deployer
- name: Download pyinstaller repository
uses: actions/checkout@v3
with:
repository: 'pyinstaller/pyinstaller'
ref: 'v5.8.0'
path: pyinstaller
# - name: Setup python
# uses: actions/setup-python@v4
# with:
# python-version: '3.11'
- name: Compile and install pyinstaller
run: |
pip3 uninstall pyinstaller
pip3 install wheel
pushd bootloader
python3 ./waf all ${{ matrix.pyinstaller-build-options }}
popd
pip3 install .
working-directory: pyinstaller
# - name: Download pyinstaller repository
# uses: actions/checkout@v3
# with:
# repository: 'pyinstaller/pyinstaller'
# ref: 'v5.8.0'
# path: pyinstaller
- name: Install sc64.py requirements
run: pip3 install -r requirements.txt
working-directory: sw/pc
# - name: Compile and install pyinstaller
# run: |
# pip3 uninstall pyinstaller
# pip3 install wheel
# pushd bootloader
# python3 ./waf all ${{ matrix.pyinstaller-build-options }}
# popd
# pip3 install .
# working-directory: pyinstaller
- name: Create sc64.py executable
run: python3 -m PyInstaller --clean ${{ matrix.pyinstaller-options }} sc64.py
working-directory: sw/pc
# - name: Install sc64.py requirements
# run: pip3 install -r requirements.txt
# working-directory: sw/pc
- name: Package executable
run: |
mkdir package
pushd dist
tar ${{ matrix.package-options }} ../package/${{ matrix.package-name }}${{ steps.sc64version.outputs.replaced }}.${{ matrix.package-extension }} *
popd
working-directory: sw/pc
# - name: Create sc64.py executable
# run: python3 -m PyInstaller --clean ${{ matrix.pyinstaller-options }} sc64.py
# working-directory: sw/pc
# - name: Package executable
# run: |
# mkdir package
# pushd dist
# tar ${{ matrix.package-options }} ../package/${{ matrix.package-name }}${{ steps.sc64version.outputs.replaced }}.${{ matrix.package-extension }} *
# popd
# working-directory: sw/pc
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.package-name }}${{ steps.sc64version.outputs.replaced }}
path: sw/pc/package/${{ matrix.package-name }}${{ steps.sc64version.outputs.replaced }}.${{ matrix.package-extension }}
path: sw/deployer/target/release/${{ matrix.executable-name }}
# path: sw/pc/package/${{ matrix.package-name }}${{ steps.sc64version.outputs.replaced }}.${{ matrix.package-extension }}
- name: Upload release assets
if: github.event_name == 'release' && github.event.action == 'created'
uses: softprops/action-gh-release@v0.1.15
with:
files: |
sw/pc/package/${{ matrix.package-name }}${{ steps.sc64version.outputs.replaced }}.${{ matrix.package-extension }}
# - name: Upload release assets
# if: github.event_name == 'release' && github.event.action == 'created'
# uses: softprops/action-gh-release@v0.1.15
# with:
# files: |
# sw/pc/package/${{ matrix.package-name }}${{ steps.sc64version.outputs.replaced }}.${{ matrix.package-extension }}

8
sw/deployer/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
/target
*.bin
*.eep
*.fla
*.n64
*.srm
*.v64
*.z64

803
sw/deployer/Cargo.lock generated Normal file
View File

@ -0,0 +1,803 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "CoreFoundation-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b"
dependencies = [
"libc",
"mach",
]
[[package]]
name = "IOKit-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a"
dependencies = [
"CoreFoundation-sys",
"libc",
"mach",
]
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"time",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "clap"
version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
dependencies = [
"bitflags",
"clap_derive",
"clap_lex",
"is-terminal",
"once_cell",
"strsim",
"termcolor",
]
[[package]]
name = "clap-num"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488557e97528174edaa2ee268b23a809e0c598213a4bbcb4f34575a46fda147e"
dependencies = [
"num-traits",
]
[[package]]
name = "clap_derive"
version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "colored"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
dependencies = [
"atty",
"lazy_static",
"winapi",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "ctrlc"
version = "3.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639"
dependencies = [
"nix",
"windows-sys",
]
[[package]]
name = "cxx"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf"
[[package]]
name = "cxxbridge-macro"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "encoding_rs"
version = "0.8.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
dependencies = [
"cfg-if",
]
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "iana-time-zone"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
dependencies = [
"cxx",
"cxx-build",
]
[[package]]
name = "io-lifetimes"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libudev"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0"
dependencies = [
"libc",
"libudev-sys",
]
[[package]]
name = "libudev-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "link-cplusplus"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [
"cc",
]
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "mach"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9"
dependencies = [
"libc",
]
[[package]]
name = "mach2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8"
dependencies = [
"libc",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "nix"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
dependencies = [
"bitflags",
"cfg-if",
"libc",
"static_assertions",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "os_str_bytes"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "panic-message"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384e52fd8fbd4cbe3c317e8216260c21a0f9134de108cea8a4dd4e7e152c472d"
[[package]]
name = "pkg-config"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rustix"
version = "0.36.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "sc64loader"
version = "2.12.2"
dependencies = [
"chrono",
"clap",
"clap-num",
"colored",
"crc32fast",
"ctrlc",
"encoding_rs",
"panic-message",
"serialport",
]
[[package]]
name = "scratch"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
[[package]]
name = "serialport"
version = "4.2.1-alpha.0"
source = "git+https://github.com/serialport/serialport-rs?branch=main#e1f46eef5af7df2430f0a595681243e46f721b2a"
dependencies = [
"CoreFoundation-sys",
"IOKit-sys",
"bitflags",
"cfg-if",
"libudev",
"mach2",
"nix",
"regex",
"winapi",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"

18
sw/deployer/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "sc64deployer"
version = "2.12.2"
edition = "2021"
authors = ["Polprzewodnikowy"]
description = "SC64 loader and control software"
documentation = "https://github.com/Polprzewodnikowy/SummerCart64"
[dependencies]
chrono = "0.4.23"
clap = { version = "4.1.6", features = ["derive"] }
clap-num = "1.0.2"
colored = "2.0.0"
crc32fast = "1.3.2"
ctrlc = "3.2.5"
encoding_rs = "0.8.32"
panic-message = "0.3.0"
serialport = { git = "https://github.com/serialport/serialport-rs", branch = "main" }

27
sw/deployer/src/debug.rs Normal file
View File

@ -0,0 +1,27 @@
use crate::sc64::DebugPacket;
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),
_ => {}
}
}
fn handle_datatype_text(data: &[u8]) {
if let Ok(message) = std::str::from_utf8(data) {
print!("{message}");
}
}
// fn handle_datatype_raw_binary(data: &[u8]) {}
// fn handle_datatype_header(data: &[u8]) {}
// fn handle_datatype_screenshot(data: &[u8]) {}
// fn handle_datatype_gdb(data: &[u8]) {}

524
sw/deployer/src/main.rs Normal file
View File

@ -0,0 +1,524 @@
mod debug;
mod n64;
mod sc64;
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};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
/// Use SC64 device matching provided serial number
#[arg(long)]
sn: Option<String>,
}
#[derive(Subcommand)]
enum Commands {
/// List connected SC64 devices
List,
/// Upload ROM (and save) to the SC64
Upload(UploadArgs),
/// Upload ROM, 64DD IPL and run disk server
_64DD(_64DDArgs),
/// Enter debug mode
Debug(DebugArgs),
/// Dump data from arbitrary location in SC64 memory space
Dump(DumpArgs),
/// Print information about SC64 device
Info,
/// Update persistent settings on SC64 device
Set {
#[command(subcommand)]
command: SetCommands,
},
/// Print firmware metadata / update or backup SC64 firmware
Firmware {
#[command(subcommand)]
command: FirmwareCommands,
},
}
#[derive(Args)]
struct UploadArgs {
/// Path to the ROM file
rom: PathBuf,
/// Path to the save file
#[arg(short, long)]
save: Option<PathBuf>,
/// Override autodetected save type
#[arg(short = 't', long)]
save_type: Option<SaveType>,
/// Use direct boot mode (skip bootloader)
#[arg(short, long)]
direct: bool,
/// Do not put last 128 kiB of ROM inside flash memory (can corrupt non EEPROM saves)
#[arg(short, long)]
no_shadow: bool,
/// Force TV type (ignored when used in conjunction with direct boot mode)
#[arg(long)]
tv: Option<TvType>,
}
#[derive(Args)]
struct _64DDArgs {
/// Path to the ROM file
#[arg(short, long)]
rom: Option<PathBuf>,
/// Path to the 64DD IPL file
ddipl: PathBuf,
/// Path to the 64DD disk file (.ndd format, can be specified multiple times)
disk: Vec<PathBuf>,
/// Use direct boot mode (skip bootloader)
#[arg(short, long)]
direct: bool,
}
#[derive(Args)]
struct DebugArgs {
/// Enable IS-Viewer64 and set listening address at ROM offset (in most cases it's fixed at 0x03FF0000)
#[arg(long, value_name = "offset", value_parser = |s: &str| maybe_hex_range::<u32>(s, 0x00000004, 0x03FF0000))]
isv: Option<u32>,
/// Expose TCP socket port for GDB debugging
#[arg(long, value_name = "port", value_parser = clap::value_parser!(u16).range(1..))]
gdb: Option<u16>,
}
#[derive(Args)]
struct DumpArgs {
/// Starting memory address
#[arg(value_parser = |s: &str| maybe_hex_range::<u32>(s, 0, sc64::MEMORY_LENGTH as u32))]
address: u32,
/// Dump length
#[arg(value_parser = |s: &str| maybe_hex_range::<usize>(s, 1, sc64::MEMORY_LENGTH))]
length: usize,
/// Path to the dump file
path: PathBuf,
}
#[derive(Subcommand)]
enum SetCommands {
/// Synchronize real time clock (RTC) on the SC64 with local system time
Rtc,
/// Enable LED I/O activity blinking
BlinkOn,
/// Disable LED I/O activity blinking
BlinkOff,
}
#[derive(Subcommand)]
enum FirmwareCommands {
/// Print metadata included inside SC64 firmware file
Info(FirmwareArgs),
/// Download current SC64 firmware and save it to provided file
Backup(FirmwareArgs),
/// Update SC64 firmware from provided file
Update(FirmwareArgs),
}
#[derive(Args)]
struct FirmwareArgs {
/// Path to the firmware file
firmware: PathBuf,
}
#[derive(Clone, Debug, ValueEnum)]
enum SaveType {
None,
Eeprom4k,
Eeprom16k,
Sram,
SramBanked,
Flashram,
}
#[derive(Clone, Debug, ValueEnum)]
enum TvType {
PAL,
NTSC,
MPAL,
}
fn main() {
let cli = Cli::parse();
// panic::set_hook(Box::new(|_| {}));
match panic::catch_unwind(|| handle_command(&cli.command, cli.sn)) {
Ok(_) => {}
Err(payload) => {
eprintln!("{}", panic_message(&payload).red());
process::exit(1);
}
}
}
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>) {
let result = match command {
Commands::Upload(args) => handle_upload_command(sn, args),
Commands::_64DD(args) => handle_64dd_command(sn, args),
Commands::Dump(args) => handle_dump_command(sn, args),
Commands::Debug(args) => handle_debug_command(sn, args),
Commands::Info => handle_info_command(sn),
Commands::Set { command } => handle_set_command(sn, command),
Commands::Firmware { command } => handle_firmware_command(sn, command),
Commands::List => handle_list_command(),
};
match result {
Ok(()) => {}
Err(error) => panic!("{error}"),
};
}
fn handle_upload_command(sn: Option<String>, args: &UploadArgs) -> Result<(), sc64::Error> {
let mut sc64 = init_sc64(sn, true)?;
sc64.reset_state()?;
let rom_path = args.rom.to_str().unwrap();
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");
// 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,
};
sc64.set_save_type(save_type)?;
if args.save.is_some() {
let save = 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");
}
println!("Save type set to [{args_save_type:?}]");
let boot_mode = if args.direct {
sc64::BootMode::DirectRom
} else {
sc64::BootMode::Rom
};
sc64.set_boot_mode(boot_mode)?;
println!("Boot mode set to [{:?}]", boot_mode);
if let Some(tv) = args.tv.as_ref() {
if args.direct {
println!("TV type ignored because direct boot mode is 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:?}]");
}
}
sc64.calculate_cic_parameters()?;
Ok(())
}
fn handle_64dd_command(sn: Option<String>, args: &_64DDArgs) -> Result<(), sc64::Error> {
let mut sc64 = init_sc64(sn, true)?;
// TODO: parse 64DD disk files
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_micros(1));
}
}
Ok(())
}
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)?;
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)?;
file.write(&data)?;
println!("done");
Ok(())
}
fn handle_debug_command(sn: Option<String>, args: &DebugArgs) -> Result<(), sc64::Error> {
let mut sc64 = init_sc64(sn, true)?;
if args.isv.is_some() {
sc64.configure_isviewer64(args.isv)?;
}
println!("{}", "Debug mode started".bold());
let exit = setup_exit_flag();
while !exit.load(Ordering::Relaxed) {
if let Some(data_packet) = sc64.receive_data_packet()? {
match data_packet {
sc64::DataPacket::IsViewer(message) => print!("{}", message),
sc64::DataPacket::Debug(debug_packet) => handle_debug_packet(debug_packet),
_ => {}
}
} else {
thread::sleep(Duration::from_micros(1));
}
}
println!("{}", "Debug mode ended".bold());
if args.isv.is_some() {
sc64.configure_isviewer64(None)?;
}
Ok(())
}
fn handle_info_command(sn: Option<String>) -> Result<(), sc64::Error> {
let mut sc64 = init_sc64(sn, false)?;
let (major, minor) = sc64.check_firmware_version()?;
let state = sc64.get_device_state()?;
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);
Ok(())
}
fn handle_set_command(sn: Option<String>, command: &SetCommands) -> Result<(), sc64::Error> {
let mut sc64 = init_sc64(sn, true)?;
match command {
SetCommands::Rtc => {
let datetime = Local::now();
sc64.set_datetime(datetime)?;
println!(
"SC64 RTC datetime synchronized to: {}",
datetime.format("%Y-%m-%d %H:%M:%S %Z").to_string().green()
);
}
SetCommands::BlinkOn => {
sc64.set_led_blink(true)?;
println!(
"SC64 LED I/O activity blinking set to {}",
"enabled".green()
);
}
SetCommands::BlinkOff => {
sc64.set_led_blink(false)?;
println!("SC64 LED I/O activity blinking set to {}", "disabled".red());
}
}
Ok(())
}
fn handle_firmware_command(
sn: Option<String>,
command: &FirmwareCommands,
) -> Result<(), sc64::Error> {
match command {
FirmwareCommands::Info(args) => {
let firmware_path = &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)?;
// TODO: print firmware metadata
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");
Ok(())
}
FirmwareCommands::Update(args) => {
let update_path = &args.firmware;
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)?;
// 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");
Ok(())
}
}
}
fn handle_list_command() -> Result<(), sc64::Error> {
let devices = sc64::list_serial_devices()?;
println!("{}", "Found devices:".bold());
for (i, d) in devices.iter().enumerate() {
println!(" {i}: {}", d.sn);
}
Ok(())
}

0
sw/deployer/src/n64.rs Normal file
View File

180
sw/deployer/src/sc64/cic.rs Normal file
View File

@ -0,0 +1,180 @@
use super::Error;
use crc32fast::Hasher;
pub const IPL3_OFFSET: u32 = 0x40;
pub const IPL3_LENGTH: usize = 0xFC0;
pub fn guess_ipl3_seed(ipl3: &[u8]) -> Result<u8, Error> {
if ipl3.len() < IPL3_LENGTH {
return Err(Error::new("Invalid IPL3 length provided"));
}
let mut hasher = Hasher::new();
hasher.update(ipl3);
Ok(match hasher.finalize() {
0x587BD543 => 0xAC, // 5101
0x6170A4A1 => 0x3F, // 6101
0x009E9EA3 => 0x3F, // 7102
0x90BB6CB5 => 0x3F, // 6102/7101
0x0B050EE0 => 0x78, // x103
0x98BC2C86 => 0x91, // x105
0xACC8580A => 0x85, // x106
0x0E018159 => 0xDD, // 5167
0x10C68B18 => 0xDD, // NDXJ0
0xBC605D0A => 0xDD, // NDDJ0
0x502C4466 => 0xDD, // NDDJ1
0x0C965795 => 0xDD, // NDDJ2
0x8FEBA21E => 0xDE, // NDDE0
_ => 0x3F,
})
}
pub fn calculate_ipl3_checksum(ipl3: &[u8], seed: u8) -> Result<[u8; 6], Error> {
if ipl3.len() < IPL3_LENGTH {
return Err(Error::new("Invalid IPL3 length provided"));
}
const MAGIC: u32 = 0x6C078965;
let get = |offset: u32| {
let o: usize = offset as usize * 4;
return ((ipl3[o] as u32) << 24)
| ((ipl3[o + 1] as u32) << 16)
| ((ipl3[o + 2] as u32) << 8)
| (ipl3[o + 3] as u32);
};
let add = |a1: u32, a2: u32| u32::wrapping_add(a1, a2);
let sub = |a1: u32, a2: u32| u32::wrapping_sub(a1, a2);
let mul = |a1: u32, a2: u32| u32::wrapping_mul(a1, a2);
let lsh = |a: u32, s: u32| if s >= 32 { 0 } else { u32::wrapping_shl(a, s) };
let rsh = |a: u32, s: u32| if s >= 32 { 0 } else { u32::wrapping_shr(a, s) };
let checksum = |a0: u32, a1: u32, a2: u32| {
let prod = (a0 as u64).wrapping_mul(if a1 == 0 { a2 as u64 } else { a1 as u64 });
let hi = ((prod >> 32) & 0xFFFFFFFF) as u32;
let lo = (prod & 0xFFFFFFFF) as u32;
let diff = hi.wrapping_sub(lo);
return if diff == 0 { a0 } else { diff };
};
let init = add(mul(MAGIC, seed as u32), 1) ^ get(0);
let mut buffer = vec![init; 16];
for i in 1..=1008 as u32 {
let data_prev = get(i.saturating_sub(2));
let data_curr = get(i - 1);
buffer[0] = add(buffer[0], checksum(sub(1007, i), data_curr, i));
buffer[1] = checksum(buffer[1], data_curr, i);
buffer[2] = buffer[2] ^ data_curr;
buffer[3] = add(buffer[3], checksum(add(data_curr, 5), MAGIC, i));
let shift = data_prev & 0x1F;
let data_left = lsh(data_curr, 32 - shift);
let data_right = rsh(data_curr, shift);
let b4_shifted = data_left | data_right;
buffer[4] = add(buffer[4], b4_shifted);
let shift = rsh(data_prev, 27);
let data_left = lsh(data_curr, shift);
let data_right = rsh(data_curr, 32 - shift);
let b5_shifted = data_left | data_right;
buffer[5] = add(buffer[5], b5_shifted);
if data_curr < buffer[6] {
buffer[6] = add(buffer[3], buffer[6]) ^ add(data_curr, i);
} else {
buffer[6] = add(buffer[4], data_curr) ^ buffer[6];
}
let shift = data_prev & 0x1F;
let data_left = lsh(data_curr, shift);
let data_right = rsh(data_curr, 32 - shift);
buffer[7] = checksum(buffer[7], data_left | data_right, i);
let shift = rsh(data_prev, 27);
let data_left = lsh(data_curr, 32 - shift);
let data_right = rsh(data_curr, shift);
buffer[8] = checksum(buffer[8], data_left | data_right, i);
if data_prev < data_curr {
buffer[9] = checksum(buffer[9], data_curr, i)
} else {
buffer[9] = add(buffer[9], data_curr);
}
if i == 1008 {
break;
}
let data_next = get(i);
buffer[10] = checksum(add(buffer[10], data_curr), data_next, i);
buffer[11] = checksum(buffer[11] ^ data_curr, data_next, i);
buffer[12] = add(buffer[12], buffer[8] ^ data_curr);
let shift = data_curr & 0x1F;
let data_left = lsh(data_curr, 32 - shift);
let data_right = rsh(data_curr, shift);
let tmp = data_left | data_right;
let shift = data_next & 0x1F;
let data_left = lsh(data_next, 32 - shift);
let data_right = rsh(data_next, shift);
buffer[13] = add(buffer[13], add(tmp, data_left | data_right));
let shift = data_curr & 0x1F;
let data_left = lsh(data_next, 32 - shift);
let data_right = rsh(data_next, shift);
let sum = checksum(buffer[14], b4_shifted, i);
buffer[14] = checksum(sum, data_left | data_right, i);
let shift = rsh(data_curr, 27);
let data_left = lsh(data_next, shift);
let data_right = rsh(data_next, 32 - shift);
let sum = checksum(buffer[15], b5_shifted, i);
buffer[15] = checksum(sum, data_left | data_right, i);
}
let mut final_buffer = vec![buffer[0]; 4];
for i in 0..16 as u32 {
let data = buffer[i as usize];
let shift = data & 0x1F;
let data_left = lsh(data, 32 - shift);
let data_right = rsh(data, shift);
let b0_shifted = add(final_buffer[0], data_left | data_right);
final_buffer[0] = b0_shifted;
if data < b0_shifted {
final_buffer[1] = add(final_buffer[1], data);
} else {
final_buffer[1] = checksum(final_buffer[1], data, i);
}
if rsh(data & 0x02, 1) == (data & 0x01) {
final_buffer[2] = add(final_buffer[2], data);
} else {
final_buffer[2] = checksum(final_buffer[2], data, i);
}
if (data & 0x01) == 0x01 {
final_buffer[3] = final_buffer[3] ^ data;
} else {
final_buffer[3] = checksum(final_buffer[3], data, i);
}
}
let sum = checksum(final_buffer[0], final_buffer[1], 16);
let xor = final_buffer[3] ^ final_buffer[2];
Ok([
(sum >> 8) as u8,
(sum >> 0) as u8,
(xor >> 24) as u8,
(xor >> 16) as u8,
(xor >> 8) as u8,
(xor >> 0) as u8,
])
}

View File

@ -0,0 +1,34 @@
use std::fmt::{Display, Formatter, Result};
#[derive(Debug, Clone)]
pub struct Error {
description: String,
}
impl Error {
pub fn new(description: &str) -> Self {
Error {
description: format!("{}", description),
}
}
}
impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result {
write!(f, "SC64 error: {}", self.description.as_str())
}
}
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Error::new(format!("IO error: {}", value).as_str())
}
}
impl From<serialport::Error> for Error {
fn from(value: serialport::Error) -> Self {
Error::new(format!("SerialPort error: {}", value.description).as_str())
}
}

View File

View File

@ -0,0 +1,231 @@
use super::{error::Error, utils};
use std::{collections::VecDeque, time::Duration};
#[derive(Clone, Debug)]
pub struct Device {
pub sn: String,
pub port: String,
}
#[derive(Clone, Debug)]
pub struct Command {
pub id: u8,
pub args: [u32; 2],
pub data: Vec<u8>,
}
#[derive(Clone, Debug)]
pub struct Response {
pub id: u8,
pub data: Vec<u8>,
pub error: bool,
}
#[derive(Clone, Debug)]
pub struct Packet {
pub id: u8,
pub data: Vec<u8>,
}
enum DataType {
Response,
Packet,
}
pub trait Link {
fn execute_command(&mut self, command: &Command) -> Result<Vec<u8>, Error>;
fn execute_command_raw(
&mut self,
command: &Command,
timeout: Duration,
no_response: bool,
ignore_error: bool,
) -> Result<Vec<u8>, Error>;
fn receive_packet(&mut self) -> Result<Option<Packet>, Error>;
}
pub struct SerialLink {
serial: Box<dyn serialport::SerialPort>,
packets: VecDeque<Packet>,
}
const COMMAND_TIMEOUT: Duration = Duration::from_secs(5);
const PACKET_TIMEOUT: Duration = Duration::from_secs(5);
impl SerialLink {
fn reset(&mut self) -> Result<(), Error> {
const WAIT_DURATION: Duration = Duration::from_millis(10);
const RETRY_COUNT: i32 = 100;
self.serial.write_data_terminal_ready(true)?;
for n in 0..=RETRY_COUNT {
self.serial.clear(serialport::ClearBuffer::All)?;
std::thread::sleep(WAIT_DURATION);
if self.serial.read_data_set_ready()? {
break;
}
if n == RETRY_COUNT {
return Err(Error::new("Couldn't reset SC64 device (on)"));
}
}
self.serial.write_data_terminal_ready(false)?;
for n in 0..=RETRY_COUNT {
std::thread::sleep(WAIT_DURATION);
if !self.serial.read_data_set_ready()? {
break;
}
if n == RETRY_COUNT {
return Err(Error::new("Couldn't reset SC64 device (off)"));
}
}
Ok(())
}
fn serial_send_command(&mut self, command: &Command, timeout: Duration) -> Result<(), Error> {
let Command { id, args, mut data } = command.clone();
let mut packet: Vec<u8> = 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);
self.serial.set_timeout(timeout)?;
self.serial.write_all(&packet)?;
self.serial.flush()?;
Ok(())
}
fn serial_process_incoming_data(
&mut self,
data_type: DataType,
timeout: Duration,
) -> Result<Option<Response>, Error> {
const HEADER_SIZE: u32 = 8;
let mut buffer = [0u8; HEADER_SIZE as usize];
self.serial.set_timeout(timeout)?;
while matches!(data_type, DataType::Response) || self.serial.bytes_to_read()? >= HEADER_SIZE
{
self.serial.read_exact(&mut buffer)?;
let (packet_token, error) = (match &buffer[0..3] {
b"CMP" => Ok((false, false)),
b"PKT" => Ok((true, false)),
b"ERR" => Ok((false, true)),
_ => Err(Error::new("Unknown response token")),
})?;
let id = buffer[3];
let length = utils::u32_from_vec(&buffer[4..8])? as usize;
let mut data = vec![0u8; length];
self.serial.read_exact(&mut data)?;
if packet_token {
self.packets.push_back(Packet { id, data });
if matches!(data_type, DataType::Packet) {
return Ok(None);
}
} else {
return Ok(Some(Response { id, error, data }));
}
}
Ok(None)
}
fn send_command(&mut self, command: &Command) -> Result<(), Error> {
self.serial_send_command(command, COMMAND_TIMEOUT)
}
fn receive_response(&mut self, timeout: Duration) -> Result<Response, Error> {
if let Some(response) = self.serial_process_incoming_data(DataType::Response, timeout)? {
return Ok(response);
}
Err(Error::new("Command response timeout"))
}
}
impl Link for SerialLink {
fn execute_command(&mut self, command: &Command) -> Result<Vec<u8>, Error> {
self.execute_command_raw(command, COMMAND_TIMEOUT, false, false)
}
fn execute_command_raw(
&mut self,
command: &Command,
timeout: Duration,
no_response: bool,
ignore_error: bool,
) -> Result<Vec<u8>, Error> {
self.send_command(command)?;
if no_response {
return Ok(vec![]);
}
let response = self.receive_response(timeout)?;
compare_id(&command, &response)?;
if !ignore_error && response.error {
return Err(Error::new("Command response error"));
}
Ok(response.data)
}
fn receive_packet(&mut self) -> Result<Option<Packet>, Error> {
if self.packets.len() == 0 {
self.serial_process_incoming_data(DataType::Packet, PACKET_TIMEOUT)?;
}
Ok(self.packets.pop_front())
}
}
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> {
const SC64_VID: u16 = 0x0403;
const SC64_PID: u16 = 0x6014;
const SC64_SID: &str = "SC64";
let mut devices: Vec<Device> = Vec::new();
for device in serialport::available_ports()?.into_iter() {
if let serialport::SerialPortType::UsbPort(info) = device.port_type {
let sn = info.serial_number.unwrap_or("".to_string());
if info.vid == SC64_VID && info.pid == SC64_PID && sn.starts_with(SC64_SID) {
devices.push(Device {
sn,
port: device.port_name,
});
}
}
}
if devices.len() == 0 {
return Err(Error::new("No SC64 devices found"));
}
return Ok(devices);
}
pub fn new_serial(port: &str) -> Result<Box<dyn Link>, Error> {
let mut link = SerialLink {
serial: serialport::new(port, 115_200)
.timeout(COMMAND_TIMEOUT)
.open()?,
packets: VecDeque::new(),
};
link.reset()?;
Ok(Box::new(link))
}

672
sw/deployer/src/sc64/mod.rs Normal file
View File

@ -0,0 +1,672 @@
mod cic;
mod error;
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,
},
};
use chrono::{DateTime, Local};
pub use error::Error;
use std::io::{Read, Seek};
use std::time::Instant;
use std::{cmp::min, time::Duration};
pub struct SC64 {
link: Box<dyn Link>,
}
#[derive(Debug)]
pub struct DeviceState {
pub bootloader_switch: bool,
pub rom_write_enable: bool,
pub rom_shadow_enable: bool,
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_drive_type: DdDriveType,
pub dd_disk_state: DdDiskState,
pub button_state: bool,
pub button_mode: ButtonMode,
pub rom_extended_enable: bool,
pub led_enable: bool,
pub datetime: DateTime<Local>,
}
const SUPPORTED_MAJOR_VERSION: u16 = 2;
const SUPPORTED_MINOR_VERSION: u16 = 12;
const SDRAM_ADDRESS: u32 = 0x0000_0000;
const SDRAM_LENGTH: usize = 64 * 1024 * 1024;
const ROM_SHADOW_ADDRESS: u32 = 0x04FE_0000;
const ROM_SHADOW_LENGTH: usize = 128 * 1024;
const ROM_EXTENDED_ADDRESS: u32 = 0x0400_0000;
const ROM_EXTENDED_LENGTH: usize = 14 * 1024 * 1024;
const MAX_ROM_LENGTH: usize = 78 * 1024 * 1024;
const DDIPL_ADDRESS: u32 = 0x03BC_0000;
const DDIPL_LENGTH: usize = 4 * 1024 * 1024;
const SAVE_ADDRESS: u32 = 0x03FE_0000;
const EEPROM_ADDRESS: u32 = 0x0500_2000;
const EEPROM_4K_LENGTH: usize = 512;
const EEPROM_16K_LENGTH: usize = 2 * 1024;
const SRAM_LENGTH: usize = 32 * 1024;
const SRAM_BANKED_LENGTH: usize = 3 * 32 * 1024;
const FLASHRAM_LENGTH: usize = 128 * 1024;
const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000;
const BOOTLOADER_LENGTH: usize = 1920 * 1024;
const FIRMWARE_ADDRESS: u32 = 0x0010_0000; // Arbitrary offset in SDRAM memory
const FIRMWARE_COMMAND_TIMEOUT: Duration = Duration::from_secs(30);
const FIRMWARE_UPDATE_TIMEOUT: Duration = Duration::from_secs(90);
const COMMAND_USB_WRITE_TIMEOUT: Duration = Duration::from_secs(5);
const ISV_BUFFER_LENGTH: usize = 64 * 1024;
pub const MEMORY_LENGTH: usize = 0x0500_2980;
impl SC64 {
fn command_identifier_get(&mut self) -> Result<Vec<u8>, Error> {
let identifier = self.link.execute_command(&Command {
id: b'v',
args: [0, 0],
data: vec![],
})?;
Ok(identifier)
}
fn command_version_get(&mut self) -> Result<(u16, u16), Error> {
let version = self.link.execute_command(&Command {
id: b'V',
args: [0, 0],
data: vec![],
})?;
let major = utils::u16_from_vec(&version[0..2])?;
let minor = utils::u16_from_vec(&version[2..4])?;
Ok((major, minor))
}
fn command_state_reset(&mut self) -> Result<(), Error> {
self.link.execute_command(&Command {
id: b'R',
args: [0, 0],
data: vec![],
})?;
Ok(())
}
fn command_cic_params_set(
&mut self,
disable: bool,
seed: u8,
checksum: &[u8; 6],
) -> Result<(), Error> {
let mut params: Vec<u8> = vec![];
params.append(&mut [(disable as u8) << 0, seed].to_vec());
params.append(&mut checksum.to_vec());
self.link.execute_command(&Command {
id: b'B',
args: args_from_vec(&params[0..8])?,
data: vec![],
})?;
Ok(())
}
fn command_config_get(&mut self, config_id: ConfigId) -> Result<Config, Error> {
let data = self.link.execute_command(&Command {
id: b'c',
args: [config_id.into(), 0],
data: vec![],
})?;
let value = u32_from_vec(&data[0..4])?;
Ok((config_id, value).try_into()?)
}
fn command_config_set(&mut self, config: Config) -> Result<(), Error> {
self.link.execute_command(&Command {
id: b'C',
args: config.into(),
data: vec![],
})?;
Ok(())
}
fn command_setting_get(&mut self, setting_id: SettingId) -> Result<Setting, Error> {
let data = self.link.execute_command(&Command {
id: b'a',
args: [setting_id.into(), 0],
data: vec![],
})?;
let value = u32_from_vec(&data[0..4])?;
Ok((setting_id, value).try_into()?)
}
fn command_setting_set(&mut self, setting: Setting) -> Result<(), Error> {
self.link.execute_command(&Command {
id: b'A',
args: setting.into(),
data: vec![],
})?;
Ok(())
}
fn command_time_get(&mut self) -> Result<DateTime<Local>, Error> {
let data = self.link.execute_command(&Command {
id: b't',
args: [0, 0],
data: vec![],
})?;
Ok(datetime_from_vec(&data[0..8])?)
}
fn command_time_set(&mut self, datetime: DateTime<Local>) -> Result<(), Error> {
self.link.execute_command(&Command {
id: b'T',
args: args_from_vec(&vec_from_datetime(datetime)?[0..8])?,
data: vec![],
})?;
Ok(())
}
fn command_memory_read(&mut self, address: u32, length: usize) -> Result<Vec<u8>, Error> {
let data = self.link.execute_command(&Command {
id: b'm',
args: [address, length as u32],
data: vec![],
})?;
Ok(data)
}
fn command_memory_write(&mut self, address: u32, data: &[u8]) -> Result<(), Error> {
self.link.execute_command(&Command {
id: b'M',
args: [address, data.len() as u32],
data: data.to_vec(),
})?;
Ok(())
}
fn command_usb_write(&mut self, datatype: u8, data: &[u8]) -> Result<(), Error> {
self.link.execute_command_raw(
&Command {
id: b'U',
args: [datatype as u32, data.len() as u32],
data: data.to_vec(),
},
COMMAND_USB_WRITE_TIMEOUT,
true,
false,
)?;
Ok(())
}
fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> {
self.link.execute_command(&Command {
id: b'D',
args: [error as u32, 0],
data: vec![],
})?;
Ok(())
}
fn command_flash_wait_busy(&mut self, wait: bool) -> Result<u32, Error> {
let erase_block_size = self.link.execute_command(&Command {
id: b'p',
args: [wait as u32, 0],
data: vec![],
})?;
Ok(utils::u32_from_vec(&erase_block_size[0..4])?)
}
fn command_flash_erase_block(&mut self, address: u32) -> Result<(), Error> {
self.link.execute_command(&Command {
id: b'P',
args: [address, 0],
data: vec![],
})?;
Ok(())
}
fn command_firmware_backup(&mut self, address: u32) -> Result<(FirmwareStatus, u32), Error> {
let data = self.link.execute_command_raw(
&Command {
id: b'f',
args: [address, 0],
data: vec![],
},
FIRMWARE_COMMAND_TIMEOUT,
false,
true,
)?;
let status = FirmwareStatus::try_from(utils::u32_from_vec(&data[0..4])?)?;
let length = utils::u32_from_vec(&data[4..8])?;
Ok((status, length))
}
fn command_firmware_update(
&mut self,
address: u32,
length: usize,
) -> Result<FirmwareStatus, Error> {
let data = self.link.execute_command_raw(
&Command {
id: b'F',
args: [address, length as u32],
data: vec![],
},
FIRMWARE_COMMAND_TIMEOUT,
false,
true,
)?;
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",
));
}
Ok((major, minor))
}
pub fn reset_state(&mut self) -> Result<(), Error> {
self.command_state_reset()?;
Ok(())
}
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| {
c.swap(0, 3);
c.swap(1, 2)
})
},
_ => |_b: &mut [u8]| {},
};
let rom_shadow_enabled = !no_shadow && length > (SDRAM_LENGTH - ROM_SHADOW_LENGTH);
let rom_extended_enabled = length > SDRAM_LENGTH;
let sdram_length = if rom_shadow_enabled {
min(length, SDRAM_LENGTH - ROM_SHADOW_LENGTH)
} else {
min(length, SDRAM_LENGTH)
};
let mut buffer = vec![0u8; BUFFER_SIZE];
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))?;
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])?;
}
self.command_config_set(Config::RomExtendedEnable(rom_extended_enabled))?;
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])?;
}
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"));
}
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,
};
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 {
return Err(Error::new(
"Save file size did not match currently enabled save type",
));
}
let mut buffer = vec![0u8; length];
file.read(&mut buffer)?;
self.command_memory_write(address, &buffer)
}
pub fn dump_memory(&mut self, address: u32, length: usize) -> Result<Vec<u8>, Error> {
if address + length as u32 > MEMORY_LENGTH as u32 {
return Err(Error::new("Invalid dump address or length"));
}
self.command_memory_read(address, length)
}
pub fn calculate_cic_parameters(&mut self) -> Result<(), Error> {
let boot_mode = get_config!(self, BootMode)?;
let address = match boot_mode {
BootMode::DirectRom => SDRAM_ADDRESS,
BootMode::DirectDdIpl => DDIPL_ADDRESS,
_ => BOOTLOADER_ADDRESS,
};
let ipl3 = self.command_memory_read(address + IPL3_OFFSET, IPL3_LENGTH)?;
let seed = guess_ipl3_seed(&ipl3)?;
let checksum = &calculate_ipl3_checksum(&ipl3, seed)?;
self.command_cic_params_set(false, seed, checksum)
}
pub fn set_boot_mode(&mut self, boot_mode: BootMode) -> Result<(), Error> {
self.command_config_set(Config::BootMode(boot_mode))
}
pub fn set_save_type(&mut self, save_type: SaveType) -> Result<(), Error> {
self.command_config_set(Config::SaveType(save_type))
}
pub fn set_tv_type(&mut self, tv_type: TvType) -> Result<(), Error> {
self.command_config_set(Config::TvType(tv_type))
}
pub fn get_datetime(&mut self) -> Result<DateTime<Local>, Error> {
self.command_time_get()
}
pub fn set_datetime(&mut self, datetime: DateTime<Local>) -> Result<(), Error> {
self.command_time_set(datetime)
}
pub fn set_led_blink(&mut self, enabled: bool) -> Result<(), Error> {
self.command_setting_set(Setting::LedEnable(enabled))
}
pub fn get_device_state(&mut self) -> Result<DeviceState, Error> {
Ok(DeviceState {
bootloader_switch: get_config!(self, BootloaderSwitch)?,
rom_write_enable: get_config!(self, RomWriteEnable)?,
rom_shadow_enable: get_config!(self, RomShadowEnable)?,
dd_mode: get_config!(self, DdMode)?,
isv_address: get_config!(self, IsvAddress)?,
boot_mode: get_config!(self, BootMode)?,
save_type: get_config!(self, SaveType)?,
cic_seed: get_config!(self, CicSeed)?,
tv_type: get_config!(self, TvType)?,
dd_sd_enable: get_config!(self, DdSdEnable)?,
dd_drive_type: get_config!(self, DdDriveType)?,
dd_disk_state: get_config!(self, DdDiskState)?,
button_state: get_config!(self, ButtonState)?,
button_mode: get_config!(self, ButtonMode)?,
rom_extended_enable: get_config!(self, RomExtendedEnable)?,
led_enable: get_setting!(self, LedEnable)?,
datetime: self.get_datetime()?,
})
}
pub fn configure_64dd(
&mut self,
dd_mode: DdMode,
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::DdDriveType(drive_type))?;
self.command_config_set(Config::DdDiskState(DdDiskState::Ejected))?;
Ok(())
}
pub fn set_64dd_disk_state(&mut self, disk_state: DdDiskState) -> Result<(), Error> {
self.command_config_set(Config::DdDiskState(disk_state))
}
pub fn configure_isviewer64(&mut self, offset: Option<u32>) -> Result<(), Error> {
if let Some(off) = offset {
if get_config!(self, RomShadowEnable)? {
if off > (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"
)
.as_str(),
));
}
}
self.command_config_set(Config::RomWriteEnable(true))?;
self.command_config_set(Config::IsvAddress(off))?;
} else {
self.command_config_set(Config::RomWriteEnable(false))?;
self.command_config_set(Config::IsvAddress(0))?;
}
Ok(())
}
pub fn receive_data_packet(&mut self) -> Result<Option<DataPacket>, Error> {
if let Some(packet) = self.link.receive_packet()? {
return Ok(Some(packet.try_into()?));
}
Ok(None)
}
pub fn reply_disk_packet(&mut self, disk_packet: Option<DiskPacket>) -> Result<(), Error> {
if let Some(packet) = disk_packet {
match packet {
DiskPacket::ReadBlock(disk_block) => {
self.command_memory_write(disk_block.address, &disk_block.data)?;
}
DiskPacket::WriteBlock(_) => {}
}
self.command_dd_set_block_ready(false)?;
} else {
self.command_dd_set_block_ready(true)?;
}
Ok(())
}
pub fn send_debug_packet(&mut self, debug_packet: DebugPacket) -> Result<(), Error> {
self.command_usb_write(debug_packet.datatype, &debug_packet.data)
}
pub fn backup_firmware(&mut self) -> Result<Vec<u8>, 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(),
));
}
self.command_memory_read(FIRMWARE_ADDRESS, length as usize)
}
pub fn update_firmware(&mut self, data: &[u8]) -> Result<(), Error> {
self.command_state_reset()?;
self.command_memory_write(FIRMWARE_ADDRESS, data)?;
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(),
));
}
let timeout = Instant::now();
let mut last_update_status = UpdateStatus::Err;
loop {
if let Some(packet) = self.receive_data_packet()? {
if let DataPacket::UpdateStatus(status) = packet {
match status {
UpdateStatus::Done => {
std::thread::sleep(Duration::from_secs(2));
return Ok(());
}
UpdateStatus::Err => {
return Err(Error::new(
format!(
"Firmware update error on step {:?}, device is most likely bricked",
last_update_status
)
.as_str(),
))
}
current_update_status => last_update_status = current_update_status,
}
}
}
if timeout.elapsed() > FIRMWARE_UPDATE_TIMEOUT {
return Err(Error::new(
format!(
"Firmware update timeout, SC64 did not finish update in {} seconds",
FIRMWARE_UPDATE_TIMEOUT.as_secs()
)
.as_str(),
));
}
std::thread::sleep(Duration::from_millis(1));
}
}
}
pub fn new(sn: Option<String>) -> Result<SC64, Error> {
let port = match sn {
Some(sn) => match list_serial_devices()?.iter().find(|d| d.sn == sn) {
Some(device) => device.port.clone(),
None => {
return Err(Error::new(
"No SC64 device found matching provided serial number",
))
}
},
None => list_serial_devices()?[0].port.clone(),
};
let mut sc64 = SC64 {
link: link::new_serial(&port)?,
};
let identifier = sc64
.command_identifier_get()
.map_err(|_| Error::new("Couldn't get SC64 device identifier"))?;
if identifier != b"SCv2" {
return Err(Error::new("Unknown identifier received, not a SC64 device"));
}
Ok(sc64)
}

View File

@ -0,0 +1,553 @@
use super::{link::Packet, utils::u32_from_vec, Error};
use encoding_rs::EUC_JP;
#[derive(Clone, Copy)]
pub enum ConfigId {
BootloaderSwitch,
RomWriteEnable,
RomShadowEnable,
DdMode,
IsvAddress,
BootMode,
SaveType,
CicSeed,
TvType,
DdSdEnable,
DdDriveType,
DdDiskState,
ButtonState,
ButtonMode,
RomExtendedEnable,
}
pub enum Config {
BootloaderSwitch(bool),
RomWriteEnable(bool),
RomShadowEnable(bool),
DdMode(DdMode),
IsvAddress(u32),
BootMode(BootMode),
SaveType(SaveType),
CicSeed(CicSeed),
TvType(TvType),
DdSdEnable(bool),
DdDriveType(DdDriveType),
DdDiskState(DdDiskState),
ButtonState(bool),
ButtonMode(ButtonMode),
RomExtendedEnable(bool),
}
impl From<ConfigId> for u32 {
fn from(value: ConfigId) -> Self {
match value {
ConfigId::BootloaderSwitch => 0,
ConfigId::RomWriteEnable => 1,
ConfigId::RomShadowEnable => 2,
ConfigId::DdMode => 3,
ConfigId::IsvAddress => 4,
ConfigId::BootMode => 5,
ConfigId::SaveType => 6,
ConfigId::CicSeed => 7,
ConfigId::TvType => 8,
ConfigId::DdSdEnable => 9,
ConfigId::DdDriveType => 10,
ConfigId::DdDiskState => 11,
ConfigId::ButtonState => 12,
ConfigId::ButtonMode => 13,
ConfigId::RomExtendedEnable => 14,
}
}
}
impl TryFrom<(ConfigId, u32)> for Config {
type Error = Error;
fn try_from(value: (ConfigId, u32)) -> Result<Self, Self::Error> {
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),
})
}
}
impl From<Config> for [u32; 2] {
fn from(value: Config) -> Self {
match value {
Config::BootloaderSwitch(val) => [ConfigId::BootloaderSwitch.into(), val.into()],
Config::RomWriteEnable(val) => [ConfigId::RomWriteEnable.into(), val.into()],
Config::RomShadowEnable(val) => [ConfigId::RomShadowEnable.into(), val.into()],
Config::DdMode(val) => [ConfigId::DdMode.into(), val.into()],
Config::IsvAddress(val) => [ConfigId::IsvAddress.into(), val.into()],
Config::BootMode(val) => [ConfigId::BootMode.into(), val.into()],
Config::SaveType(val) => [ConfigId::SaveType.into(), val.into()],
Config::CicSeed(val) => [ConfigId::CicSeed.into(), val.into()],
Config::TvType(val) => [ConfigId::TvType.into(), val.into()],
Config::DdSdEnable(val) => [ConfigId::DdSdEnable.into(), val.into()],
Config::DdDriveType(val) => [ConfigId::DdDriveType.into(), val.into()],
Config::DdDiskState(val) => [ConfigId::DdDiskState.into(), val.into()],
Config::ButtonState(val) => [ConfigId::ButtonState.into(), val.into()],
Config::ButtonMode(val) => [ConfigId::ButtonMode.into(), val.into()],
Config::RomExtendedEnable(val) => [ConfigId::RomExtendedEnable.into(), val.into()],
}
}
}
#[derive(Debug)]
pub enum DdMode {
None,
Regs,
DdIpl,
Full,
}
impl TryFrom<u32> for DdMode {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0 => DdMode::None,
1 => DdMode::Regs,
2 => DdMode::DdIpl,
3 => DdMode::Full,
_ => return Err(Error::new("Unknown 64DD mode code")),
})
}
}
impl From<DdMode> for u32 {
fn from(value: DdMode) -> Self {
match value {
DdMode::None => 0,
DdMode::Regs => 1,
DdMode::DdIpl => 2,
DdMode::Full => 3,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum BootMode {
Menu,
Rom,
DdIpl,
DirectRom,
DirectDdIpl,
}
impl TryFrom<u32> for BootMode {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0 => BootMode::Menu,
1 => BootMode::Rom,
2 => BootMode::DdIpl,
3 => BootMode::DirectRom,
4 => BootMode::DirectDdIpl,
_ => return Err(Error::new("Unknown boot mode code")),
})
}
}
impl From<BootMode> for u32 {
fn from(value: BootMode) -> Self {
match value {
BootMode::Menu => 0,
BootMode::Rom => 1,
BootMode::DdIpl => 2,
BootMode::DirectRom => 3,
BootMode::DirectDdIpl => 4,
}
}
}
#[derive(Debug)]
pub enum SaveType {
None,
Eeprom4k,
Eeprom16k,
Sram,
Flashram,
SramBanked,
}
impl TryFrom<u32> for SaveType {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0 => SaveType::None,
1 => SaveType::Eeprom4k,
2 => SaveType::Eeprom16k,
3 => SaveType::Sram,
4 => SaveType::Flashram,
5 => SaveType::SramBanked,
_ => return Err(Error::new("Unknown save type code")),
})
}
}
impl From<SaveType> for u32 {
fn from(value: SaveType) -> Self {
match value {
SaveType::None => 0,
SaveType::Eeprom4k => 1,
SaveType::Eeprom16k => 2,
SaveType::Sram => 3,
SaveType::Flashram => 4,
SaveType::SramBanked => 5,
}
}
}
#[derive(Debug)]
pub enum CicSeed {
Seed(u8),
Auto,
}
impl TryFrom<u32> for CicSeed {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(if value <= 0xFF {
CicSeed::Seed(value as u8)
} else if value == 0xFFFF {
CicSeed::Auto
} else {
return Err(Error::new("Unknown CIC seed code"));
})
}
}
impl From<CicSeed> for u32 {
fn from(value: CicSeed) -> Self {
match value {
CicSeed::Seed(seed) => seed.into(),
CicSeed::Auto => 0xFFFF,
}
}
}
#[derive(Debug)]
pub enum TvType {
PAL,
NTSC,
MPAL,
Auto,
}
impl TryFrom<u32> for TvType {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0 => TvType::PAL,
1 => TvType::NTSC,
2 => TvType::MPAL,
3 => TvType::Auto,
_ => return Err(Error::new("Unknown TV type code")),
})
}
}
impl From<TvType> for u32 {
fn from(value: TvType) -> Self {
match value {
TvType::PAL => 0,
TvType::NTSC => 1,
TvType::MPAL => 2,
TvType::Auto => 3,
}
}
}
#[derive(Debug)]
pub enum DdDriveType {
Retail,
Development,
}
impl TryFrom<u32> for DdDriveType {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0 => DdDriveType::Retail,
1 => DdDriveType::Development,
_ => return Err(Error::new("Unknown 64DD drive type code")),
})
}
}
impl From<DdDriveType> for u32 {
fn from(value: DdDriveType) -> Self {
match value {
DdDriveType::Retail => 0,
DdDriveType::Development => 1,
}
}
}
#[derive(Debug)]
pub enum DdDiskState {
Ejected,
Inserted,
Changed,
}
impl TryFrom<u32> for DdDiskState {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0 => DdDiskState::Ejected,
1 => DdDiskState::Inserted,
2 => DdDiskState::Changed,
_ => return Err(Error::new("Unknown 64DD disk state code")),
})
}
}
impl From<DdDiskState> for u32 {
fn from(value: DdDiskState) -> Self {
match value {
DdDiskState::Ejected => 0,
DdDiskState::Inserted => 1,
DdDiskState::Changed => 2,
}
}
}
#[derive(Debug)]
pub enum ButtonMode {
None,
N64Irq,
UsbPacket,
DdDiskSwap,
}
impl TryFrom<u32> for ButtonMode {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0 => ButtonMode::None,
1 => ButtonMode::N64Irq,
2 => ButtonMode::UsbPacket,
3 => ButtonMode::DdDiskSwap,
_ => return Err(Error::new("Unknown button mode code")),
})
}
}
impl From<ButtonMode> for u32 {
fn from(value: ButtonMode) -> Self {
match value {
ButtonMode::None => 0,
ButtonMode::N64Irq => 1,
ButtonMode::UsbPacket => 2,
ButtonMode::DdDiskSwap => 3,
}
}
}
#[derive(Clone, Copy)]
pub enum SettingId {
LedEnable,
}
pub enum Setting {
LedEnable(bool),
}
impl From<SettingId> for u32 {
fn from(value: SettingId) -> Self {
match value {
SettingId::LedEnable => 0,
}
}
}
impl TryFrom<(SettingId, u32)> for Setting {
type Error = Error;
fn try_from(value: (SettingId, u32)) -> Result<Self, Self::Error> {
let (id, setting) = value;
Ok(match id {
SettingId::LedEnable => Setting::LedEnable(setting != 0),
})
}
}
impl From<Setting> for [u32; 2] {
fn from(value: Setting) -> Self {
match value {
Setting::LedEnable(val) => [SettingId::LedEnable.into(), val.into()],
}
}
}
pub enum DataPacket {
Button,
Debug(DebugPacket),
Disk(DiskPacket),
IsViewer(String),
UpdateStatus(UpdateStatus),
}
impl TryFrom<Packet> for DataPacket {
type Error = Error;
fn try_from(value: Packet) -> Result<Self, Self::Error> {
Ok(match value.id {
b'B' => Self::Button,
b'U' => Self::Debug(value.data.try_into()?),
b'D' => Self::Disk(value.data.try_into()?),
b'I' => Self::IsViewer(EUC_JP.decode(&value.data).0.into()),
b'F' => Self::UpdateStatus(u32_from_vec(&value.data[0..4])?.try_into()?),
_ => return Err(Error::new("Unknown data packet code")),
})
}
}
pub struct DebugPacket {
pub datatype: u8,
pub data: Vec<u8>,
}
impl TryFrom<Vec<u8>> for DebugPacket {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
if value.len() < 4 {
return Err(Error::new("Couldn't extract header from debug packet"));
}
let header = u32_from_vec(&value[0..4])?;
let datatype = ((header >> 24) & 0xFF) as u8;
let length = (header & 0x00FFFFFF) as usize;
let data = value[4..].to_vec();
if data.len() != length {
return Err(Error::new("Debug packet length did not match"));
}
Ok(Self { datatype, data })
}
}
pub enum DiskPacket {
ReadBlock(DiskBlock),
WriteBlock(DiskBlock),
}
impl TryFrom<Vec<u8>> for DiskPacket {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
if value.len() < 12 {
return Err(Error::new("Couldn't extract block info from disk packet"));
}
let command = u32_from_vec(&value[0..4])?;
let address = u32_from_vec(&value[4..8])?;
let track_head_block = u32_from_vec(&value[8..12])?;
let disk_block = DiskBlock {
address,
track: (track_head_block >> 2) & 0xFFF,
head: (track_head_block >> 1) & 0x01,
block: track_head_block & 0x01,
data: value[12..].to_vec(),
};
Ok(match command {
1 => Self::ReadBlock(disk_block),
2 => Self::WriteBlock(disk_block),
_ => return Err(Error::new("Unknown disk packet command code")),
})
}
}
pub struct DiskBlock {
pub address: u32,
pub track: u32,
pub head: u32,
pub block: u32,
pub data: Vec<u8>,
}
impl DiskBlock {
pub fn set_data(&mut self, data: &[u8]) {
self.data = data.to_vec();
}
}
#[derive(Debug)]
pub enum FirmwareStatus {
Ok,
ErrToken,
ErrChecksum,
ErrSize,
ErrUnknownChunk,
ErrRead,
}
impl TryFrom<u32> for FirmwareStatus {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0 => FirmwareStatus::Ok,
1 => FirmwareStatus::ErrToken,
2 => FirmwareStatus::ErrChecksum,
3 => FirmwareStatus::ErrSize,
4 => FirmwareStatus::ErrUnknownChunk,
5 => FirmwareStatus::ErrRead,
_ => return Err(Error::new("Unknown firmware status code")),
})
}
}
#[derive(Debug)]
pub enum UpdateStatus {
MCU,
FPGA,
Bootloader,
Done,
Err,
}
impl TryFrom<u32> for UpdateStatus {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
1 => UpdateStatus::MCU,
2 => UpdateStatus::FPGA,
3 => UpdateStatus::Bootloader,
0x80 => UpdateStatus::Done,
0xFF => UpdateStatus::Err,
_ => return Err(Error::new("Unknown update status code")),
})
}
}
macro_rules! get_config {
($sc64:ident, $config:ident) => {{
if let Config::$config(value) = $sc64.command_config_get(ConfigId::$config)? {
Ok(value)
} else {
Err(Error::new("Unexpected config type"))
}
}};
}
macro_rules! get_setting {
($sc64:ident, $setting:ident) => {{
#[allow(irrefutable_let_patterns)] // TODO: is there another way to ignore this warning?
if let Setting::$setting(value) = $sc64.command_setting_get(SettingId::$setting)? {
Ok(value)
} else {
Err(Error::new("Unexpected setting type"))
}
}};
}
pub(crate) use get_config;
pub(crate) use get_setting;

View File

@ -0,0 +1,75 @@
use super::Error;
use chrono::{DateTime, Datelike, Local, NaiveDateTime, TimeZone, Timelike};
pub fn u16_from_vec(data: &[u8]) -> Result<u16, Error> {
if data.len() != 2 {
return Err(Error::new("Invalid slice length provided to u16_from_vec"));
}
let bytes = data[0..2]
.try_into()
.map_err(|_| Error::new("Couldn't convert from bytes to u16"))?;
Ok(u16::from_be_bytes(bytes))
}
pub fn u32_from_vec(data: &[u8]) -> Result<u32, Error> {
if data.len() != 4 {
return Err(Error::new("Invalid slice length provided to u32_from_vec"));
}
let bytes = data[0..4]
.try_into()
.map_err(|_| Error::new("Couldn't convert from bytes to u32"))?;
Ok(u32::from_be_bytes(bytes))
}
pub fn args_from_vec(data: &[u8]) -> Result<[u32; 2], Error> {
if data.len() != 8 {
return Err(Error::new("Invalid slice length provided to args_from_vec"));
}
Ok([u32_from_vec(&data[0..4])?, u32_from_vec(&data[4..8])?])
}
pub fn u8_from_bcd(value: u8) -> u8 {
(((value & 0xF0) >> 4) * 10) + (value & 0x0F)
}
pub fn bcd_from_u8(value: u8) -> u8 {
(((value / 10) & 0x0F) << 4) | ((value % 10) & 0x0F)
}
pub fn datetime_from_vec(data: &[u8]) -> Result<DateTime<Local>, Error> {
let hour = u8_from_bcd(data[1]);
let minute = u8_from_bcd(data[2]);
let second = u8_from_bcd(data[3]);
let year = u8_from_bcd(data[5]) as u32 + 2000;
let month = u8_from_bcd(data[6]);
let day = u8_from_bcd(data[7]);
let native = &NaiveDateTime::parse_from_str(
&format!("{year:02}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}"),
"%Y-%m-%dT%H:%M:%S",
)
.map_err(|_| Error::new("Couldn't convert from bytes to DateTime<Local>"))?;
Ok(Local.from_local_datetime(native).unwrap())
}
pub fn vec_from_datetime(datetime: DateTime<Local>) -> Result<Vec<u8>, Error> {
let weekday = bcd_from_u8((datetime.weekday() as u8) + 1);
let hour = bcd_from_u8(datetime.hour() as u8);
let minute = bcd_from_u8(datetime.minute() as u8);
let second = bcd_from_u8(datetime.second() as u8);
let year = bcd_from_u8((datetime.year() - 2000) as u8);
let month = bcd_from_u8(datetime.month() as u8);
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))
}