mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2024-11-26 15:44:14 +01:00
Merge remote-tracking branch 'origin/main' into pyinstaller
This commit is contained in:
commit
e627e11bb7
29
.github/ISSUE_TEMPLATE.md
vendored
29
.github/ISSUE_TEMPLATE.md
vendored
@ -1,29 +0,0 @@
|
|||||||
## Expected Behavior
|
|
||||||
<!--- Provide a headline summary of the issue in the Title above -->
|
|
||||||
<!--- Tell us what should happen -->
|
|
||||||
|
|
||||||
## Current Behavior
|
|
||||||
<!--- Tell us what happens instead of the expected behavior -->
|
|
||||||
|
|
||||||
## Possible Solution
|
|
||||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
|
||||||
|
|
||||||
## Steps to Reproduce
|
|
||||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
|
||||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
4.
|
|
||||||
|
|
||||||
## Context (Environment)
|
|
||||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
|
||||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
|
||||||
|
|
||||||
<!--- Provide a general summary of the issue in the Title above -->
|
|
||||||
|
|
||||||
## Detailed Description
|
|
||||||
<!--- Provide a detailed description of the change or addition you are proposing -->
|
|
||||||
|
|
||||||
## Possible Implementation
|
|
||||||
<!--- Not obligatory, but suggest an idea for implementing addition or change -->
|
|
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Possible solution**
|
||||||
|
Not obligatory, but suggest a fix/reason for the bug.
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: GitHub Discussions
|
||||||
|
url: https://github.com/Polprzewodnikowy/SummerCart64/discussions
|
||||||
|
about: Please use for QUESTIONS, conversations or discussions.
|
||||||
|
- name: N64brew Discord
|
||||||
|
url: https://discord.gg/8xvRPqCAFC
|
||||||
|
about: Alternative channel for asking QUESTIONS.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
20
.github/PULL_REQUEST_TEMPLATE.md
vendored
20
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,20 +0,0 @@
|
|||||||
## Description
|
|
||||||
<!--- Provide a headline summary of your changes in the Title above -->
|
|
||||||
<!--- Describe your changes in detail -->
|
|
||||||
|
|
||||||
## Related Issue
|
|
||||||
<!--- This project only accepts pull requests related to open issues -->
|
|
||||||
<!--- If suggesting a change, please discuss it in an issue first -->
|
|
||||||
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
|
|
||||||
<!--- Please link to the issue here: -->
|
|
||||||
|
|
||||||
## Motivation and Context
|
|
||||||
<!--- Why is this change required? What problem does it solve? -->
|
|
||||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
|
||||||
|
|
||||||
## How Has This Been Tested?
|
|
||||||
<!--- Please describe in detail how you tested your changes. -->
|
|
||||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
|
||||||
<!--- see how your change affects other areas of the code, etc. -->
|
|
||||||
|
|
||||||
## Screenshots (if appropriate):
|
|
20
.github/PULL_REQUEST_TEMPLATE/default.md
vendored
Normal file
20
.github/PULL_REQUEST_TEMPLATE/default.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
**Description**
|
||||||
|
Provide a headline summary of your changes in the Title above.
|
||||||
|
Describe your changes in detail.
|
||||||
|
|
||||||
|
**Related Issue**
|
||||||
|
This project only accepts pull requests related to open issues.
|
||||||
|
If suggesting a change, please discuss it in an issue first.
|
||||||
|
If fixing a bug, there should be an issue describing it with steps to reproduce.
|
||||||
|
Please link to the issue here:
|
||||||
|
|
||||||
|
**Motivation and Context**
|
||||||
|
Why is this change required? What problem does it solve?
|
||||||
|
If it fixes an open issue, please link to the issue here.
|
||||||
|
|
||||||
|
**How Has This Been Tested?**
|
||||||
|
Please describe in detail how you tested your changes.
|
||||||
|
Include details of your testing environment, and the tests you ran to see how your change affects other areas of the code, etc.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help demonstrate your feature/bugfix.
|
8
.github/config.yml
vendored
8
.github/config.yml
vendored
@ -1,8 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: GitHub Discussions
|
|
||||||
url: https://github.com/Polprzewodnikowy/SummerCart64/discussions
|
|
||||||
about: Please use for QUESTIONS, conversations or discussions.
|
|
||||||
- name: N64brew Discord
|
|
||||||
url: https://discord.gg/8xvRPqCAFC
|
|
||||||
about: Alternative channel for asking QUESTIONS.
|
|
1
build.sh
1
build.sh
@ -5,7 +5,6 @@ set -e
|
|||||||
PACKAGE_FILE_NAME="SC64"
|
PACKAGE_FILE_NAME="SC64"
|
||||||
|
|
||||||
TOP_FILES=(
|
TOP_FILES=(
|
||||||
"./sw/pc/dd64.py"
|
|
||||||
"./sw/pc/primer.py"
|
"./sw/pc/primer.py"
|
||||||
"./sw/pc/requirements.txt"
|
"./sw/pc/requirements.txt"
|
||||||
"./sw/pc/sc64.py"
|
"./sw/pc/sc64.py"
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
- [Running 64DD games](#running-64dd-games)
|
- [Running 64DD games](#running-64dd-games)
|
||||||
- [Direct boot option](#direct-boot-option)
|
- [Direct boot option](#direct-boot-option)
|
||||||
- [Debug terminal](#debug-terminal)
|
- [Debug terminal](#debug-terminal)
|
||||||
|
- [LED blink patters](#led-blink-patters)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -24,9 +25,9 @@
|
|||||||
|
|
||||||
Keeping SC64 firmware up to date is highly recommended. `sc64.py` script is tightly coupled with specific firmware versions and will error out when it detects unsupported firmware version.
|
Keeping SC64 firmware up to date is highly recommended. `sc64.py` script is tightly coupled with specific firmware versions and will error out when it detects unsupported firmware version.
|
||||||
|
|
||||||
To download and backup current version of SC64 firmware run `python3 sc64.py --backup-firmware sc64_backup_package.bin`
|
To download and backup current version of SC64 firmware run `python3 sc64.py --backup-firmware sc64_firmware_backup.bin`
|
||||||
|
|
||||||
To update SC64 firmware run `python3 sc64.py --update-firmware sc64_update_package.bin`
|
To update SC64 firmware run `python3 sc64.py --update-firmware sc64_firmware.bin`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -71,3 +72,21 @@ Run `python3 sc64.py --boot direct-rom --rom path_to_rom.n64` to disable bootloa
|
|||||||
## Debug terminal
|
## Debug terminal
|
||||||
|
|
||||||
`sc64.py` supports UNFLoader protocol and has same functionality implemented as aforementioned program. Use argument `--debug` to activate it.
|
`sc64.py` supports UNFLoader protocol and has same functionality implemented as aforementioned program. Use argument `--debug` to activate it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## LED blink patters
|
||||||
|
|
||||||
|
LED on SC64 board can blink in certain situations. Most of them during normal use are related to SD card access. Here's list of blink patters meaning:
|
||||||
|
|
||||||
|
| Pattern | Meaning |
|
||||||
|
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| Nx [Short ON - Short OFF] | SD card is being accessed (initialization or data read/write) or save writeback is in progress |
|
||||||
|
| Nx [Medium ON - Long OFF] | CIC region didn't match, please power off console and power on again |
|
||||||
|
| 2x [Very short ON - Short OFF] | Pattern used during firmware update process, it means that specific part of firmware has started programming |
|
||||||
|
| 10x [Very short ON - Very short OFF] | Firmware has been successfully updated |
|
||||||
|
| 30x [Long ON - Long OFF] | There was serious problem during firmware update, device is most likely bricked |
|
||||||
|
|
||||||
|
Nx means that blink count is varied.
|
||||||
|
|
||||||
|
LED blinking on SD card access can be disabled through `sc64.py` script. Please refer to included help for option to change the LED behavior.
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
- [SC64 registers](#sc64-registers)
|
- [SC64 registers](#sc64-registers)
|
||||||
- [`0x1FFF_0000`: **STATUS/COMMAND**](#0x1fff_0000-statuscommand)
|
- [`0x1FFF_0000`: **STATUS/COMMAND**](#0x1fff_0000-statuscommand)
|
||||||
- [`0x1FFF_0004`: **DATA0** and `0x1FFF_0008`: **DATA1**](#0x1fff_0004-data0-and-0x1fff_0008-data1)
|
- [`0x1FFF_0004`: **DATA0** and `0x1FFF_0008`: **DATA1**](#0x1fff_0004-data0-and-0x1fff_0008-data1)
|
||||||
- [`0x1FFF_000C`: **VERSION**](#0x1fff_000c-version)
|
- [`0x1FFF_000C`: **IDENTIFIER**](#0x1fff_000c-identifier)
|
||||||
- [`0x1FFF_0010`: **KEY**](#0x1fff_0010-key)
|
- [`0x1FFF_0010`: **KEY**](#0x1fff_0010-key)
|
||||||
- [Command execution flow](#command-execution-flow)
|
- [Command execution flow](#command-execution-flow)
|
||||||
|
|
||||||
@ -88,11 +88,11 @@ Protocol is command based with support for up to 256 diferrent commands and two
|
|||||||
Support for interrupts is provided but currently no command relies on it, 64DD IRQ is handled separately.
|
Support for interrupts is provided but currently no command relies on it, 64DD IRQ is handled separately.
|
||||||
|
|
||||||
| name | address | size | access | usage |
|
| name | address | size | access | usage |
|
||||||
| ------------------ | ------------- | ------- | ------ | -------------------------------- |
|
| ------------------ | ------------- | ------- | ------ | ---------------------------------- |
|
||||||
| **STATUS/COMMAND** | `0x1FFF_0000` | 4 bytes | RW | Command execution and status |
|
| **STATUS/COMMAND** | `0x1FFF_0000` | 4 bytes | RW | Command execution and status |
|
||||||
| **DATA0** | `0x1FFF_0004` | 4 bytes | RW | Command argument/result 0 |
|
| **DATA0** | `0x1FFF_0004` | 4 bytes | RW | Command argument/result 0 |
|
||||||
| **DATA1** | `0x1FFF_0008` | 4 bytes | RW | Command argument/result 1 |
|
| **DATA1** | `0x1FFF_0008` | 4 bytes | RW | Command argument/result 1 |
|
||||||
| **VERSION** | `0x1FFF_000C` | 4 bytes | RW | Hardware version and IRQ clear |
|
| **IDENTIFIER** | `0x1FFF_000C` | 4 bytes | RW | Flashcart identifier and IRQ clear |
|
||||||
| **KEY** | `0x1FFF_0010` | 4 bytes | W | SC64 register access lock/unlock |
|
| **KEY** | `0x1FFF_0010` | 4 bytes | W | SC64 register access lock/unlock |
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -121,11 +121,11 @@ Note: Result is valid only when command has executed and `CMD_BUSY` bit is reset
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### `0x1FFF_000C`: **VERSION**
|
#### `0x1FFF_000C`: **IDENTIFIER**
|
||||||
|
|
||||||
| name | bits | access | meaning |
|
| name | bits | access | meaning |
|
||||||
| --------- | ------ | ------ | --------------------------------------------- |
|
| ------------ | ------ | ------ | ------------------------------------------------- |
|
||||||
| `VERSION` | [31:0] | RW | Hardware version (ASCII `SCv2`) and IRQ clear |
|
| `IDENTIFIER` | [31:0] | RW | Flashcart identifier (ASCII `SCv2`) and IRQ clear |
|
||||||
|
|
||||||
Note: Writing any value to this register will clear pending flashcart interrupt.
|
Note: Writing any value to this register will clear pending flashcart interrupt.
|
||||||
|
|
||||||
|
@ -5,24 +5,24 @@
|
|||||||
## USB commands
|
## USB commands
|
||||||
|
|
||||||
| id | name | arg0 | arg1 | data | response | description |
|
| id | name | arg0 | arg1 | data | response | description |
|
||||||
| --- | ---------------------- | ------------ | ------------ | ---- | ---------------- | --------------------------------------------------- |
|
| --- | ---------------------- | ------------ | ------------ | ---- | ---------------- | ------------------------------------------------------------- |
|
||||||
| `v` | **HW_VERSION_GET** | --- | --- | --- | hw_version | Get HW version |
|
| `v` | **IDENTIFIER_GET** | --- | --- | --- | identifier | Get flashcart identifier `SCv2` |
|
||||||
| `V` | **API_VERSION_GET** | --- | --- | --- | api_version | Get USB command API version |
|
| `V` | **VERSION_GET** | --- | --- | --- | version | Get flashcart firmware version |
|
||||||
| `R` | **STATE_RESET** | --- | --- | --- | --- | Reset entire flashcart state |
|
| `R` | **STATE_RESET** | --- | --- | --- | --- | Reset flashcart state (CIC params and config options) |
|
||||||
| `B` | **CIC_PARAMS_SET** | cic_params_0 | cic_params_1 | --- | --- | Set CIC disable/mode/seed/checksum |
|
| `B` | **CIC_PARAMS_SET** | cic_params_0 | cic_params_1 | --- | --- | Set CIC emulation parameters (disable/seed/checksum) |
|
||||||
| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option |
|
| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option |
|
||||||
| `C` | **CONFIG_SET** | config_id | new_value | --- | --- | Set config option |
|
| `C` | **CONFIG_SET** | config_id | new_value | --- | --- | Set config option |
|
||||||
| `a` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting |
|
| `a` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting option |
|
||||||
| `A` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting |
|
| `A` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting option |
|
||||||
| `t` | **TIME_GET** | --- | --- | --- | time | Get current RTC value |
|
| `t` | **TIME_GET** | --- | --- | --- | time | Get current RTC value |
|
||||||
| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set RTC value |
|
| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set new RTC value |
|
||||||
| `m` | **MEMORY_READ** | address | length | --- | data | Read data from specified memory address |
|
| `m` | **MEMORY_READ** | address | length | --- | data | Read data from specified memory address |
|
||||||
| `M` | **MEMORY_WRITE** | address | length | data | --- | Write data to specified memory address |
|
| `M` | **MEMORY_WRITE** | address | length | data | --- | Write data to specified memory address |
|
||||||
|
| `U` | **USB_WRITE** | type | length | data | N/A | Send data to be received by app running on N64 (no response!) |
|
||||||
| `D` | **DD_SET_BLOCK_READY** | success | --- | --- | --- | Notify flashcart about 64DD block readiness |
|
| `D` | **DD_SET_BLOCK_READY** | success | --- | --- | --- | Notify flashcart about 64DD block readiness |
|
||||||
| `U` | **USB_WRITE** | type | length | data | N/A | Send data to be received by app running on N64 |
|
| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size |
|
||||||
|
| `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase |
|
||||||
| `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address |
|
| `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address |
|
||||||
| `F` | **FIRMWARE_UPDATE** | address | length | --- | status | Update firmware from specified memory address |
|
| `F` | **FIRMWARE_UPDATE** | address | length | --- | status | Update firmware from specified memory address |
|
||||||
| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / get flash block erase size |
|
|
||||||
| `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase |
|
|
||||||
| `?` | **DEBUG_GET** | --- | --- | --- | debug_data | Get internal FPGA debug info |
|
| `?` | **DEBUG_GET** | --- | --- | --- | debug_data | Get internal FPGA debug info |
|
||||||
| `%` | **STACK_USAGE_GET** | --- | --- | --- | stack_usage | Get per task stack usage |
|
| `%` | **STACK_USAGE_GET** | --- | --- | --- | stack_usage | Get per task stack usage |
|
||||||
|
@ -6,14 +6,14 @@
|
|||||||
|
|
||||||
| id | name | arg0 | arg1 | rsp0 | rsp1 | description |
|
| id | name | arg0 | arg1 | rsp0 | rsp1 | description |
|
||||||
| --- | --------------------- | ---------- | ------------ | ---------------- | -------------- | -------------------------------------------------- |
|
| --- | --------------------- | ---------- | ------------ | ---------------- | -------------- | -------------------------------------------------- |
|
||||||
| `v` | **HW_VERSION_GET** | --- | --- | hw_version | --- | Get HW version |
|
| `v` | **IDENTIFIER_GET** | --- | --- | identifier | --- | Get flashcart identifier `SCv2` |
|
||||||
| `V` | **API_VERSION_GET** | --- | --- | api_version | --- | Get N64 command API version |
|
| `V` | **VERSION_GET** | --- | --- | version | --- | Get flashcart firmware version |
|
||||||
| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option |
|
| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option |
|
||||||
| `C` | **CONFIG_SET** | config_id | new_value | --- | previous_value | Set config option and get previous value |
|
| `C` | **CONFIG_SET** | config_id | new_value | --- | previous_value | Set config option and get previous value |
|
||||||
| `c` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting option |
|
| `c` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting option |
|
||||||
| `C` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting option |
|
| `C` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting option |
|
||||||
| `t` | **TIME_GET** | --- | --- | time_0 | time_1 | Get current RTC value |
|
| `t` | **TIME_GET** | --- | --- | time_0 | time_1 | Get current RTC value |
|
||||||
| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set RTC value |
|
| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set new RTC value |
|
||||||
| `m` | **USB_READ** | pi_address | length | --- | --- | Receive data from USB to flashcart |
|
| `m` | **USB_READ** | pi_address | length | --- | --- | Receive data from USB to flashcart |
|
||||||
| `M` | **USB_WRITE** | pi_address | length/type | --- | --- | Send data from from flashcart to USB |
|
| `M` | **USB_WRITE** | pi_address | length/type | --- | --- | Send data from from flashcart to USB |
|
||||||
| `u` | **USB_READ_STATUS** | --- | --- | read_status/type | length | Get USB read status and type/length |
|
| `u` | **USB_READ_STATUS** | --- | --- | read_status/type | length | Get USB read status and type/length |
|
||||||
@ -27,4 +27,3 @@
|
|||||||
| `K` | **FLASH_PROGRAM** | pi_address | length | --- | --- | Program flash with bytes loaded into data buffer |
|
| `K` | **FLASH_PROGRAM** | pi_address | length | --- | --- | Program flash with bytes loaded into data buffer |
|
||||||
| `p` | **FLASH_WAIT_BUSY** | wait | --- | erase_block_size | --- | Wait until flash ready / get block erase size |
|
| `p` | **FLASH_WAIT_BUSY** | wait | --- | erase_block_size | --- | Wait until flash ready / get block erase size |
|
||||||
| `P` | **FLASH_ERASE_BLOCK** | pi_address | --- | --- | --- | Start flash block erase |
|
| `P` | **FLASH_ERASE_BLOCK** | pi_address | --- | --- | --- | Start flash block erase |
|
||||||
| `?` | **DEBUG_GET** | --- | --- | debug_data_0 | debug_data_1 | Get internal FPGA debug info |
|
|
||||||
|
@ -57,6 +57,8 @@ There are no special requirements for soldering components to board. All chips a
|
|||||||
|
|
||||||
### **Initial programming**
|
### **Initial programming**
|
||||||
|
|
||||||
|
**Please read the following instructions carefully before proceeding with programming.**
|
||||||
|
|
||||||
For initial programming you are going to need a PC and a USB to UART (serial) adapter (3.3V signaling is required). These steps assume you are using modern Windows OS (version 10 or higher).
|
For initial programming you are going to need a PC and a USB to UART (serial) adapter (3.3V signaling is required). These steps assume you are using modern Windows OS (version 10 or higher).
|
||||||
|
|
||||||
As for software here's list of required applications:
|
As for software here's list of required applications:
|
||||||
@ -83,11 +85,11 @@ Second, program FPGA, microcontroller and Flash memory:
|
|||||||
4. Check in device manager which port number `COMx` is assigned to serial adapter
|
4. Check in device manager which port number `COMx` is assigned to serial adapter
|
||||||
5. Connect SC64 board to the PC with USB-C cable (***IMPORTANT:*** connect it to the same computer as serial adapter)
|
5. Connect SC64 board to the PC with USB-C cable (***IMPORTANT:*** connect it to the same computer as serial adapter)
|
||||||
6. Locate `primer.py` script in root folder
|
6. Locate `primer.py` script in root folder
|
||||||
7. Make sure these files are located in the same folder as `primer.py` script: `requirements.txt`, `sc64.py`, `dd64.py`, `sc64_firmware.bin`
|
7. Make sure these files are located in the same folder as `primer.py` script: `requirements.txt`, `sc64.py`, `sc64_firmware.bin`
|
||||||
8. Run `pip3 install -r requirements.txt` to install required python packages
|
8. Run `pip3 install -r requirements.txt` to install required python packages
|
||||||
9. Run `python3 primer.py COMx sc64_firmware.bin` (replace `COMx` with port located in step **4**)
|
9. Run `python3 primer.py COMx sc64_firmware.bin` (replace `COMx` with port located in step **4**)
|
||||||
10. Follow the instructions on the screen
|
10. Follow the instructions on the screen
|
||||||
11. Wait until programming process has finished
|
11. Wait until programming process has finished (**DO NOT STOP PROGRAMMING PROCESS OR DISCONNECT SC64 BOARD FROM PC**, doing so might irrecoverably break programming through UART header and you would need to program microcontroller, FPGA and bootloader with separate dedicated programming interfaces through *Tag-Connect* connector on the PCB)
|
||||||
|
|
||||||
Congratulations! Your SC64 flashcart should be ready for use!
|
Congratulations! Your SC64 flashcart should be ready for use!
|
||||||
|
|
||||||
|
@ -332,7 +332,7 @@ module mcu_top (
|
|||||||
REG_CFG_DATA_0,
|
REG_CFG_DATA_0,
|
||||||
REG_CFG_DATA_1,
|
REG_CFG_DATA_1,
|
||||||
REG_CFG_CMD,
|
REG_CFG_CMD,
|
||||||
REG_CFG_VERSION,
|
REG_CFG_IDENTIFIER,
|
||||||
REG_FLASHRAM_SCR,
|
REG_FLASHRAM_SCR,
|
||||||
REG_FLASH_SCR,
|
REG_FLASH_SCR,
|
||||||
REG_RTC_SCR,
|
REG_RTC_SCR,
|
||||||
@ -363,7 +363,7 @@ module mcu_top (
|
|||||||
|
|
||||||
logic bootloader_skip;
|
logic bootloader_skip;
|
||||||
|
|
||||||
assign n64_scb.cfg_version = 32'h53437632;
|
assign n64_scb.cfg_identifier = 32'h53437632;
|
||||||
|
|
||||||
logic dd_bm_ack;
|
logic dd_bm_ack;
|
||||||
|
|
||||||
@ -460,8 +460,8 @@ module mcu_top (
|
|||||||
};
|
};
|
||||||
end
|
end
|
||||||
|
|
||||||
REG_CFG_VERSION: begin
|
REG_CFG_IDENTIFIER: begin
|
||||||
reg_rdata <= n64_scb.cfg_version;
|
reg_rdata <= n64_scb.cfg_identifier;
|
||||||
end
|
end
|
||||||
|
|
||||||
REG_FLASHRAM_SCR: begin
|
REG_FLASHRAM_SCR: begin
|
||||||
|
@ -16,8 +16,8 @@ module n64_cfg (
|
|||||||
REG_DATA_0_L,
|
REG_DATA_0_L,
|
||||||
REG_DATA_1_H,
|
REG_DATA_1_H,
|
||||||
REG_DATA_1_L,
|
REG_DATA_1_L,
|
||||||
REG_VERSION_H,
|
REG_IDENTIFIER_H,
|
||||||
REG_VERSION_L,
|
REG_IDENTIFIER_L,
|
||||||
REG_KEY_H,
|
REG_KEY_H,
|
||||||
REG_KEY_L
|
REG_KEY_L
|
||||||
} e_reg;
|
} e_reg;
|
||||||
@ -39,8 +39,8 @@ module n64_cfg (
|
|||||||
REG_DATA_0_L: reg_bus.rdata = n64_scb.cfg_wdata[0][15:0];
|
REG_DATA_0_L: reg_bus.rdata = n64_scb.cfg_wdata[0][15:0];
|
||||||
REG_DATA_1_H: reg_bus.rdata = n64_scb.cfg_wdata[1][31:16];
|
REG_DATA_1_H: reg_bus.rdata = n64_scb.cfg_wdata[1][31:16];
|
||||||
REG_DATA_1_L: reg_bus.rdata = n64_scb.cfg_wdata[1][15:0];
|
REG_DATA_1_L: reg_bus.rdata = n64_scb.cfg_wdata[1][15:0];
|
||||||
REG_VERSION_H: reg_bus.rdata = n64_scb.cfg_version[31:16];
|
REG_IDENTIFIER_H: reg_bus.rdata = n64_scb.cfg_identifier[31:16];
|
||||||
REG_VERSION_L: reg_bus.rdata = n64_scb.cfg_version[15:0];
|
REG_IDENTIFIER_L: reg_bus.rdata = n64_scb.cfg_identifier[15:0];
|
||||||
REG_KEY_H: reg_bus.rdata = 16'd0;
|
REG_KEY_H: reg_bus.rdata = 16'd0;
|
||||||
REG_KEY_L: reg_bus.rdata = 16'd0;
|
REG_KEY_L: reg_bus.rdata = 16'd0;
|
||||||
endcase
|
endcase
|
||||||
@ -83,7 +83,7 @@ module n64_cfg (
|
|||||||
REG_DATA_0_L: n64_scb.cfg_rdata[0][15:0] <= reg_bus.wdata;
|
REG_DATA_0_L: n64_scb.cfg_rdata[0][15:0] <= reg_bus.wdata;
|
||||||
REG_DATA_1_H: n64_scb.cfg_rdata[1][31:16] <= reg_bus.wdata;
|
REG_DATA_1_H: n64_scb.cfg_rdata[1][31:16] <= reg_bus.wdata;
|
||||||
REG_DATA_1_L: n64_scb.cfg_rdata[1][15:0] <= reg_bus.wdata;
|
REG_DATA_1_L: n64_scb.cfg_rdata[1][15:0] <= reg_bus.wdata;
|
||||||
REG_VERSION_H: irq <= 1'b0;
|
REG_IDENTIFIER_H: irq <= 1'b0;
|
||||||
REG_KEY_H, REG_KEY_L: begin
|
REG_KEY_H, REG_KEY_L: begin
|
||||||
lock_sequence_counter <= lock_sequence_counter + 1'd1;
|
lock_sequence_counter <= lock_sequence_counter + 1'd1;
|
||||||
if (reg_bus.wdata != 16'hFFFF) begin
|
if (reg_bus.wdata != 16'hFFFF) begin
|
||||||
|
@ -51,7 +51,7 @@ interface n64_scb ();
|
|||||||
logic [7:0] cfg_cmd;
|
logic [7:0] cfg_cmd;
|
||||||
logic [31:0] cfg_rdata [0:1];
|
logic [31:0] cfg_rdata [0:1];
|
||||||
logic [31:0] cfg_wdata [0:1];
|
logic [31:0] cfg_wdata [0:1];
|
||||||
logic [31:0] cfg_version;
|
logic [31:0] cfg_identifier;
|
||||||
|
|
||||||
logic [15:0] save_count;
|
logic [15:0] save_count;
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ interface n64_scb ();
|
|||||||
input cfg_cmd,
|
input cfg_cmd,
|
||||||
input cfg_rdata,
|
input cfg_rdata,
|
||||||
output cfg_wdata,
|
output cfg_wdata,
|
||||||
output cfg_version,
|
output cfg_identifier,
|
||||||
|
|
||||||
input save_count,
|
input save_count,
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ interface n64_scb ();
|
|||||||
output cfg_cmd,
|
output cfg_cmd,
|
||||||
output cfg_rdata,
|
output cfg_rdata,
|
||||||
input cfg_wdata,
|
input cfg_wdata,
|
||||||
input cfg_version
|
input cfg_identifier
|
||||||
);
|
);
|
||||||
|
|
||||||
modport save_counter (
|
modport save_counter (
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
io32_t SR_CMD;
|
io32_t SR_CMD;
|
||||||
io32_t DATA[2];
|
io32_t DATA[2];
|
||||||
io32_t VERSION;
|
io32_t IDENTIFIER;
|
||||||
io32_t KEY;
|
io32_t KEY;
|
||||||
} sc64_regs_t;
|
} sc64_regs_t;
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ typedef struct {
|
|||||||
#define SC64_SR_CMD_ERROR (1 << 30)
|
#define SC64_SR_CMD_ERROR (1 << 30)
|
||||||
#define SC64_SR_CPU_BUSY (1 << 31)
|
#define SC64_SR_CPU_BUSY (1 << 31)
|
||||||
|
|
||||||
#define SC64_VERSION_2 (0x53437632)
|
#define SC64_V2_IDENTIFIER (0x53437632)
|
||||||
|
|
||||||
#define SC64_KEY_RESET (0x00000000UL)
|
#define SC64_KEY_RESET (0x00000000UL)
|
||||||
#define SC64_KEY_UNLOCK_1 (0x5F554E4CUL)
|
#define SC64_KEY_UNLOCK_1 (0x5F554E4CUL)
|
||||||
@ -24,8 +24,8 @@ typedef struct {
|
|||||||
#define SC64_KEY_LOCK (0xFFFFFFFFUL)
|
#define SC64_KEY_LOCK (0xFFFFFFFFUL)
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SC64_CMD_HW_VERSION_GET = 'v',
|
SC64_CMD_IDENTIFIER_GET = 'v',
|
||||||
SC64_CMD_API_VERSION_GET = 'V',
|
SC64_CMD_VERSION_GET = 'V',
|
||||||
SC64_CMD_CONFIG_GET = 'c',
|
SC64_CMD_CONFIG_GET = 'c',
|
||||||
SC64_CMD_CONFIG_SET = 'C',
|
SC64_CMD_CONFIG_SET = 'C',
|
||||||
SC64_CMD_SETTING_GET = 'a',
|
SC64_CMD_SETTING_GET = 'a',
|
||||||
@ -45,7 +45,6 @@ typedef enum {
|
|||||||
SC64_CMD_FLASH_PROGRAM = 'K',
|
SC64_CMD_FLASH_PROGRAM = 'K',
|
||||||
SC64_CMD_FLASH_WAIT_BUSY = 'p',
|
SC64_CMD_FLASH_WAIT_BUSY = 'p',
|
||||||
SC64_CMD_FLASH_ERASE_BLOCK = 'P',
|
SC64_CMD_FLASH_ERASE_BLOCK = 'P',
|
||||||
SC64_CMD_DEBUG_GET = '?',
|
|
||||||
} cmd_id_t;
|
} cmd_id_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@ -98,8 +97,8 @@ void sc64_lock (void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool sc64_check_presence (void) {
|
bool sc64_check_presence (void) {
|
||||||
uint32_t version = pi_io_read(&SC64_REGS->VERSION);
|
uint32_t identifier = pi_io_read(&SC64_REGS->IDENTIFIER);
|
||||||
if (version == SC64_VERSION_2) {
|
if (identifier == SC64_V2_IDENTIFIER) {
|
||||||
sc64_wait_cpu_busy();
|
sc64_wait_cpu_busy();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -114,7 +113,7 @@ bool sc64_irq_pending (void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sc64_irq_clear (void) {
|
void sc64_irq_clear (void) {
|
||||||
pi_io_write(&SC64_REGS->VERSION, 0);
|
pi_io_write(&SC64_REGS->IDENTIFIER, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t sc64_get_config (sc64_cfg_id_t id) {
|
uint32_t sc64_get_config (sc64_cfg_id_t id) {
|
||||||
|
@ -7,20 +7,31 @@
|
|||||||
|
|
||||||
static void test_rtc (void) {
|
static void test_rtc (void) {
|
||||||
sc64_rtc_time_t t;
|
sc64_rtc_time_t t;
|
||||||
const char *weekdays[8] = { "", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
|
const char *weekdays[8] = {
|
||||||
|
"",
|
||||||
|
"Monday",
|
||||||
|
"Tuesday",
|
||||||
|
"Wednesday",
|
||||||
|
"Thursday",
|
||||||
|
"Friday",
|
||||||
|
"Saturday",
|
||||||
|
"Sunday",
|
||||||
|
};
|
||||||
|
|
||||||
sc64_get_time(&t);
|
sc64_get_time(&t);
|
||||||
|
|
||||||
display_printf("RTC current time:\n ");
|
display_printf("RTC current time:\n ");
|
||||||
|
display_printf("%04d-%02d-%02d", 2000 + FROM_BCD(t.year), FROM_BCD(t.month), FROM_BCD(t.day));
|
||||||
|
display_printf("T");
|
||||||
display_printf("%02d:%02d:%02d", FROM_BCD(t.hour), FROM_BCD(t.minute), FROM_BCD(t.second));
|
display_printf("%02d:%02d:%02d", FROM_BCD(t.hour), FROM_BCD(t.minute), FROM_BCD(t.second));
|
||||||
display_printf(" %s ", weekdays[FROM_BCD(t.weekday)]);
|
display_printf(" (%s)", weekdays[FROM_BCD(t.weekday)]);
|
||||||
display_printf("%d.%02d.%04d", FROM_BCD(t.day), FROM_BCD(t.month), 2000 + FROM_BCD(t.year));
|
|
||||||
display_printf("\n");
|
display_printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_sd_card (void) {
|
static void test_sd_card (void) {
|
||||||
sc64_sd_card_status_t card_status;
|
sc64_sd_card_status_t card_status;
|
||||||
uint8_t card_info[32] __attribute__((aligned(8)));
|
uint8_t card_info[32] __attribute__((aligned(8)));
|
||||||
|
uint8_t sector[512] __attribute__((aligned(8)));
|
||||||
|
|
||||||
card_status = sc64_sd_card_get_status();
|
card_status = sc64_sd_card_get_status();
|
||||||
|
|
||||||
@ -66,6 +77,25 @@ static void test_sd_card (void) {
|
|||||||
for (int i = 16; i < 32; i++) {
|
for (int i = 16; i < 32; i++) {
|
||||||
display_printf(" %c ", card_info[i] >= ' ' ? card_info[i] : 0xFF);
|
display_printf(" %c ", card_info[i] >= ' ' ? card_info[i] : 0xFF);
|
||||||
}
|
}
|
||||||
|
display_printf("\n");
|
||||||
|
|
||||||
|
if (sc64_sd_read_sectors((void *) (SC64_BUFFERS->BUFFER), 0, 1)) {
|
||||||
|
display_printf("SD card read sector 0 error!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pi_dma_read((io32_t *) (SC64_BUFFERS->BUFFER), sector, sizeof(sector));
|
||||||
|
|
||||||
|
display_printf("Sector 0 (0x1BE-0x1DD), partition entry 1/2:\n 0x");
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
display_printf("%02X ", sector[0x1BE + i]);
|
||||||
|
}
|
||||||
|
display_printf("\n 0x");
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
display_printf("%02X ", sector[0x1CE + i]);
|
||||||
|
}
|
||||||
|
display_printf("\n");
|
||||||
|
display_printf(" Boot signature: 0x%02X%02X\n", sector[510], sector[511]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -221,8 +221,8 @@ static bool cfg_set_save_type (save_type_t save_type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint32_t cfg_get_version (void) {
|
uint32_t cfg_get_identifier (void) {
|
||||||
return fpga_reg_get(REG_CFG_VERSION);
|
return fpga_reg_get(REG_CFG_IDENTIFIER);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cfg_query (uint32_t *args) {
|
bool cfg_query (uint32_t *args) {
|
||||||
@ -468,11 +468,11 @@ void cfg_process (void) {
|
|||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case 'v':
|
case 'v':
|
||||||
args[0] = cfg_get_version();
|
args[0] = cfg_get_identifier();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'V':
|
case 'V':
|
||||||
args[0] = version_api(API_N64);
|
args[0] = version_firmware();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'c':
|
case 'c':
|
||||||
@ -666,11 +666,6 @@ void cfg_process (void) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '?':
|
|
||||||
args[0] = fpga_reg_get(REG_DEBUG_0);
|
|
||||||
args[1] = fpga_reg_get(REG_DEBUG_1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
cfg_set_error(CFG_ERROR_UNKNOWN_CMD);
|
cfg_set_error(CFG_ERROR_UNKNOWN_CMD);
|
||||||
return;
|
return;
|
||||||
|
@ -16,7 +16,7 @@ typedef enum {
|
|||||||
} save_type_t;
|
} save_type_t;
|
||||||
|
|
||||||
|
|
||||||
uint32_t cfg_get_version (void);
|
uint32_t cfg_get_identifier (void);
|
||||||
bool cfg_query (uint32_t *args);
|
bool cfg_query (uint32_t *args);
|
||||||
bool cfg_update (uint32_t *args);
|
bool cfg_update (uint32_t *args);
|
||||||
bool cfg_query_setting (uint32_t *args);
|
bool cfg_query_setting (uint32_t *args);
|
||||||
|
@ -28,7 +28,7 @@ typedef enum {
|
|||||||
REG_CFG_DATA_0,
|
REG_CFG_DATA_0,
|
||||||
REG_CFG_DATA_1,
|
REG_CFG_DATA_1,
|
||||||
REG_CFG_CMD,
|
REG_CFG_CMD,
|
||||||
REG_CFG_VERSION,
|
REG_CFG_IDENTIFIER,
|
||||||
REG_FLASHRAM_SCR,
|
REG_FLASHRAM_SCR,
|
||||||
REG_FLASH_SCR,
|
REG_FLASH_SCR,
|
||||||
REG_RTC_SCR,
|
REG_RTC_SCR,
|
||||||
|
@ -33,7 +33,8 @@
|
|||||||
#define SWITCH_FUNCTION_GROUP_1_HS (1 << 1)
|
#define SWITCH_FUNCTION_GROUP_1_HS (1 << 1)
|
||||||
|
|
||||||
#define DAT_BLOCK_MAX_COUNT (256)
|
#define DAT_BLOCK_MAX_COUNT (256)
|
||||||
#define DAT_TIMEOUT_MS (1000)
|
#define DAT_TIMEOUT_INIT_MS (2000)
|
||||||
|
#define DAT_TIMEOUT_DATA_MS (5000)
|
||||||
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@ -300,7 +301,7 @@ bool sd_card_init (void) {
|
|||||||
sd_card_deinit();
|
sd_card_deinit();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
sd_dat_wait(DAT_TIMEOUT_MS);
|
sd_dat_wait(DAT_TIMEOUT_INIT_MS);
|
||||||
if (sd_did_timeout()) {
|
if (sd_did_timeout()) {
|
||||||
sd_card_deinit();
|
sd_card_deinit();
|
||||||
return true;
|
return true;
|
||||||
@ -318,7 +319,7 @@ bool sd_card_init (void) {
|
|||||||
sd_card_deinit();
|
sd_card_deinit();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
sd_dat_wait(DAT_TIMEOUT_MS);
|
sd_dat_wait(DAT_TIMEOUT_INIT_MS);
|
||||||
if (sd_did_timeout()) {
|
if (sd_did_timeout()) {
|
||||||
sd_card_deinit();
|
sd_card_deinit();
|
||||||
return true;
|
return true;
|
||||||
@ -382,14 +383,11 @@ bool sd_write_sectors (uint32_t address, uint32_t sector, uint32_t count) {
|
|||||||
while (count > 0) {
|
while (count > 0) {
|
||||||
uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count);
|
uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count);
|
||||||
led_blink_act();
|
led_blink_act();
|
||||||
if (sd_cmd(23, blocks, RSP_R1, NULL)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (sd_cmd(25, sector, RSP_R1, NULL)) {
|
if (sd_cmd(25, sector, RSP_R1, NULL)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
sd_dat_prepare(address, blocks, DAT_WRITE);
|
sd_dat_prepare(address, blocks, DAT_WRITE);
|
||||||
if (sd_dat_wait(DAT_TIMEOUT_MS)) {
|
if (sd_dat_wait(DAT_TIMEOUT_DATA_MS)) {
|
||||||
sd_dat_abort();
|
sd_dat_abort();
|
||||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||||
return true;
|
return true;
|
||||||
@ -416,20 +414,17 @@ bool sd_read_sectors (uint32_t address, uint32_t sector, uint32_t count) {
|
|||||||
uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count);
|
uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count);
|
||||||
led_blink_act();
|
led_blink_act();
|
||||||
sd_dat_prepare(address, blocks, DAT_READ);
|
sd_dat_prepare(address, blocks, DAT_READ);
|
||||||
if (sd_cmd(23, blocks, RSP_R1, NULL)) {
|
|
||||||
sd_dat_abort();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (sd_cmd(18, sector, RSP_R1, NULL)) {
|
if (sd_cmd(18, sector, RSP_R1, NULL)) {
|
||||||
sd_dat_abort();
|
sd_dat_abort();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (sd_dat_wait(DAT_TIMEOUT_MS)) {
|
if (sd_dat_wait(DAT_TIMEOUT_DATA_MS)) {
|
||||||
if (sd_did_timeout()) {
|
if (sd_did_timeout()) {
|
||||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||||
address += (blocks * SD_SECTOR_SIZE);
|
address += (blocks * SD_SECTOR_SIZE);
|
||||||
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
|
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
|
||||||
count -= blocks;
|
count -= blocks;
|
||||||
|
@ -160,14 +160,14 @@ static void usb_rx_process (void) {
|
|||||||
p.rx_state = RX_STATE_IDLE;
|
p.rx_state = RX_STATE_IDLE;
|
||||||
p.response_pending = true;
|
p.response_pending = true;
|
||||||
p.response_info.data_length = 4;
|
p.response_info.data_length = 4;
|
||||||
p.response_info.data[0] = cfg_get_version();
|
p.response_info.data[0] = cfg_get_identifier();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'V':
|
case 'V':
|
||||||
p.rx_state = RX_STATE_IDLE;
|
p.rx_state = RX_STATE_IDLE;
|
||||||
p.response_pending = true;
|
p.response_pending = true;
|
||||||
p.response_info.data_length = 4;
|
p.response_info.data_length = 4;
|
||||||
p.response_info.data[0] = version_api(API_USB);
|
p.response_info.data[0] = version_firmware();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'R':
|
case 'R':
|
||||||
@ -247,12 +247,6 @@ static void usb_rx_process (void) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'D':
|
|
||||||
dd_set_block_ready(p.rx_args[0] == 0);
|
|
||||||
p.rx_state = RX_STATE_IDLE;
|
|
||||||
p.response_pending = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'U':
|
case 'U':
|
||||||
if ((p.read_length > 0) && usb_dma_ready()) {
|
if ((p.read_length > 0) && usb_dma_ready()) {
|
||||||
uint32_t length = (p.read_length > p.rx_args[1]) ? p.rx_args[1] : p.read_length;
|
uint32_t length = (p.read_length > p.rx_args[1]) ? p.rx_args[1] : p.read_length;
|
||||||
@ -274,6 +268,28 @@ static void usb_rx_process (void) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'D':
|
||||||
|
dd_set_block_ready(p.rx_args[0] == 0);
|
||||||
|
p.rx_state = RX_STATE_IDLE;
|
||||||
|
p.response_pending = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
if (p.rx_args[0]) {
|
||||||
|
flash_wait_busy();
|
||||||
|
}
|
||||||
|
p.rx_state = RX_STATE_IDLE;
|
||||||
|
p.response_pending = true;
|
||||||
|
p.response_info.data_length = 4;
|
||||||
|
p.response_info.data[0] = FLASH_ERASE_BLOCK_SIZE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'P':
|
||||||
|
p.response_error = flash_erase_block(p.rx_args[0]);
|
||||||
|
p.rx_state = RX_STATE_IDLE;
|
||||||
|
p.response_pending = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case 'f': {
|
case 'f': {
|
||||||
bool rom_write_enable_restore = cfg_set_rom_write_enable(false);
|
bool rom_write_enable_restore = cfg_set_rom_write_enable(false);
|
||||||
p.response_info.data[0] = update_backup(p.rx_args[0], &p.response_info.data[1]);
|
p.response_info.data[0] = update_backup(p.rx_args[0], &p.response_info.data[1]);
|
||||||
@ -300,22 +316,6 @@ static void usb_rx_process (void) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'p':
|
|
||||||
if (p.rx_args[0]) {
|
|
||||||
flash_wait_busy();
|
|
||||||
}
|
|
||||||
p.rx_state = RX_STATE_IDLE;
|
|
||||||
p.response_pending = true;
|
|
||||||
p.response_info.data_length = 4;
|
|
||||||
p.response_info.data[0] = FLASH_ERASE_BLOCK_SIZE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'P':
|
|
||||||
p.response_error = flash_erase_block(p.rx_args[0]);
|
|
||||||
p.rx_state = RX_STATE_IDLE;
|
|
||||||
p.response_pending = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
p.rx_state = RX_STATE_IDLE;
|
p.rx_state = RX_STATE_IDLE;
|
||||||
p.response_pending = true;
|
p.response_pending = true;
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
|
|
||||||
#define VERSION_API_USB (2)
|
#define VERSION_MAJOR (2)
|
||||||
#define VERSION_API_N64 (2)
|
#define VERSION_MINOR (12)
|
||||||
|
|
||||||
|
|
||||||
uint32_t version_api (version_api_type_t type) {
|
uint32_t version_firmware (void) {
|
||||||
switch (type) {
|
return ((VERSION_MAJOR << 16) | (VERSION_MINOR));
|
||||||
case API_USB:
|
|
||||||
return VERSION_API_USB;
|
|
||||||
case API_N64:
|
|
||||||
return VERSION_API_N64;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
typedef enum {
|
uint32_t version_firmware (void);
|
||||||
API_USB,
|
|
||||||
API_N64,
|
|
||||||
} version_api_type_t;
|
|
||||||
|
|
||||||
|
|
||||||
uint32_t version_api (version_api_type_t type);
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
236
sw/pc/dd64.py
236
sw/pc/dd64.py
@ -1,236 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from io import BufferedReader
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BadBlockError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DD64Image:
|
|
||||||
__DISK_HEADS = 2
|
|
||||||
__DISK_TRACKS = 1175
|
|
||||||
__DISK_BLOCKS_PER_TRACK = 2
|
|
||||||
__DISK_SECTORS_PER_BLOCK = 85
|
|
||||||
__DISK_BAD_TRACKS_PER_ZONE = 12
|
|
||||||
__DISK_SYSTEM_SECTOR_SIZE = 232
|
|
||||||
__DISK_ZONES = [
|
|
||||||
(0, 232, 158, 0),
|
|
||||||
(0, 216, 158, 158),
|
|
||||||
(0, 208, 149, 316),
|
|
||||||
(0, 192, 149, 465),
|
|
||||||
(0, 176, 149, 614),
|
|
||||||
(0, 160, 149, 763),
|
|
||||||
(0, 144, 149, 912),
|
|
||||||
(0, 128, 114, 1061),
|
|
||||||
(1, 216, 158, 157),
|
|
||||||
(1, 208, 158, 315),
|
|
||||||
(1, 192, 149, 464),
|
|
||||||
(1, 176, 149, 613),
|
|
||||||
(1, 160, 149, 762),
|
|
||||||
(1, 144, 149, 911),
|
|
||||||
(1, 128, 149, 1060),
|
|
||||||
(1, 112, 114, 1174),
|
|
||||||
]
|
|
||||||
__DISK_VZONE_TO_PZONE = [
|
|
||||||
[0, 1, 2, 9, 8, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10],
|
|
||||||
[0, 1, 2, 3, 10, 9, 8, 4, 5, 6, 7, 15, 14, 13, 12, 11],
|
|
||||||
[0, 1, 2, 3, 4, 11, 10, 9, 8, 5, 6, 7, 15, 14, 13, 12],
|
|
||||||
[0, 1, 2, 3, 4, 5, 12, 11, 10, 9, 8, 6, 7, 15, 14, 13],
|
|
||||||
[0, 1, 2, 3, 4, 5, 6, 13, 12, 11, 10, 9, 8, 7, 15, 14],
|
|
||||||
[0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8, 15],
|
|
||||||
[0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8],
|
|
||||||
]
|
|
||||||
__DISK_DRIVE_TYPES = [(
|
|
||||||
'development',
|
|
||||||
192,
|
|
||||||
[11, 10, 3, 2],
|
|
||||||
[0, 1, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23],
|
|
||||||
), (
|
|
||||||
'retail',
|
|
||||||
232,
|
|
||||||
[9, 8, 1, 0],
|
|
||||||
[2, 3, 10, 11, 12, 16, 17, 18, 19, 20, 21, 22, 23],
|
|
||||||
)]
|
|
||||||
|
|
||||||
__file: Optional[BufferedReader]
|
|
||||||
__drive_type: Optional[str]
|
|
||||||
__block_info_table: list[tuple[int, int]]
|
|
||||||
loaded: bool = False
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.__file = None
|
|
||||||
self.__drive_type = None
|
|
||||||
block_info_table_length = self.__DISK_HEADS * self.__DISK_TRACKS * self.__DISK_BLOCKS_PER_TRACK
|
|
||||||
self.__block_info_table = [None] * block_info_table_length
|
|
||||||
|
|
||||||
def __del__(self) -> None:
|
|
||||||
self.unload()
|
|
||||||
|
|
||||||
def __check_system_block(self, lba: int, sector_size: int, check_disk_type: bool) -> tuple[bool, bytes]:
|
|
||||||
self.__file.seek(lba * self.__DISK_SYSTEM_SECTOR_SIZE * self.__DISK_SECTORS_PER_BLOCK)
|
|
||||||
system_block_data = self.__file.read(sector_size * self.__DISK_SECTORS_PER_BLOCK)
|
|
||||||
system_data = system_block_data[:sector_size]
|
|
||||||
for sector in range(1, self.__DISK_SECTORS_PER_BLOCK):
|
|
||||||
sector_data = system_block_data[(sector * sector_size):][:sector_size]
|
|
||||||
if (system_data != sector_data):
|
|
||||||
return (False, None)
|
|
||||||
if (check_disk_type):
|
|
||||||
if (system_data[4] != 0x10):
|
|
||||||
return (False, None)
|
|
||||||
if ((system_data[5] & 0xF0) != 0x10):
|
|
||||||
return (False, None)
|
|
||||||
return (True, system_data)
|
|
||||||
|
|
||||||
def __parse_disk(self) -> None:
|
|
||||||
disk_system_data = None
|
|
||||||
disk_id_data = None
|
|
||||||
disk_bad_lbas = []
|
|
||||||
|
|
||||||
drive_index = 0
|
|
||||||
while (disk_system_data == None) and (drive_index < len(self.__DISK_DRIVE_TYPES)):
|
|
||||||
(drive_type, system_sector_size, system_data_lbas, bad_lbas) = self.__DISK_DRIVE_TYPES[drive_index]
|
|
||||||
disk_bad_lbas.clear()
|
|
||||||
disk_bad_lbas.extend(bad_lbas)
|
|
||||||
for system_lba in system_data_lbas:
|
|
||||||
(valid, system_data) = self.__check_system_block(system_lba, system_sector_size, check_disk_type=True)
|
|
||||||
if (valid):
|
|
||||||
self.__drive_type = drive_type
|
|
||||||
disk_system_data = system_data
|
|
||||||
else:
|
|
||||||
disk_bad_lbas.append(system_lba)
|
|
||||||
drive_index += 1
|
|
||||||
|
|
||||||
for id_lba in [15, 14]:
|
|
||||||
(valid, id_data) = self.__check_system_block(id_lba, self.__DISK_SYSTEM_SECTOR_SIZE, check_disk_type=False)
|
|
||||||
if (valid):
|
|
||||||
disk_id_data = id_data
|
|
||||||
else:
|
|
||||||
disk_bad_lbas.append(id_lba)
|
|
||||||
|
|
||||||
if not (disk_system_data and disk_id_data):
|
|
||||||
raise ValueError('Provided 64DD disk file is not valid')
|
|
||||||
|
|
||||||
disk_zone_bad_tracks = []
|
|
||||||
|
|
||||||
for zone in range(len(self.__DISK_ZONES)):
|
|
||||||
zone_bad_tracks = []
|
|
||||||
start = 0 if zone == 0 else system_data[0x07 + zone]
|
|
||||||
stop = system_data[0x07 + zone + 1]
|
|
||||||
for offset in range(start, stop):
|
|
||||||
zone_bad_tracks.append(system_data[0x20 + offset])
|
|
||||||
for ignored_track in range(self.__DISK_BAD_TRACKS_PER_ZONE - len(zone_bad_tracks)):
|
|
||||||
zone_bad_tracks.append(self.__DISK_ZONES[zone][2] - ignored_track - 1)
|
|
||||||
disk_zone_bad_tracks.append(zone_bad_tracks)
|
|
||||||
|
|
||||||
disk_type = disk_system_data[5] & 0x0F
|
|
||||||
|
|
||||||
current_lba = 0
|
|
||||||
starting_block = 0
|
|
||||||
disk_file_offset = 0
|
|
||||||
|
|
||||||
for zone in self.__DISK_VZONE_TO_PZONE[disk_type]:
|
|
||||||
(head, sector_size, tracks, track) = self.__DISK_ZONES[zone]
|
|
||||||
|
|
||||||
for zone_track in range(tracks):
|
|
||||||
current_zone_track = (
|
|
||||||
(tracks - 1) - zone_track) if head else zone_track
|
|
||||||
|
|
||||||
if (current_zone_track in disk_zone_bad_tracks[zone]):
|
|
||||||
track += (-1) if head else 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
for block in range(self.__DISK_BLOCKS_PER_TRACK):
|
|
||||||
index = (track << 2) | (head << 1) | (starting_block ^ block)
|
|
||||||
if (current_lba not in disk_bad_lbas):
|
|
||||||
self.__block_info_table[index] = (disk_file_offset, sector_size * self.__DISK_SECTORS_PER_BLOCK)
|
|
||||||
else:
|
|
||||||
self.__block_info_table[index] = None
|
|
||||||
disk_file_offset += sector_size * self.__DISK_SECTORS_PER_BLOCK
|
|
||||||
current_lba += 1
|
|
||||||
|
|
||||||
track += (-1) if head else 1
|
|
||||||
starting_block ^= 1
|
|
||||||
|
|
||||||
def __check_track_head_block(self, track: int, head: int, block: int) -> None:
|
|
||||||
if (track < 0 or track >= self.__DISK_TRACKS):
|
|
||||||
raise ValueError('Track outside of possible range')
|
|
||||||
if (head < 0 or head >= self.__DISK_HEADS):
|
|
||||||
raise ValueError('Head outside of possible range')
|
|
||||||
if (block < 0 or block >= self.__DISK_BLOCKS_PER_TRACK):
|
|
||||||
raise ValueError('Block outside of possible range')
|
|
||||||
|
|
||||||
def __get_table_index(self, track: int, head: int, block: int) -> int:
|
|
||||||
return (track << 2) | (head << 1) | (block)
|
|
||||||
|
|
||||||
def __get_block_info(self, track: int, head: int, block: int) -> Optional[tuple[int, int]]:
|
|
||||||
if (self.__file.closed):
|
|
||||||
return None
|
|
||||||
self.__check_track_head_block(track, head, block)
|
|
||||||
index = self.__get_table_index(track, head, block)
|
|
||||||
return self.__block_info_table[index]
|
|
||||||
|
|
||||||
def load(self, path: str) -> None:
|
|
||||||
self.unload()
|
|
||||||
self.__file = open(path, 'rb+')
|
|
||||||
self.__parse_disk()
|
|
||||||
self.loaded = True
|
|
||||||
|
|
||||||
def unload(self) -> None:
|
|
||||||
self.loaded = False
|
|
||||||
if (self.__file != None and not self.__file.closed):
|
|
||||||
self.__file.close()
|
|
||||||
self.__drive_type = None
|
|
||||||
|
|
||||||
def get_block_info_table(self) -> list[tuple[int, int]]:
|
|
||||||
return self.__block_info_table
|
|
||||||
|
|
||||||
def get_drive_type(self) -> str:
|
|
||||||
return self.__drive_type
|
|
||||||
|
|
||||||
def read_block(self, track: int, head: int, block: int) -> bytes:
|
|
||||||
info = self.__get_block_info(track, head, block)
|
|
||||||
if (info == None):
|
|
||||||
raise BadBlockError
|
|
||||||
(offset, block_size) = info
|
|
||||||
self.__file.seek(offset)
|
|
||||||
return self.__file.read(block_size)
|
|
||||||
|
|
||||||
def write_block(self, track: int, head: int, block: int, data: bytes) -> None:
|
|
||||||
info = self.__get_block_info(track, head, block)
|
|
||||||
if (info == None):
|
|
||||||
raise BadBlockError
|
|
||||||
(offset, block_size) = info
|
|
||||||
if (len(data) != block_size):
|
|
||||||
raise ValueError(f'Provided data block size is different than expected ({len(data)} != {block_size})')
|
|
||||||
self.__file.seek(offset)
|
|
||||||
self.__file.write(data)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
id_lba_locations = [
|
|
||||||
(7, 0, 1),
|
|
||||||
(7, 0, 0)
|
|
||||||
]
|
|
||||||
if (len(sys.argv) >= 2):
|
|
||||||
dd = DD64Image()
|
|
||||||
dd.load(sys.argv[1])
|
|
||||||
print(dd.get_drive_type())
|
|
||||||
for (track, head, block) in id_lba_locations:
|
|
||||||
try:
|
|
||||||
print(dd.read_block(track, head, block)[:4])
|
|
||||||
except BadBlockError:
|
|
||||||
print(f'Bad ID block [track: {track}, head: {head}, block: {block}]')
|
|
||||||
if (len(sys.argv) >= 3):
|
|
||||||
with open(sys.argv[2], 'wb') as f:
|
|
||||||
block_info_table = dd.get_block_info_table()
|
|
||||||
for block in block_info_table:
|
|
||||||
offset = 0xFFFFFFFF if block == None else block[0]
|
|
||||||
f.write(offset.to_bytes(4, byteorder='big'))
|
|
||||||
dd.unload()
|
|
||||||
else:
|
|
||||||
print(f'[{sys.argv[0]}]: Expected disk image path as first argument')
|
|
309
sw/pc/sc64.py
309
sw/pc/sc64.py
@ -9,8 +9,8 @@ import sys
|
|||||||
import time
|
import time
|
||||||
from binascii import crc32
|
from binascii import crc32
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dd64 import BadBlockError, DD64Image
|
|
||||||
from enum import Enum, IntEnum
|
from enum import Enum, IntEnum
|
||||||
|
from io import BufferedReader
|
||||||
from serial.tools import list_ports
|
from serial.tools import list_ports
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
@ -18,6 +18,210 @@ from PIL import Image
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BadBlockError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DD64Image:
|
||||||
|
__DISK_HEADS = 2
|
||||||
|
__DISK_TRACKS = 1175
|
||||||
|
__DISK_BLOCKS_PER_TRACK = 2
|
||||||
|
__DISK_SECTORS_PER_BLOCK = 85
|
||||||
|
__DISK_BAD_TRACKS_PER_ZONE = 12
|
||||||
|
__DISK_SYSTEM_SECTOR_SIZE = 232
|
||||||
|
__DISK_ZONES = [
|
||||||
|
(0, 232, 158, 0),
|
||||||
|
(0, 216, 158, 158),
|
||||||
|
(0, 208, 149, 316),
|
||||||
|
(0, 192, 149, 465),
|
||||||
|
(0, 176, 149, 614),
|
||||||
|
(0, 160, 149, 763),
|
||||||
|
(0, 144, 149, 912),
|
||||||
|
(0, 128, 114, 1061),
|
||||||
|
(1, 216, 158, 157),
|
||||||
|
(1, 208, 158, 315),
|
||||||
|
(1, 192, 149, 464),
|
||||||
|
(1, 176, 149, 613),
|
||||||
|
(1, 160, 149, 762),
|
||||||
|
(1, 144, 149, 911),
|
||||||
|
(1, 128, 149, 1060),
|
||||||
|
(1, 112, 114, 1174),
|
||||||
|
]
|
||||||
|
__DISK_VZONE_TO_PZONE = [
|
||||||
|
[0, 1, 2, 9, 8, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10],
|
||||||
|
[0, 1, 2, 3, 10, 9, 8, 4, 5, 6, 7, 15, 14, 13, 12, 11],
|
||||||
|
[0, 1, 2, 3, 4, 11, 10, 9, 8, 5, 6, 7, 15, 14, 13, 12],
|
||||||
|
[0, 1, 2, 3, 4, 5, 12, 11, 10, 9, 8, 6, 7, 15, 14, 13],
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 13, 12, 11, 10, 9, 8, 7, 15, 14],
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8, 15],
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8],
|
||||||
|
]
|
||||||
|
__DISK_DRIVE_TYPES = [(
|
||||||
|
'development',
|
||||||
|
192,
|
||||||
|
[11, 10, 3, 2],
|
||||||
|
[0, 1, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
|
), (
|
||||||
|
'retail',
|
||||||
|
232,
|
||||||
|
[9, 8, 1, 0],
|
||||||
|
[2, 3, 10, 11, 12, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
|
)]
|
||||||
|
|
||||||
|
__file: Optional[BufferedReader]
|
||||||
|
__drive_type: Optional[str]
|
||||||
|
__block_info_table: list[tuple[int, int]]
|
||||||
|
loaded: bool = False
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.__file = None
|
||||||
|
self.__drive_type = None
|
||||||
|
block_info_table_length = self.__DISK_HEADS * self.__DISK_TRACKS * self.__DISK_BLOCKS_PER_TRACK
|
||||||
|
self.__block_info_table = [None] * block_info_table_length
|
||||||
|
|
||||||
|
def __del__(self) -> None:
|
||||||
|
self.unload()
|
||||||
|
|
||||||
|
def __check_system_block(self, lba: int, sector_size: int, check_disk_type: bool) -> tuple[bool, bytes]:
|
||||||
|
self.__file.seek(lba * self.__DISK_SYSTEM_SECTOR_SIZE * self.__DISK_SECTORS_PER_BLOCK)
|
||||||
|
system_block_data = self.__file.read(sector_size * self.__DISK_SECTORS_PER_BLOCK)
|
||||||
|
system_data = system_block_data[:sector_size]
|
||||||
|
for sector in range(1, self.__DISK_SECTORS_PER_BLOCK):
|
||||||
|
sector_data = system_block_data[(sector * sector_size):][:sector_size]
|
||||||
|
if (system_data != sector_data):
|
||||||
|
return (False, None)
|
||||||
|
if (check_disk_type):
|
||||||
|
if (system_data[4] != 0x10):
|
||||||
|
return (False, None)
|
||||||
|
if ((system_data[5] & 0xF0) != 0x10):
|
||||||
|
return (False, None)
|
||||||
|
return (True, system_data)
|
||||||
|
|
||||||
|
def __parse_disk(self) -> None:
|
||||||
|
disk_system_data = None
|
||||||
|
disk_id_data = None
|
||||||
|
disk_bad_lbas = []
|
||||||
|
|
||||||
|
drive_index = 0
|
||||||
|
while (disk_system_data == None) and (drive_index < len(self.__DISK_DRIVE_TYPES)):
|
||||||
|
(drive_type, system_sector_size, system_data_lbas, bad_lbas) = self.__DISK_DRIVE_TYPES[drive_index]
|
||||||
|
disk_bad_lbas.clear()
|
||||||
|
disk_bad_lbas.extend(bad_lbas)
|
||||||
|
for system_lba in system_data_lbas:
|
||||||
|
(valid, system_data) = self.__check_system_block(system_lba, system_sector_size, check_disk_type=True)
|
||||||
|
if (valid):
|
||||||
|
self.__drive_type = drive_type
|
||||||
|
disk_system_data = system_data
|
||||||
|
else:
|
||||||
|
disk_bad_lbas.append(system_lba)
|
||||||
|
drive_index += 1
|
||||||
|
|
||||||
|
for id_lba in [15, 14]:
|
||||||
|
(valid, id_data) = self.__check_system_block(id_lba, self.__DISK_SYSTEM_SECTOR_SIZE, check_disk_type=False)
|
||||||
|
if (valid):
|
||||||
|
disk_id_data = id_data
|
||||||
|
else:
|
||||||
|
disk_bad_lbas.append(id_lba)
|
||||||
|
|
||||||
|
if not (disk_system_data and disk_id_data):
|
||||||
|
raise ValueError('Provided 64DD disk file is not valid')
|
||||||
|
|
||||||
|
disk_zone_bad_tracks = []
|
||||||
|
|
||||||
|
for zone in range(len(self.__DISK_ZONES)):
|
||||||
|
zone_bad_tracks = []
|
||||||
|
start = 0 if zone == 0 else system_data[0x07 + zone]
|
||||||
|
stop = system_data[0x07 + zone + 1]
|
||||||
|
for offset in range(start, stop):
|
||||||
|
zone_bad_tracks.append(system_data[0x20 + offset])
|
||||||
|
for ignored_track in range(self.__DISK_BAD_TRACKS_PER_ZONE - len(zone_bad_tracks)):
|
||||||
|
zone_bad_tracks.append(self.__DISK_ZONES[zone][2] - ignored_track - 1)
|
||||||
|
disk_zone_bad_tracks.append(zone_bad_tracks)
|
||||||
|
|
||||||
|
disk_type = disk_system_data[5] & 0x0F
|
||||||
|
|
||||||
|
current_lba = 0
|
||||||
|
starting_block = 0
|
||||||
|
disk_file_offset = 0
|
||||||
|
|
||||||
|
for zone in self.__DISK_VZONE_TO_PZONE[disk_type]:
|
||||||
|
(head, sector_size, tracks, track) = self.__DISK_ZONES[zone]
|
||||||
|
|
||||||
|
for zone_track in range(tracks):
|
||||||
|
current_zone_track = (
|
||||||
|
(tracks - 1) - zone_track) if head else zone_track
|
||||||
|
|
||||||
|
if (current_zone_track in disk_zone_bad_tracks[zone]):
|
||||||
|
track += (-1) if head else 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
for block in range(self.__DISK_BLOCKS_PER_TRACK):
|
||||||
|
index = (track << 2) | (head << 1) | (starting_block ^ block)
|
||||||
|
if (current_lba not in disk_bad_lbas):
|
||||||
|
self.__block_info_table[index] = (disk_file_offset, sector_size * self.__DISK_SECTORS_PER_BLOCK)
|
||||||
|
else:
|
||||||
|
self.__block_info_table[index] = None
|
||||||
|
disk_file_offset += sector_size * self.__DISK_SECTORS_PER_BLOCK
|
||||||
|
current_lba += 1
|
||||||
|
|
||||||
|
track += (-1) if head else 1
|
||||||
|
starting_block ^= 1
|
||||||
|
|
||||||
|
def __check_track_head_block(self, track: int, head: int, block: int) -> None:
|
||||||
|
if (track < 0 or track >= self.__DISK_TRACKS):
|
||||||
|
raise ValueError('Track outside of possible range')
|
||||||
|
if (head < 0 or head >= self.__DISK_HEADS):
|
||||||
|
raise ValueError('Head outside of possible range')
|
||||||
|
if (block < 0 or block >= self.__DISK_BLOCKS_PER_TRACK):
|
||||||
|
raise ValueError('Block outside of possible range')
|
||||||
|
|
||||||
|
def __get_table_index(self, track: int, head: int, block: int) -> int:
|
||||||
|
return (track << 2) | (head << 1) | (block)
|
||||||
|
|
||||||
|
def __get_block_info(self, track: int, head: int, block: int) -> Optional[tuple[int, int]]:
|
||||||
|
if (self.__file.closed):
|
||||||
|
return None
|
||||||
|
self.__check_track_head_block(track, head, block)
|
||||||
|
index = self.__get_table_index(track, head, block)
|
||||||
|
return self.__block_info_table[index]
|
||||||
|
|
||||||
|
def load(self, path: str) -> None:
|
||||||
|
self.unload()
|
||||||
|
self.__file = open(path, 'rb+')
|
||||||
|
self.__parse_disk()
|
||||||
|
self.loaded = True
|
||||||
|
|
||||||
|
def unload(self) -> None:
|
||||||
|
self.loaded = False
|
||||||
|
if (self.__file != None and not self.__file.closed):
|
||||||
|
self.__file.close()
|
||||||
|
self.__drive_type = None
|
||||||
|
|
||||||
|
def get_block_info_table(self) -> list[tuple[int, int]]:
|
||||||
|
return self.__block_info_table
|
||||||
|
|
||||||
|
def get_drive_type(self) -> str:
|
||||||
|
return self.__drive_type
|
||||||
|
|
||||||
|
def read_block(self, track: int, head: int, block: int) -> bytes:
|
||||||
|
info = self.__get_block_info(track, head, block)
|
||||||
|
if (info == None):
|
||||||
|
raise BadBlockError
|
||||||
|
(offset, block_size) = info
|
||||||
|
self.__file.seek(offset)
|
||||||
|
return self.__file.read(block_size)
|
||||||
|
|
||||||
|
def write_block(self, track: int, head: int, block: int, data: bytes) -> None:
|
||||||
|
info = self.__get_block_info(track, head, block)
|
||||||
|
if (info == None):
|
||||||
|
raise BadBlockError
|
||||||
|
(offset, block_size) = info
|
||||||
|
if (len(data) != block_size):
|
||||||
|
raise ValueError(f'Provided data block size is different than expected ({len(data)} != {block_size})')
|
||||||
|
self.__file.seek(offset)
|
||||||
|
self.__file.write(data)
|
||||||
|
|
||||||
|
|
||||||
class ConnectionException(Exception):
|
class ConnectionException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -200,6 +404,7 @@ class SC64:
|
|||||||
FLASH = 0x0400_0000
|
FLASH = 0x0400_0000
|
||||||
BUFFER = 0x0500_0000
|
BUFFER = 0x0500_0000
|
||||||
EEPROM = 0x0500_2000
|
EEPROM = 0x0500_2000
|
||||||
|
END = 0x0500_297F
|
||||||
FIRMWARE = 0x0200_0000
|
FIRMWARE = 0x0200_0000
|
||||||
DDIPL = 0x03BC_0000
|
DDIPL = 0x03BC_0000
|
||||||
SAVE = 0x03FE_0000
|
SAVE = 0x03FE_0000
|
||||||
@ -321,7 +526,8 @@ class SC64:
|
|||||||
SCREENSHOT = 4
|
SCREENSHOT = 4
|
||||||
GDB = 0xDB
|
GDB = 0xDB
|
||||||
|
|
||||||
__MIN_SUPPORTED_API_VERSION = 2
|
__SUPPORTED_MAJOR_VERSION = 2
|
||||||
|
__SUPPORTED_MINOR_VERSION = 12
|
||||||
|
|
||||||
__isv_line_buffer: bytes = b''
|
__isv_line_buffer: bytes = b''
|
||||||
__debug_header: Optional[bytes] = None
|
__debug_header: Optional[bytes] = None
|
||||||
@ -329,21 +535,25 @@ class SC64:
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__link = SC64Serial()
|
self.__link = SC64Serial()
|
||||||
version = self.__link.execute_cmd(cmd=b'v')
|
identifier = self.__link.execute_cmd(cmd=b'v')
|
||||||
if (version != b'SCv2'):
|
if (identifier != b'SCv2'):
|
||||||
raise ConnectionException('Unknown SC64 HW version')
|
raise ConnectionException('Unknown SC64 v2 identifier')
|
||||||
|
|
||||||
def __get_int(self, data: bytes) -> int:
|
def __get_int(self, data: bytes) -> int:
|
||||||
return int.from_bytes(data[:4], byteorder='big')
|
return int.from_bytes(data[:4], byteorder='big')
|
||||||
|
|
||||||
def check_api_version(self) -> None:
|
def check_firmware_version(self) -> tuple[str, bool]:
|
||||||
try:
|
try:
|
||||||
data = self.__link.execute_cmd(cmd=b'V')
|
version = self.__link.execute_cmd(cmd=b'V')
|
||||||
|
major = self.__get_int(version[0:2])
|
||||||
|
minor = self.__get_int(version[2:4])
|
||||||
|
if (major != self.__SUPPORTED_MAJOR_VERSION):
|
||||||
|
raise ConnectionException()
|
||||||
|
if (minor < self.__SUPPORTED_MINOR_VERSION):
|
||||||
|
raise ConnectionException()
|
||||||
|
return (f'{major}.{minor}', minor > self.__SUPPORTED_MINOR_VERSION)
|
||||||
except ConnectionException:
|
except ConnectionException:
|
||||||
raise ConnectionException('Outdated SC64 API, please update firmware')
|
raise ConnectionException(f'Unsupported SC64 version [{major}.{minor}], please update firmware')
|
||||||
version = self.__get_int(data)
|
|
||||||
if (version < self.__MIN_SUPPORTED_API_VERSION):
|
|
||||||
raise ConnectionException('Unsupported SC64 API version, please update firmware')
|
|
||||||
|
|
||||||
def __set_config(self, config: __CfgId, value: int) -> None:
|
def __set_config(self, config: __CfgId, value: int) -> None:
|
||||||
try:
|
try:
|
||||||
@ -461,8 +671,10 @@ class SC64:
|
|||||||
raise ValueError('Debug data size too big')
|
raise ValueError('Debug data size too big')
|
||||||
self.__link.execute_cmd(cmd=b'U', args=[datatype, len(data)], data=data, response=False)
|
self.__link.execute_cmd(cmd=b'U', args=[datatype, len(data)], data=data, response=False)
|
||||||
|
|
||||||
def download_memory(self) -> bytes:
|
def download_memory(self, address: int, length: int) -> bytes:
|
||||||
return self.__read_memory(self.__Address.MEMORY, self.__Length.MEMORY)
|
if ((address < 0) or (length < 0) or ((address + length) > self.__Address.END)):
|
||||||
|
raise ValueError('Invalid address or length')
|
||||||
|
return self.__read_memory(address, length)
|
||||||
|
|
||||||
def upload_rom(self, data: bytes, use_shadow: bool=True) -> None:
|
def upload_rom(self, data: bytes, use_shadow: bool=True) -> None:
|
||||||
rom_length = len(data)
|
rom_length = len(data)
|
||||||
@ -582,7 +794,7 @@ class SC64:
|
|||||||
raise ConnectionException('Error while getting firmware backup')
|
raise ConnectionException('Error while getting firmware backup')
|
||||||
return self.__read_memory(address, length)
|
return self.__read_memory(address, length)
|
||||||
|
|
||||||
def set_cic_parameters(self, seed: Optional[int]=None, disabled=False) -> tuple[int, int, bool]:
|
def update_cic_parameters(self, seed: Optional[int]=None, disabled=False) -> tuple[int, int, bool]:
|
||||||
if ((seed != None) and (seed < 0 or seed > 0xFF)):
|
if ((seed != None) and (seed < 0 or seed > 0xFF)):
|
||||||
raise ValueError('CIC seed outside of allowed values')
|
raise ValueError('CIC seed outside of allowed values')
|
||||||
boot_mode = self.__get_config(self.__CfgId.BOOT_MODE)
|
boot_mode = self.__get_config(self.__CfgId.BOOT_MODE)
|
||||||
@ -884,7 +1096,7 @@ class SC64:
|
|||||||
current_image = 0
|
current_image = 0
|
||||||
next_image = 0
|
next_image = 0
|
||||||
|
|
||||||
self.__set_config(self.__CfgId.ROM_WRITE_ENABLE, isv)
|
self.__set_config(self.__CfgId.ROM_WRITE_ENABLE, 1 if (isv != 0) else 0)
|
||||||
self.__set_config(self.__CfgId.ISV_ADDRESS, isv)
|
self.__set_config(self.__CfgId.ISV_ADDRESS, isv)
|
||||||
if (isv != 0):
|
if (isv != 0):
|
||||||
print(f'IS-Viewer64 support set to [ENABLED] at ROM offset [0x{(isv):08X}]')
|
print(f'IS-Viewer64 support set to [ENABLED] at ROM offset [0x{(isv):08X}]')
|
||||||
@ -992,16 +1204,24 @@ if __name__ == '__main__':
|
|||||||
disabled = bool(int(params[1])) if len(params) >= 2 else None
|
disabled = bool(int(params[1])) if len(params) >= 2 else None
|
||||||
return (seed, disabled)
|
return (seed, disabled)
|
||||||
|
|
||||||
|
def download_memory_type(argument: str):
|
||||||
|
params = argument.split(',')
|
||||||
|
if (len(params) < 2 or len(params) > 3):
|
||||||
|
raise argparse.ArgumentError()
|
||||||
|
address = int(params[0], 0)
|
||||||
|
length = int(params[1], 0)
|
||||||
|
file = params[2] if len(params) >= 3 else 'sc64dump.bin'
|
||||||
|
return (address, length, file)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='SC64 control software')
|
parser = argparse.ArgumentParser(description='SC64 control software')
|
||||||
parser.add_argument('--backup-firmware', metavar='file', help='backup SC64 firmware and write it to specified file')
|
parser.add_argument('--backup-firmware', metavar='file', help='backup SC64 firmware and write it to specified file')
|
||||||
parser.add_argument('--update-firmware', metavar='file', help='update SC64 firmware from specified file')
|
parser.add_argument('--update-firmware', metavar='file', help='update SC64 firmware from specified file')
|
||||||
parser.add_argument('--update-bootloader', metavar='file', help='update SC64 bootloader (not recommended, use --update-firmware instead)')
|
|
||||||
parser.add_argument('--reset-state', action='store_true', help='reset SC64 internal state')
|
parser.add_argument('--reset-state', action='store_true', help='reset SC64 internal state')
|
||||||
parser.add_argument('--print-state', action='store_true', help='print SC64 internal state')
|
parser.add_argument('--print-state', action='store_true', help='print SC64 internal state')
|
||||||
parser.add_argument('--led-enabled', metavar='{true,false}', help='disable or enable LED I/O activity blinking')
|
parser.add_argument('--led-blink', metavar='{yes,no}', help='disable or enable LED I/O activity blinking')
|
||||||
parser.add_argument('--boot', type=SC64.BootMode, action=EnumAction, help='set boot mode')
|
parser.add_argument('--boot', type=SC64.BootMode, action=EnumAction, help='set boot mode')
|
||||||
parser.add_argument('--tv', type=SC64.TVType, action=EnumAction, help='force TV type to set value')
|
parser.add_argument('--tv', type=SC64.TVType, action=EnumAction, help='force TV type to set value, not used when direct boot mode is enabled')
|
||||||
parser.add_argument('--cic', type=SC64.CICSeed, action=EnumAction, help='force CIC seed to set value')
|
parser.add_argument('--cic', type=SC64.CICSeed, action=EnumAction, help='force CIC seed to set value, not used when direct boot mode is enabled')
|
||||||
parser.add_argument('--cic-params', metavar='seed,[disabled]', type=cic_params_type, help='set CIC emulation parameters')
|
parser.add_argument('--cic-params', metavar='seed,[disabled]', type=cic_params_type, help='set CIC emulation parameters')
|
||||||
parser.add_argument('--rtc', action='store_true', help='update clock in SC64 to system time')
|
parser.add_argument('--rtc', action='store_true', help='update clock in SC64 to system time')
|
||||||
parser.add_argument('--no-shadow', action='store_false', help='do not put last 128 kB of ROM inside flash memory (can corrupt non EEPROM saves)')
|
parser.add_argument('--no-shadow', action='store_false', help='do not put last 128 kB of ROM inside flash memory (can corrupt non EEPROM saves)')
|
||||||
@ -1014,7 +1234,7 @@ if __name__ == '__main__':
|
|||||||
parser.add_argument('--isv', type=lambda x: int(x, 0), default=0, help='enable IS-Viewer64 support at provided ROM offset')
|
parser.add_argument('--isv', type=lambda x: int(x, 0), default=0, help='enable IS-Viewer64 support at provided ROM offset')
|
||||||
parser.add_argument('--gdb', metavar='port', type=int, help='expose socket port for GDB debugging')
|
parser.add_argument('--gdb', metavar='port', type=int, help='expose socket port for GDB debugging')
|
||||||
parser.add_argument('--debug', action='store_true', help='run debug loop')
|
parser.add_argument('--debug', action='store_true', help='run debug loop')
|
||||||
parser.add_argument('--download-memory', metavar='file', help='download whole memory space and write it to specified file')
|
parser.add_argument('--download-memory', metavar='address,length,[file]', type=download_memory_type, help='download specified memory region and write it to file')
|
||||||
|
|
||||||
if (len(sys.argv) <= 1):
|
if (len(sys.argv) <= 1):
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
@ -1039,13 +1259,15 @@ if __name__ == '__main__':
|
|||||||
sc64.update_firmware(f.read(), status_callback)
|
sc64.update_firmware(f.read(), status_callback)
|
||||||
print('done')
|
print('done')
|
||||||
|
|
||||||
sc64.check_api_version()
|
(version, script_outdated) = sc64.check_firmware_version()
|
||||||
|
|
||||||
if (args.update_bootloader):
|
print(f'SC64 firmware version: [{version}]')
|
||||||
with open(args.update_bootloader, 'rb') as f:
|
if (script_outdated):
|
||||||
print('Uploading Bootloader... ', end='', flush=True)
|
print('\x1b[33m')
|
||||||
sc64.upload_bootloader(f.read())
|
print('[ SC64 firmware is newer than last known version. ]')
|
||||||
print('done')
|
print('[ Consider downloading latest script from ]')
|
||||||
|
print('[ https://github.com/Polprzewodnikowy/SummerCart64/releases ]')
|
||||||
|
print('\x1b[0m')
|
||||||
|
|
||||||
if (args.reset_state):
|
if (args.reset_state):
|
||||||
sc64.reset_state()
|
sc64.reset_state()
|
||||||
@ -1059,10 +1281,10 @@ if __name__ == '__main__':
|
|||||||
value = getattr(value, 'name')
|
value = getattr(value, 'name')
|
||||||
print(f' {key}: {value}')
|
print(f' {key}: {value}')
|
||||||
|
|
||||||
if (args.led_enabled != None):
|
if (args.led_blink):
|
||||||
value = (args.led_enabled == 'true')
|
blink = (args.led_blink == 'yes')
|
||||||
sc64.set_led_enable(value)
|
sc64.set_led_enable(blink)
|
||||||
print(f'LED blinking set to [{"ENABLED" if value else "DISABLED"}]')
|
print(f'LED blinking set to [{"ENABLED" if blink else "DISABLED"}]')
|
||||||
|
|
||||||
if (args.tv != None):
|
if (args.tv != None):
|
||||||
sc64.set_tv_type(args.tv)
|
sc64.set_tv_type(args.tv)
|
||||||
@ -1085,6 +1307,12 @@ if __name__ == '__main__':
|
|||||||
autodetected_save_type = sc64.autodetect_save_type(rom_data)
|
autodetected_save_type = sc64.autodetect_save_type(rom_data)
|
||||||
print('done')
|
print('done')
|
||||||
|
|
||||||
|
if (args.ddipl):
|
||||||
|
with open(args.ddipl, 'rb') as f:
|
||||||
|
print('Uploading 64DD IPL... ', end='', flush=True)
|
||||||
|
sc64.upload_ddipl(f.read())
|
||||||
|
print('done')
|
||||||
|
|
||||||
if (args.save_type != None or autodetected_save_type != None):
|
if (args.save_type != None or autodetected_save_type != None):
|
||||||
save_type = args.save_type if args.save_type != None else autodetected_save_type
|
save_type = args.save_type if args.save_type != None else autodetected_save_type
|
||||||
sc64.set_save_type(save_type)
|
sc64.set_save_type(save_type)
|
||||||
@ -1096,26 +1324,20 @@ if __name__ == '__main__':
|
|||||||
sc64.upload_save(f.read())
|
sc64.upload_save(f.read())
|
||||||
print('done')
|
print('done')
|
||||||
|
|
||||||
if (args.ddipl):
|
|
||||||
with open(args.ddipl, 'rb') as f:
|
|
||||||
print('Uploading 64DD IPL... ', end='', flush=True)
|
|
||||||
sc64.upload_ddipl(f.read())
|
|
||||||
print('done')
|
|
||||||
|
|
||||||
if (args.boot != None):
|
if (args.boot != None):
|
||||||
sc64.set_boot_mode(args.boot)
|
sc64.set_boot_mode(args.boot)
|
||||||
print(f'Boot mode set to [{args.boot.name}]')
|
print(f'Boot mode set to [{args.boot.name}]')
|
||||||
|
|
||||||
if (args.cic_params != None):
|
if (args.cic_params != None):
|
||||||
(seed, disabled) = args.cic_params
|
(seed, disabled) = args.cic_params
|
||||||
(seed, checksum, dd_mode) = sc64.set_cic_parameters(seed, disabled)
|
(seed, checksum, dd_mode) = sc64.update_cic_parameters(seed, disabled)
|
||||||
print('CIC parameters set to [', end='')
|
print('CIC parameters set to [', end='')
|
||||||
print(f'{"DISABLED" if disabled else "ENABLED"}, ', end='')
|
print(f'{"DISABLED" if disabled else "ENABLED"}, ', end='')
|
||||||
print(f'{"DDIPL" if dd_mode else "ROM"}, ', end='')
|
print(f'{"DDIPL" if dd_mode else "ROM"}, ', end='')
|
||||||
print(f'seed: 0x{seed:02X}, checksum: 0x{checksum:012X}', end='')
|
print(f'seed: 0x{seed:02X}, checksum: 0x{checksum:012X}', end='')
|
||||||
print(']')
|
print(']')
|
||||||
else:
|
else:
|
||||||
sc64.set_cic_parameters()
|
sc64.update_cic_parameters()
|
||||||
|
|
||||||
if (args.debug or args.isv or args.disk or args.gdb):
|
if (args.debug or args.isv or args.disk or args.gdb):
|
||||||
sc64.debug_loop(isv=args.isv, disks=args.disk, gdb_port=args.gdb)
|
sc64.debug_loop(isv=args.isv, disks=args.disk, gdb_port=args.gdb)
|
||||||
@ -1126,12 +1348,13 @@ if __name__ == '__main__':
|
|||||||
f.write(sc64.download_save())
|
f.write(sc64.download_save())
|
||||||
print('done')
|
print('done')
|
||||||
|
|
||||||
if (args.download_memory):
|
if (args.download_memory != None):
|
||||||
with open(args.download_memory, 'wb') as f:
|
(address, length, file) = args.download_memory
|
||||||
|
with open(file, 'wb') as f:
|
||||||
print('Downloading memory... ', end='', flush=True)
|
print('Downloading memory... ', end='', flush=True)
|
||||||
f.write(sc64.download_memory())
|
f.write(sc64.download_memory(address, length))
|
||||||
print('done')
|
print('done')
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(f'\nValue error: {e}')
|
print(f'\n\x1b[31mValue error: {e}\x1b[0m\n')
|
||||||
except ConnectionException as e:
|
except ConnectionException as e:
|
||||||
print(f'\nSC64 error: {e}')
|
print(f'\n\x1b[31mSC64 error: {e}\x1b[0m\n')
|
||||||
|
Loading…
Reference in New Issue
Block a user