Merge remote-tracking branch 'origin/main' into pyinstaller

This commit is contained in:
Mateusz Faderewski 2023-02-24 17:42:48 +01:00
commit e627e11bb7
27 changed files with 1052 additions and 1022 deletions

View File

@ -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
View 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
View 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.

View 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.

View File

@ -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):

View 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
View File

@ -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.

View File

@ -5,7 +5,6 @@ set -e
PACKAGE_FILE_NAME="SC64"
TOP_FILES=(
"./sw/pc/dd64.py"
"./sw/pc/primer.py"
"./sw/pc/requirements.txt"
"./sw/pc/sc64.py"

View File

@ -6,6 +6,7 @@
- [Running 64DD games](#running-64dd-games)
- [Direct boot option](#direct-boot-option)
- [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.
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
`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.

View File

@ -5,7 +5,7 @@
- [SC64 registers](#sc64-registers)
- [`0x1FFF_0000`: **STATUS/COMMAND**](#0x1fff_0000-statuscommand)
- [`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)
- [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.
| name | address | size | access | usage |
| ------------------ | ------------- | ------- | ------ | -------------------------------- |
| ------------------ | ------------- | ------- | ------ | ---------------------------------- |
| **STATUS/COMMAND** | `0x1FFF_0000` | 4 bytes | RW | Command execution and status |
| **DATA0** | `0x1FFF_0004` | 4 bytes | RW | Command argument/result 0 |
| **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 |
---
@ -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 |
| --------- | ------ | ------ | --------------------------------------------- |
| `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.

View File

@ -5,24 +5,24 @@
## USB commands
| id | name | arg0 | arg1 | data | response | description |
| --- | ---------------------- | ------------ | ------------ | ---- | ---------------- | --------------------------------------------------- |
| `v` | **HW_VERSION_GET** | --- | --- | --- | hw_version | Get HW version |
| `V` | **API_VERSION_GET** | --- | --- | --- | api_version | Get USB command API version |
| `R` | **STATE_RESET** | --- | --- | --- | --- | Reset entire flashcart state |
| `B` | **CIC_PARAMS_SET** | cic_params_0 | cic_params_1 | --- | --- | Set CIC disable/mode/seed/checksum |
| --- | ---------------------- | ------------ | ------------ | ---- | ---------------- | ------------------------------------------------------------- |
| `v` | **IDENTIFIER_GET** | --- | --- | --- | identifier | Get flashcart identifier `SCv2` |
| `V` | **VERSION_GET** | --- | --- | --- | version | Get flashcart firmware version |
| `R` | **STATE_RESET** | --- | --- | --- | --- | Reset flashcart state (CIC params and config options) |
| `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_SET** | config_id | new_value | --- | --- | Set config option |
| `a` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting |
| `A` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting |
| `a` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting option |
| `A` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting option |
| `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_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 |
| `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_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 |
| `%` | **STACK_USAGE_GET** | --- | --- | --- | stack_usage | Get per task stack usage |

View File

@ -6,14 +6,14 @@
| id | name | arg0 | arg1 | rsp0 | rsp1 | description |
| --- | --------------------- | ---------- | ------------ | ---------------- | -------------- | -------------------------------------------------- |
| `v` | **HW_VERSION_GET** | --- | --- | hw_version | --- | Get HW version |
| `V` | **API_VERSION_GET** | --- | --- | api_version | --- | Get N64 command API version |
| `v` | **IDENTIFIER_GET** | --- | --- | identifier | --- | Get flashcart identifier `SCv2` |
| `V` | **VERSION_GET** | --- | --- | version | --- | Get flashcart firmware version |
| `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` | **SETTING_GET** | setting_id | --- | --- | current_value | Get 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_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_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 |
@ -27,4 +27,3 @@
| `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_ERASE_BLOCK** | pi_address | --- | --- | --- | Start flash block erase |
| `?` | **DEBUG_GET** | --- | --- | debug_data_0 | debug_data_1 | Get internal FPGA debug info |

View File

@ -57,6 +57,8 @@ There are no special requirements for soldering components to board. All chips a
### **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).
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
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
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
9. Run `python3 primer.py COMx sc64_firmware.bin` (replace `COMx` with port located in step **4**)
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!

View File

@ -332,7 +332,7 @@ module mcu_top (
REG_CFG_DATA_0,
REG_CFG_DATA_1,
REG_CFG_CMD,
REG_CFG_VERSION,
REG_CFG_IDENTIFIER,
REG_FLASHRAM_SCR,
REG_FLASH_SCR,
REG_RTC_SCR,
@ -363,7 +363,7 @@ module mcu_top (
logic bootloader_skip;
assign n64_scb.cfg_version = 32'h53437632;
assign n64_scb.cfg_identifier = 32'h53437632;
logic dd_bm_ack;
@ -460,8 +460,8 @@ module mcu_top (
};
end
REG_CFG_VERSION: begin
reg_rdata <= n64_scb.cfg_version;
REG_CFG_IDENTIFIER: begin
reg_rdata <= n64_scb.cfg_identifier;
end
REG_FLASHRAM_SCR: begin

View File

@ -16,8 +16,8 @@ module n64_cfg (
REG_DATA_0_L,
REG_DATA_1_H,
REG_DATA_1_L,
REG_VERSION_H,
REG_VERSION_L,
REG_IDENTIFIER_H,
REG_IDENTIFIER_L,
REG_KEY_H,
REG_KEY_L
} 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_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_VERSION_H: reg_bus.rdata = n64_scb.cfg_version[31:16];
REG_VERSION_L: reg_bus.rdata = n64_scb.cfg_version[15:0];
REG_IDENTIFIER_H: reg_bus.rdata = n64_scb.cfg_identifier[31:16];
REG_IDENTIFIER_L: reg_bus.rdata = n64_scb.cfg_identifier[15:0];
REG_KEY_H: reg_bus.rdata = 16'd0;
REG_KEY_L: reg_bus.rdata = 16'd0;
endcase
@ -83,7 +83,7 @@ module n64_cfg (
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_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
lock_sequence_counter <= lock_sequence_counter + 1'd1;
if (reg_bus.wdata != 16'hFFFF) begin

View File

@ -51,7 +51,7 @@ interface n64_scb ();
logic [7:0] cfg_cmd;
logic [31:0] cfg_rdata [0:1];
logic [31:0] cfg_wdata [0:1];
logic [31:0] cfg_version;
logic [31:0] cfg_identifier;
logic [15:0] save_count;
@ -94,7 +94,7 @@ interface n64_scb ();
input cfg_cmd,
input cfg_rdata,
output cfg_wdata,
output cfg_version,
output cfg_identifier,
input save_count,
@ -194,7 +194,7 @@ interface n64_scb ();
output cfg_cmd,
output cfg_rdata,
input cfg_wdata,
input cfg_version
input cfg_identifier
);
modport save_counter (

View File

@ -5,7 +5,7 @@
typedef struct {
io32_t SR_CMD;
io32_t DATA[2];
io32_t VERSION;
io32_t IDENTIFIER;
io32_t KEY;
} sc64_regs_t;
@ -16,7 +16,7 @@ typedef struct {
#define SC64_SR_CMD_ERROR (1 << 30)
#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_UNLOCK_1 (0x5F554E4CUL)
@ -24,8 +24,8 @@ typedef struct {
#define SC64_KEY_LOCK (0xFFFFFFFFUL)
typedef enum {
SC64_CMD_HW_VERSION_GET = 'v',
SC64_CMD_API_VERSION_GET = 'V',
SC64_CMD_IDENTIFIER_GET = 'v',
SC64_CMD_VERSION_GET = 'V',
SC64_CMD_CONFIG_GET = 'c',
SC64_CMD_CONFIG_SET = 'C',
SC64_CMD_SETTING_GET = 'a',
@ -45,7 +45,6 @@ typedef enum {
SC64_CMD_FLASH_PROGRAM = 'K',
SC64_CMD_FLASH_WAIT_BUSY = 'p',
SC64_CMD_FLASH_ERASE_BLOCK = 'P',
SC64_CMD_DEBUG_GET = '?',
} cmd_id_t;
typedef enum {
@ -98,8 +97,8 @@ void sc64_lock (void) {
}
bool sc64_check_presence (void) {
uint32_t version = pi_io_read(&SC64_REGS->VERSION);
if (version == SC64_VERSION_2) {
uint32_t identifier = pi_io_read(&SC64_REGS->IDENTIFIER);
if (identifier == SC64_V2_IDENTIFIER) {
sc64_wait_cpu_busy();
return true;
}
@ -114,7 +113,7 @@ bool sc64_irq_pending (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) {

View File

@ -7,20 +7,31 @@
static void test_rtc (void) {
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);
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(" %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(" (%s)", weekdays[FROM_BCD(t.weekday)]);
display_printf("\n");
}
static void test_sd_card (void) {
sc64_sd_card_status_t card_status;
uint8_t card_info[32] __attribute__((aligned(8)));
uint8_t sector[512] __attribute__((aligned(8)));
card_status = sc64_sd_card_get_status();
@ -66,6 +77,25 @@ static void test_sd_card (void) {
for (int i = 16; i < 32; i++) {
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]);
}

View File

@ -221,8 +221,8 @@ static bool cfg_set_save_type (save_type_t save_type) {
}
uint32_t cfg_get_version (void) {
return fpga_reg_get(REG_CFG_VERSION);
uint32_t cfg_get_identifier (void) {
return fpga_reg_get(REG_CFG_IDENTIFIER);
}
bool cfg_query (uint32_t *args) {
@ -468,11 +468,11 @@ void cfg_process (void) {
switch (cmd) {
case 'v':
args[0] = cfg_get_version();
args[0] = cfg_get_identifier();
break;
case 'V':
args[0] = version_api(API_N64);
args[0] = version_firmware();
break;
case 'c':
@ -666,11 +666,6 @@ void cfg_process (void) {
}
break;
case '?':
args[0] = fpga_reg_get(REG_DEBUG_0);
args[1] = fpga_reg_get(REG_DEBUG_1);
break;
default:
cfg_set_error(CFG_ERROR_UNKNOWN_CMD);
return;

View File

@ -16,7 +16,7 @@ typedef enum {
} save_type_t;
uint32_t cfg_get_version (void);
uint32_t cfg_get_identifier (void);
bool cfg_query (uint32_t *args);
bool cfg_update (uint32_t *args);
bool cfg_query_setting (uint32_t *args);

View File

@ -28,7 +28,7 @@ typedef enum {
REG_CFG_DATA_0,
REG_CFG_DATA_1,
REG_CFG_CMD,
REG_CFG_VERSION,
REG_CFG_IDENTIFIER,
REG_FLASHRAM_SCR,
REG_FLASH_SCR,
REG_RTC_SCR,

View File

@ -33,7 +33,8 @@
#define SWITCH_FUNCTION_GROUP_1_HS (1 << 1)
#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 {
@ -300,7 +301,7 @@ bool sd_card_init (void) {
sd_card_deinit();
return true;
}
sd_dat_wait(DAT_TIMEOUT_MS);
sd_dat_wait(DAT_TIMEOUT_INIT_MS);
if (sd_did_timeout()) {
sd_card_deinit();
return true;
@ -318,7 +319,7 @@ bool sd_card_init (void) {
sd_card_deinit();
return true;
}
sd_dat_wait(DAT_TIMEOUT_MS);
sd_dat_wait(DAT_TIMEOUT_INIT_MS);
if (sd_did_timeout()) {
sd_card_deinit();
return true;
@ -382,14 +383,11 @@ bool sd_write_sectors (uint32_t address, uint32_t sector, uint32_t count) {
while (count > 0) {
uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count);
led_blink_act();
if (sd_cmd(23, blocks, RSP_R1, NULL)) {
return true;
}
if (sd_cmd(25, sector, RSP_R1, NULL)) {
return true;
}
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_cmd(12, 0, RSP_R1b, NULL);
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);
led_blink_act();
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)) {
sd_dat_abort();
return true;
}
if (sd_dat_wait(DAT_TIMEOUT_MS)) {
if (sd_dat_wait(DAT_TIMEOUT_DATA_MS)) {
if (sd_did_timeout()) {
sd_cmd(12, 0, RSP_R1b, NULL);
}
return true;
}
sd_cmd(12, 0, RSP_R1b, NULL);
address += (blocks * SD_SECTOR_SIZE);
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
count -= blocks;

View File

@ -160,14 +160,14 @@ static void usb_rx_process (void) {
p.rx_state = RX_STATE_IDLE;
p.response_pending = true;
p.response_info.data_length = 4;
p.response_info.data[0] = cfg_get_version();
p.response_info.data[0] = cfg_get_identifier();
break;
case 'V':
p.rx_state = RX_STATE_IDLE;
p.response_pending = true;
p.response_info.data_length = 4;
p.response_info.data[0] = version_api(API_USB);
p.response_info.data[0] = version_firmware();
break;
case 'R':
@ -247,12 +247,6 @@ static void usb_rx_process (void) {
}
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':
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;
@ -274,6 +268,28 @@ static void usb_rx_process (void) {
}
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': {
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]);
@ -300,22 +316,6 @@ static void usb_rx_process (void) {
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 '?':
p.rx_state = RX_STATE_IDLE;
p.response_pending = true;

View File

@ -1,17 +1,10 @@
#include "version.h"
#define VERSION_API_USB (2)
#define VERSION_API_N64 (2)
#define VERSION_MAJOR (2)
#define VERSION_MINOR (12)
uint32_t version_api (version_api_type_t type) {
switch (type) {
case API_USB:
return VERSION_API_USB;
case API_N64:
return VERSION_API_N64;
default:
return 0;
}
uint32_t version_firmware (void) {
return ((VERSION_MAJOR << 16) | (VERSION_MINOR));
}

View File

@ -5,13 +5,7 @@
#include <stdint.h>
typedef enum {
API_USB,
API_N64,
} version_api_type_t;
uint32_t version_api (version_api_type_t type);
uint32_t version_firmware (void);
#endif

View File

@ -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')

View File

@ -9,8 +9,8 @@ import sys
import time
from binascii import crc32
from datetime import datetime
from dd64 import BadBlockError, DD64Image
from enum import Enum, IntEnum
from io import BufferedReader
from serial.tools import list_ports
from threading import Thread
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):
pass
@ -200,6 +404,7 @@ class SC64:
FLASH = 0x0400_0000
BUFFER = 0x0500_0000
EEPROM = 0x0500_2000
END = 0x0500_297F
FIRMWARE = 0x0200_0000
DDIPL = 0x03BC_0000
SAVE = 0x03FE_0000
@ -321,7 +526,8 @@ class SC64:
SCREENSHOT = 4
GDB = 0xDB
__MIN_SUPPORTED_API_VERSION = 2
__SUPPORTED_MAJOR_VERSION = 2
__SUPPORTED_MINOR_VERSION = 12
__isv_line_buffer: bytes = b''
__debug_header: Optional[bytes] = None
@ -329,21 +535,25 @@ class SC64:
def __init__(self) -> None:
self.__link = SC64Serial()
version = self.__link.execute_cmd(cmd=b'v')
if (version != b'SCv2'):
raise ConnectionException('Unknown SC64 HW version')
identifier = self.__link.execute_cmd(cmd=b'v')
if (identifier != b'SCv2'):
raise ConnectionException('Unknown SC64 v2 identifier')
def __get_int(self, data: bytes) -> int:
return int.from_bytes(data[:4], byteorder='big')
def check_api_version(self) -> None:
def check_firmware_version(self) -> tuple[str, bool]:
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:
raise ConnectionException('Outdated SC64 API, please update firmware')
version = self.__get_int(data)
if (version < self.__MIN_SUPPORTED_API_VERSION):
raise ConnectionException('Unsupported SC64 API version, please update firmware')
raise ConnectionException(f'Unsupported SC64 version [{major}.{minor}], please update firmware')
def __set_config(self, config: __CfgId, value: int) -> None:
try:
@ -461,8 +671,10 @@ class SC64:
raise ValueError('Debug data size too big')
self.__link.execute_cmd(cmd=b'U', args=[datatype, len(data)], data=data, response=False)
def download_memory(self) -> bytes:
return self.__read_memory(self.__Address.MEMORY, self.__Length.MEMORY)
def download_memory(self, address: int, length: int) -> bytes:
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:
rom_length = len(data)
@ -582,7 +794,7 @@ class SC64:
raise ConnectionException('Error while getting firmware backup')
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)):
raise ValueError('CIC seed outside of allowed values')
boot_mode = self.__get_config(self.__CfgId.BOOT_MODE)
@ -884,7 +1096,7 @@ class SC64:
current_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)
if (isv != 0):
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
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.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-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('--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('--tv', type=SC64.TVType, action=EnumAction, help='force TV type to set value')
parser.add_argument('--cic', type=SC64.CICSeed, action=EnumAction, help='force CIC seed 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, 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('--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)')
@ -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('--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('--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):
parser.print_help()
@ -1039,13 +1259,15 @@ if __name__ == '__main__':
sc64.update_firmware(f.read(), status_callback)
print('done')
sc64.check_api_version()
(version, script_outdated) = sc64.check_firmware_version()
if (args.update_bootloader):
with open(args.update_bootloader, 'rb') as f:
print('Uploading Bootloader... ', end='', flush=True)
sc64.upload_bootloader(f.read())
print('done')
print(f'SC64 firmware version: [{version}]')
if (script_outdated):
print('\x1b[33m')
print('[ SC64 firmware is newer than last known version. ]')
print('[ Consider downloading latest script from ]')
print('[ https://github.com/Polprzewodnikowy/SummerCart64/releases ]')
print('\x1b[0m')
if (args.reset_state):
sc64.reset_state()
@ -1059,10 +1281,10 @@ if __name__ == '__main__':
value = getattr(value, 'name')
print(f' {key}: {value}')
if (args.led_enabled != None):
value = (args.led_enabled == 'true')
sc64.set_led_enable(value)
print(f'LED blinking set to [{"ENABLED" if value else "DISABLED"}]')
if (args.led_blink):
blink = (args.led_blink == 'yes')
sc64.set_led_enable(blink)
print(f'LED blinking set to [{"ENABLED" if blink else "DISABLED"}]')
if (args.tv != None):
sc64.set_tv_type(args.tv)
@ -1085,6 +1307,12 @@ if __name__ == '__main__':
autodetected_save_type = sc64.autodetect_save_type(rom_data)
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):
save_type = args.save_type if args.save_type != None else autodetected_save_type
sc64.set_save_type(save_type)
@ -1096,26 +1324,20 @@ if __name__ == '__main__':
sc64.upload_save(f.read())
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):
sc64.set_boot_mode(args.boot)
print(f'Boot mode set to [{args.boot.name}]')
if (args.cic_params != None):
(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(f'{"DISABLED" if disabled else "ENABLED"}, ', end='')
print(f'{"DDIPL" if dd_mode else "ROM"}, ', end='')
print(f'seed: 0x{seed:02X}, checksum: 0x{checksum:012X}', end='')
print(']')
else:
sc64.set_cic_parameters()
sc64.update_cic_parameters()
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)
@ -1126,12 +1348,13 @@ if __name__ == '__main__':
f.write(sc64.download_save())
print('done')
if (args.download_memory):
with open(args.download_memory, 'wb') as f:
if (args.download_memory != None):
(address, length, file) = args.download_memory
with open(file, 'wb') as f:
print('Downloading memory... ', end='', flush=True)
f.write(sc64.download_memory())
f.write(sc64.download_memory(address, length))
print('done')
except ValueError as e:
print(f'\nValue error: {e}')
print(f'\n\x1b[31mValue error: {e}\x1b[0m\n')
except ConnectionException as e:
print(f'\nSC64 error: {e}')
print(f'\n\x1b[31mSC64 error: {e}\x1b[0m\n')