mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2024-11-25 15:16:53 +01:00
alpha rust deployer
This commit is contained in:
parent
a3b2819803
commit
06f1799a21
174
.github/workflows/build.yml
vendored
174
.github/workflows/build.yml
vendored
@ -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
8
sw/deployer/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/target
|
||||
*.bin
|
||||
*.eep
|
||||
*.fla
|
||||
*.n64
|
||||
*.srm
|
||||
*.v64
|
||||
*.z64
|
803
sw/deployer/Cargo.lock
generated
Normal file
803
sw/deployer/Cargo.lock
generated
Normal 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
18
sw/deployer/Cargo.toml
Normal 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
27
sw/deployer/src/debug.rs
Normal 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
524
sw/deployer/src/main.rs
Normal 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
0
sw/deployer/src/n64.rs
Normal file
180
sw/deployer/src/sc64/cic.rs
Normal file
180
sw/deployer/src/sc64/cic.rs
Normal 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,
|
||||
])
|
||||
}
|
34
sw/deployer/src/sc64/error.rs
Normal file
34
sw/deployer/src/sc64/error.rs
Normal 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())
|
||||
}
|
||||
}
|
0
sw/deployer/src/sc64/firmware.rs
Normal file
0
sw/deployer/src/sc64/firmware.rs
Normal file
231
sw/deployer/src/sc64/link.rs
Normal file
231
sw/deployer/src/sc64/link.rs
Normal 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
672
sw/deployer/src/sc64/mod.rs
Normal 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(¶ms[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)
|
||||
}
|
553
sw/deployer/src/sc64/types.rs
Normal file
553
sw/deployer/src/sc64/types.rs
Normal 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;
|
75
sw/deployer/src/sc64/utils.rs
Normal file
75
sw/deployer/src/sc64/utils.rs
Normal 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))
|
||||
}
|
Loading…
Reference in New Issue
Block a user