[SC64][SW] Server performance increase / USB save writeback / bootloader fixes (#37)

This commit is contained in:
Mateusz Faderewski 2023-04-04 20:25:58 +02:00 committed by GitHub
parent 066f3b0485
commit 7afb0ef05c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 997 additions and 660 deletions

View File

@ -20,6 +20,7 @@
| `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!) | | `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 |
| `W` | **WRITEBACK_ENABLE** | --- | --- | --- | --- | Enable save writeback through USB packet |
| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size | | `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 | | `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 |

View File

@ -132,9 +132,10 @@ type: *enum* | default: `0`
- `3` - SRAM 256 kib save is enabled - `3` - SRAM 256 kib save is enabled
- `4` - FlashRAM 1 Mib save is enabled - `4` - FlashRAM 1 Mib save is enabled
- `5` - SRAM 768 kib save is enabled - `5` - SRAM 768 kib save is enabled
- `6` - SRAM 1 Mib save is enabled
Use this setting for selecting save type that will be emulated. Only one save type can be enabled. Use this setting for selecting save type that will be emulated. Only one save type can be enabled.
Any successful write to this config will disable automatic save writeback to SD card when previously enabled. Any successful write to this config will disable automatic save writeback to USB or SD card when previously enabled.
--- ---
@ -158,10 +159,10 @@ type: *enum* | default: `3`
- `0` - PAL TV type will be used - `0` - PAL TV type will be used
- `1` - NTSC TV type will be used - `1` - NTSC TV type will be used
- `2` - MPAL TV type will be used - `2` - MPAL TV type will be used
- `3` - TV type will be autodetected - `3` - Console native TV type will be used
Use this setting to force specific TV type. Use this setting to force specific TV type.
By setting value `3` bootloader will try to guess TV type from loaded ROM header. By setting value `3` bootloader will passthrough TV type native to the console.
This setting is not used when **BOOT_MODE** is set to `3` or `4` (direct boot). This setting is not used when **BOOT_MODE** is set to `3` or `4` (direct boot).
--- ---

View File

@ -37,33 +37,6 @@ static io32_t *boot_get_device_base (boot_info_t *info) {
return device_base_address; return device_base_address;
} }
static bool boot_get_tv_type (boot_info_t *info) {
io32_t *base = boot_get_device_base(info);
char region = ((pi_io_read(&base[15]) >> 8) & 0xFF);
switch (region) {
case 'P':
case 'U':
info->tv_type = BOOT_TV_TYPE_PAL;
break;
case 'E':
case 'J':
info->tv_type = BOOT_TV_TYPE_NTSC;
break;
case 'B':
info->tv_type = BOOT_TV_TYPE_MPAL;
break;
default:
return false;
}
return true;
}
static bool boot_get_cic_seed (boot_info_t *info) { static bool boot_get_cic_seed (boot_info_t *info) {
io32_t *base = boot_get_device_base(info); io32_t *base = boot_get_device_base(info);
@ -83,12 +56,10 @@ static bool boot_get_cic_seed (boot_info_t *info) {
return false; return false;
} }
void boot (boot_info_t *info, bool detect_tv_type, bool detect_cic_seed) { void boot (boot_info_t *info, bool detect_cic_seed) {
if (detect_tv_type) { if (info->tv_type == BOOT_TV_TYPE_PASSTHROUGH) {
if (!boot_get_tv_type(info)) {
info->tv_type = OS_INFO->tv_type; info->tv_type = OS_INFO->tv_type;
} }
}
if (detect_cic_seed) { if (detect_cic_seed) {
if (!boot_get_cic_seed(info)) { if (!boot_get_cic_seed(info)) {

View File

@ -20,6 +20,7 @@ typedef enum {
BOOT_TV_TYPE_PAL = 0, BOOT_TV_TYPE_PAL = 0,
BOOT_TV_TYPE_NTSC = 1, BOOT_TV_TYPE_NTSC = 1,
BOOT_TV_TYPE_MPAL = 2, BOOT_TV_TYPE_MPAL = 2,
BOOT_TV_TYPE_PASSTHROUGH = 3,
} boot_tv_type_t; } boot_tv_type_t;
@ -31,7 +32,7 @@ typedef struct {
} boot_info_t; } boot_info_t;
void boot (boot_info_t *info, bool detect_tv_type, bool detect_cic_seed); void boot (boot_info_t *info, bool detect_cic_seed);
#endif #endif

View File

@ -13,18 +13,14 @@
#define TEXT_COLOR (0xFFFFFFFFUL) #define TEXT_COLOR (0xFFFFFFFFUL)
#define LINE_SPACING (2) #define LINE_SPACING (2)
#define VI_CR (VI_CR_PIXEL_ADVANCE_1 | VI_CR_PIXEL_ADVANCE_0 | VI_CR_ANTIALIAS_1 | VI_CR_ANTIALIAS_0 | VI_CR_TYPE_32)
static io32_t display_framebuffer[SCREEN_WIDTH * SCREEN_HEIGHT] __attribute__((section(".framebuffer, \"aw\", %nobits#"))); static io32_t display_framebuffer[SCREEN_WIDTH * SCREEN_HEIGHT] __attribute__((section(".framebuffer, \"aw\", %nobits#")));
static int char_x; static int char_x;
static int char_y; static int char_y;
static const vi_regs_t vi_config[] = {{ static const vi_regs_t vi_config[] = {{
.CR = ( .CR = VI_CR,
VI_CR_PIXEL_ADVANCE_1 |
VI_CR_PIXEL_ADVANCE_0 |
VI_CR_ANTIALIAS_1 |
VI_CR_ANTIALIAS_0 |
VI_CR_TYPE_32
),
.H_WIDTH = SCREEN_WIDTH, .H_WIDTH = SCREEN_WIDTH,
.V_INTR = 0x000003FF, .V_INTR = 0x000003FF,
.CURR_LINE = 0x00000000, .CURR_LINE = 0x00000000,
@ -38,13 +34,7 @@ static const vi_regs_t vi_config[] = {{
.H_SCALE = 0x00000400, .H_SCALE = 0x00000400,
.V_SCALE = 0x00000400, .V_SCALE = 0x00000400,
}, { }, {
.CR = ( .CR = VI_CR,
VI_CR_PIXEL_ADVANCE_1 |
VI_CR_PIXEL_ADVANCE_0 |
VI_CR_ANTIALIAS_1 |
VI_CR_ANTIALIAS_0 |
VI_CR_TYPE_32
),
.H_WIDTH = SCREEN_WIDTH, .H_WIDTH = SCREEN_WIDTH,
.V_INTR = 0x000003FF, .V_INTR = 0x000003FF,
.CURR_LINE = 0x00000000, .CURR_LINE = 0x00000000,
@ -57,6 +47,20 @@ static const vi_regs_t vi_config[] = {{
.COLOR_BURST = 0x000E0204, .COLOR_BURST = 0x000E0204,
.H_SCALE = 0x00000400, .H_SCALE = 0x00000400,
.V_SCALE = 0x00000400, .V_SCALE = 0x00000400,
}, {
.CR = VI_CR,
.H_WIDTH = SCREEN_WIDTH,
.V_INTR = 0x000003FF,
.CURR_LINE = 0x00000000,
.TIMING = 0x04651E39,
.V_SYNC = 0x0000020D,
.H_SYNC = 0x00040C11,
.H_SYNC_LEAP = 0x0C190C1A,
.H_LIMITS = 0x006C02EC,
.V_LIMITS = 0x002501FF,
.COLOR_BURST = 0x000E0204,
.H_SCALE = 0x00000400,
.V_SCALE = 0x00000400,
}}; }};

View File

@ -30,14 +30,12 @@ void main (void) {
break; break;
} }
bool detect_tv_type = (sc64_boot_info.tv_type == TV_TYPE_UNKNOWN);
bool detect_cic_seed = (sc64_boot_info.cic_seed == CIC_SEED_UNKNOWN);
boot_info.reset_type = OS_INFO->reset_type; boot_info.reset_type = OS_INFO->reset_type;
boot_info.tv_type = sc64_boot_info.tv_type; boot_info.tv_type = sc64_boot_info.tv_type;
boot_info.cic_seed = (sc64_boot_info.cic_seed & 0xFF); boot_info.cic_seed = (sc64_boot_info.cic_seed & 0xFF);
bool detect_cic_seed = (sc64_boot_info.cic_seed == CIC_SEED_AUTO);
deinit(); deinit();
boot(&boot_info, detect_tv_type, detect_cic_seed); boot(&boot_info, detect_cic_seed);
} }

View File

@ -59,18 +59,19 @@ typedef enum {
SAVE_TYPE_EEPROM_16K = 2, SAVE_TYPE_EEPROM_16K = 2,
SAVE_TYPE_SRAM = 3, SAVE_TYPE_SRAM = 3,
SAVE_TYPE_FLASHRAM = 4, SAVE_TYPE_FLASHRAM = 4,
SAVE_TYPE_SRAM_BANKED = 5 SAVE_TYPE_SRAM_BANKED = 5,
SAVE_TYPE_SRAM_1M = 6
} sc64_save_type_t; } sc64_save_type_t;
typedef enum { typedef enum {
CIC_SEED_UNKNOWN = 0xFFFF CIC_SEED_AUTO = 0xFFFF
} sc64_cic_seed_t; } sc64_cic_seed_t;
typedef enum { typedef enum {
TV_TYPE_PAL = 0, TV_TYPE_PAL = 0,
TV_TYPE_NTSC = 1, TV_TYPE_NTSC = 1,
TV_TYPE_MPAL = 2, TV_TYPE_MPAL = 2,
TV_TYPE_UNKNOWN = 3 TV_TYPE_PASSTHROUGH = 3
} sc64_tv_type_t; } sc64_tv_type_t;
typedef enum { typedef enum {

View File

@ -54,14 +54,14 @@ typedef enum {
} boot_mode_t; } boot_mode_t;
typedef enum { typedef enum {
CIC_SEED_UNKNOWN = 0xFFFF CIC_SEED_AUTO = 0xFFFF
} cic_seed_t; } cic_seed_t;
typedef enum { typedef enum {
TV_TYPE_PAL = 0, TV_TYPE_PAL = 0,
TV_TYPE_NTSC = 1, TV_TYPE_NTSC = 1,
TV_TYPE_MPAL = 2, TV_TYPE_MPAL = 2,
TV_TYPE_UNKNOWN = 3 TV_TYPE_PASSTHROUGH = 3
} tv_type_t; } tv_type_t;
typedef enum { typedef enum {
@ -210,6 +210,9 @@ static bool cfg_set_save_type (save_type_t save_type) {
case SAVE_TYPE_SRAM_BANKED: case SAVE_TYPE_SRAM_BANKED:
cfg_change_scr_bits(CFG_SCR_SRAM_BANKED | CFG_SCR_SRAM_ENABLED, true); cfg_change_scr_bits(CFG_SCR_SRAM_BANKED | CFG_SCR_SRAM_ENABLED, true);
break; break;
case SAVE_TYPE_SRAM_1M:
cfg_change_scr_bits(CFG_SCR_SRAM_ENABLED, true);
break;
default: default:
save_type = SAVE_TYPE_NONE; save_type = SAVE_TYPE_NONE;
break; break;
@ -337,7 +340,7 @@ bool cfg_update (uint32_t *args) {
p.cic_seed = (cic_seed_t) (args[1] & 0xFFFF); p.cic_seed = (cic_seed_t) (args[1] & 0xFFFF);
break; break;
case CFG_ID_TV_TYPE: case CFG_ID_TV_TYPE:
if (args[1] > TV_TYPE_UNKNOWN) { if (args[1] > TV_TYPE_PASSTHROUGH) {
return true; return true;
} }
p.tv_type = (tv_type_t) (args[1] & 0x03); p.tv_type = (tv_type_t) (args[1] & 0x03);
@ -440,8 +443,8 @@ void cfg_reset_state (void) {
dd_set_disk_state(DD_DISK_STATE_EJECTED); dd_set_disk_state(DD_DISK_STATE_EJECTED);
dd_set_sd_mode(false); dd_set_sd_mode(false);
isv_set_address(0); isv_set_address(0);
p.cic_seed = CIC_SEED_UNKNOWN; p.cic_seed = CIC_SEED_AUTO;
p.tv_type = TV_TYPE_UNKNOWN; p.tv_type = TV_TYPE_PASSTHROUGH;
p.boot_mode = BOOT_MODE_MENU; p.boot_mode = BOOT_MODE_MENU;
} }
@ -628,7 +631,7 @@ void cfg_process (void) {
return; return;
} }
writeback_load_sector_table(args[0]); writeback_load_sector_table(args[0]);
writeback_enable(); writeback_enable(WRITEBACK_SD);
break; break;
case 'K': case 'K':

View File

@ -12,7 +12,8 @@ typedef enum {
SAVE_TYPE_EEPROM_16K = 2, SAVE_TYPE_EEPROM_16K = 2,
SAVE_TYPE_SRAM = 3, SAVE_TYPE_SRAM = 3,
SAVE_TYPE_FLASHRAM = 4, SAVE_TYPE_FLASHRAM = 4,
SAVE_TYPE_SRAM_BANKED = 5 SAVE_TYPE_SRAM_BANKED = 5,
SAVE_TYPE_SRAM_1M = 6,
} save_type_t; } save_type_t;

View File

@ -8,6 +8,7 @@
#include "update.h" #include "update.h"
#include "usb.h" #include "usb.h"
#include "version.h" #include "version.h"
#include "writeback.h"
#define BOOTLOADER_ADDRESS (0x04E00000UL) #define BOOTLOADER_ADDRESS (0x04E00000UL)
@ -307,6 +308,12 @@ static void usb_rx_process (void) {
p.response_pending = true; p.response_pending = true;
break; break;
case 'W':
writeback_enable(WRITEBACK_USB);
p.rx_state = RX_STATE_IDLE;
p.response_pending = true;
break;
case 'p': case 'p':
if (p.rx_args[0]) { if (p.rx_args[0]) {
flash_wait_busy(); flash_wait_busy();

View File

@ -11,6 +11,7 @@ typedef enum packet_cmd {
PACKET_CMD_DD_REQUEST = 'D', PACKET_CMD_DD_REQUEST = 'D',
PACKET_CMD_DEBUG_OUTPUT = 'U', PACKET_CMD_DEBUG_OUTPUT = 'U',
PACKET_CMD_ISV_OUTPUT = 'I', PACKET_CMD_ISV_OUTPUT = 'I',
PACKET_CMD_SAVE_WRITEBACK = 'S',
PACKET_CMD_UPDATE_STATUS = 'F', PACKET_CMD_UPDATE_STATUS = 'F',
} usb_packet_cmd_e; } usb_packet_cmd_e;

View File

@ -2,23 +2,26 @@
#include "fpga.h" #include "fpga.h"
#include "sd.h" #include "sd.h"
#include "timer.h" #include "timer.h"
#include "usb.h"
#include "writeback.h" #include "writeback.h"
#define SAVE_MAX_SECTOR_COUNT (256) #define SAVE_MAX_SECTOR_COUNT (256)
#define EEPROM_ADDRESS (0x05002000) #define EEPROM_ADDRESS (0x05002000)
#define SRAM_FLASHRAM_ADDRESS (0x03FE0000) #define SRAM_FLASHRAM_ADDRESS (0x03FE0000)
#define EEPROM_4K_SECTOR_COUNT (1) #define EEPROM_4K_LENGTH (512)
#define EEPROM_16K_SECTOR_COUNT (4) #define EEPROM_16K_LENGTH (2048)
#define SRAM_SECTOR_COUNT (64) #define SRAM_LENGTH (32 * 1024)
#define FLASHRAM_SECTOR_COUNT (256) #define FLASHRAM_LENGTH (128 * 1024)
#define SRAM_BANKED_SECTOR_COUNT (192) #define SRAM_BANKED_LENGTH (3 * 32 * 1024)
#define SRAM_1M_LENGTH (128 * 1024)
#define WRITEBACK_DELAY_TICKS (100) #define WRITEBACK_DELAY_TICKS (100)
struct process { struct process {
bool enabled; bool enabled;
bool pending; bool pending;
writeback_mode_t mode;
uint16_t last_save_count; uint16_t last_save_count;
uint32_t sectors[SAVE_MAX_SECTOR_COUNT]; uint32_t sectors[SAVE_MAX_SECTOR_COUNT];
}; };
@ -27,41 +30,79 @@ struct process {
static struct process p; static struct process p;
static void writeback_save_to_sd (void) { static save_type_t writeback_get_address_length (uint32_t *address, uint32_t *length) {
uint32_t address; save_type_t save = cfg_get_save_type();
uint32_t count;
switch (cfg_get_save_type()) { switch (save) {
case SAVE_TYPE_EEPROM_4K: case SAVE_TYPE_EEPROM_4K:
address = EEPROM_ADDRESS; *address = EEPROM_ADDRESS;
count = EEPROM_4K_SECTOR_COUNT; *length = EEPROM_4K_LENGTH;
break; break;
case SAVE_TYPE_EEPROM_16K: case SAVE_TYPE_EEPROM_16K:
address = EEPROM_ADDRESS; *address = EEPROM_ADDRESS;
count = EEPROM_16K_SECTOR_COUNT; *length = EEPROM_16K_LENGTH;
break; break;
case SAVE_TYPE_SRAM: case SAVE_TYPE_SRAM:
address = SRAM_FLASHRAM_ADDRESS; *address = SRAM_FLASHRAM_ADDRESS;
count = SRAM_SECTOR_COUNT; *length = SRAM_LENGTH;
break; break;
case SAVE_TYPE_FLASHRAM: case SAVE_TYPE_FLASHRAM:
address = SRAM_FLASHRAM_ADDRESS; *address = SRAM_FLASHRAM_ADDRESS;
count = FLASHRAM_SECTOR_COUNT; *length = FLASHRAM_LENGTH;
break; break;
case SAVE_TYPE_SRAM_BANKED: case SAVE_TYPE_SRAM_BANKED:
address = SRAM_FLASHRAM_ADDRESS; *address = SRAM_FLASHRAM_ADDRESS;
count = SRAM_BANKED_SECTOR_COUNT; *length = SRAM_BANKED_LENGTH;
break;
case SAVE_TYPE_SRAM_1M:
*address = SRAM_FLASHRAM_ADDRESS;
*length = SRAM_1M_LENGTH;
break; break;
default: default:
*address = 0;
*length = 0;
break;
}
return save;
}
static void writeback_save_to_sd (void) {
save_type_t save;
uint32_t address;
uint32_t length;
save = writeback_get_address_length(&address, &length);
if (save == SAVE_TYPE_NONE) {
writeback_disable(); writeback_disable();
return; return;
} }
if(sd_optimize_sectors(address, p.sectors, count, sd_write_sectors)) { if(sd_optimize_sectors(address, p.sectors, length / SD_SECTOR_SIZE, sd_write_sectors)) {
writeback_disable(); writeback_disable();
} }
} }
static bool writeback_save_to_usb (void) {
save_type_t save;
uint32_t address;
uint32_t length;
save = writeback_get_address_length(&address, &length);
if (save == SAVE_TYPE_NONE) {
writeback_disable();
return true;
}
usb_tx_info_t packet_info;
usb_create_packet(&packet_info, PACKET_CMD_SAVE_WRITEBACK);
packet_info.data_length = 4;
packet_info.data[0] = save;
packet_info.dma_length = length;
packet_info.dma_address = address;
return usb_enqueue_packet(&packet_info);
}
void writeback_load_sector_table (uint32_t address) { void writeback_load_sector_table (uint32_t address) {
fpga_mem_read(address, sizeof(p.sectors), (uint8_t *) (p.sectors)); fpga_mem_read(address, sizeof(p.sectors), (uint8_t *) (p.sectors));
@ -70,9 +111,10 @@ void writeback_load_sector_table (uint32_t address) {
} }
} }
void writeback_enable (void) { void writeback_enable (writeback_mode_t mode) {
p.enabled = true; p.enabled = true;
p.pending = false; p.pending = false;
p.mode = mode;
p.last_save_count = fpga_reg_get(REG_SAVE_COUNT); p.last_save_count = fpga_reg_get(REG_SAVE_COUNT);
} }
@ -85,13 +127,14 @@ void writeback_disable (void) {
void writeback_init (void) { void writeback_init (void) {
p.enabled = false; p.enabled = false;
p.pending = false; p.pending = false;
p.mode = WRITEBACK_SD;
for (int i = 0; i < SAVE_MAX_SECTOR_COUNT; i++) { for (int i = 0; i < SAVE_MAX_SECTOR_COUNT; i++) {
p.sectors[i] = 0; p.sectors[i] = 0;
} }
} }
void writeback_process (void) { void writeback_process (void) {
if (p.enabled && !sd_card_is_inserted()) { if (p.enabled && (p.mode == WRITEBACK_SD) && !sd_card_is_inserted()) {
writeback_disable(); writeback_disable();
} }
@ -105,7 +148,19 @@ void writeback_process (void) {
} }
if (p.pending && (timer_get(TIMER_ID_WRITEBACK) == 0)) { if (p.pending && (timer_get(TIMER_ID_WRITEBACK) == 0)) {
p.pending = false; switch (p.mode) {
case WRITEBACK_SD:
writeback_save_to_sd(); writeback_save_to_sd();
p.pending = false;
break;
case WRITEBACK_USB:
if (writeback_save_to_usb()) {
p.pending = false;
}
break;
default:
writeback_disable();
break;
}
} }
} }

View File

@ -9,8 +9,14 @@
#define WRITEBACK_SECTOR_TABLE_SIZE (1024) #define WRITEBACK_SECTOR_TABLE_SIZE (1024)
typedef enum {
WRITEBACK_SD,
WRITEBACK_USB,
} writeback_mode_t;
void writeback_load_sector_table (uint32_t address); void writeback_load_sector_table (uint32_t address);
void writeback_enable (void); void writeback_enable (writeback_mode_t mode);
void writeback_disable (void); void writeback_disable (void);
void writeback_init (void); void writeback_init (void);
void writeback_process (void); void writeback_process (void);

119
sw/deployer/Cargo.lock generated
View File

@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b" checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b"
dependencies = [ dependencies = [
"libc", "libc",
"mach", "mach 0.1.2",
] ]
[[package]] [[package]]
@ -20,7 +20,7 @@ checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a"
dependencies = [ dependencies = [
"CoreFoundation-sys", "CoreFoundation-sys",
"libc", "libc",
"mach", "mach 0.1.2",
] ]
[[package]] [[package]]
@ -125,9 +125,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.23" version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
dependencies = [ dependencies = [
"iana-time-zone", "iana-time-zone",
"js-sys", "js-sys",
@ -281,7 +281,7 @@ version = "3.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639"
dependencies = [ dependencies = [
"nix", "nix 0.26.2",
"windows-sys", "windows-sys",
] ]
@ -373,17 +373,17 @@ dependencies = [
[[package]] [[package]]
name = "exr" name = "exr"
version = "1.5.3" version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8af5ef47e2ed89d23d0ecbc1b681b30390069de70260937877514377fc24feb" checksum = "bdd2162b720141a91a054640662d3edce3d50a944a50ffca5313cd951abb35b4"
dependencies = [ dependencies = [
"bit_field", "bit_field",
"flume", "flume",
"half", "half",
"lebe", "lebe",
"miniz_oxide", "miniz_oxide",
"rayon-core",
"smallvec", "smallvec",
"threadpool",
"zune-inflate", "zune-inflate",
] ]
@ -412,15 +412,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.26" version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd"
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.26" version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2"
[[package]] [[package]]
name = "getrandom" name = "getrandom"
@ -718,10 +718,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "mach2" name = "mach"
version = "0.4.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -765,6 +765,17 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "nix"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags",
"cfg-if",
"libc",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.26.2" version = "0.26.2"
@ -915,18 +926,18 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.51" version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.23" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -1017,6 +1028,7 @@ dependencies = [
"md5", "md5",
"panic-message", "panic-message",
"rust-ini", "rust-ini",
"serial2",
"serialport", "serialport",
] ]
@ -1038,27 +1050,39 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
[[package]]
name = "serial2"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa2370e12745a20d3dcd07438fc72f399c74f59537a35863a1a7a9ff2482040"
dependencies = [
"cfg-if",
"libc",
"winapi",
]
[[package]] [[package]]
name = "serialport" name = "serialport"
version = "4.2.1-alpha.0" version = "4.2.0"
source = "git+https://github.com/serialport/serialport-rs?branch=main#e1f46eef5af7df2430f0a595681243e46f721b2a" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aab92efb5cf60ad310548bc3f16fa6b0d950019cb7ed8ff41968c3d03721cf12"
dependencies = [ dependencies = [
"CoreFoundation-sys", "CoreFoundation-sys",
"IOKit-sys", "IOKit-sys",
"bitflags", "bitflags",
"cfg-if", "cfg-if",
"libudev", "libudev",
"mach2", "mach 0.3.2",
"nix", "nix 0.24.3",
"regex", "regex",
"winapi", "winapi",
] ]
[[package]] [[package]]
name = "simd-adler32" name = "simd-adler32"
version = "0.3.4" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14a5df39617d7c8558154693a1bb8157a4aab8179209540cc0b10e5dc24e0b18" checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
@ -1068,9 +1092,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]] [[package]]
name = "spin" name = "spin"
version = "0.9.5" version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dccf47db1b41fa1573ed27ccf5e08e3ca771cb994f776668c5ebda893b248fc" checksum = "b5d6e0250b93c8427a177b849d144a96d5acc57006149479403d7861ab721e34"
dependencies = [ dependencies = [
"lock_api", "lock_api",
] ]
@ -1107,15 +1131,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "threadpool"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
"num_cpus",
]
[[package]] [[package]]
name = "tiff" name = "tiff"
version = "0.8.1" version = "0.8.1"
@ -1270,9 +1285,9 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.42.1" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm",
"windows_aarch64_msvc", "windows_aarch64_msvc",
@ -1285,45 +1300,45 @@ dependencies = [
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.42.1" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.42.1" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.42.1" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.42.1" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.42.1" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.42.1" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.42.1" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]] [[package]]
name = "zune-inflate" name = "zune-inflate"

View File

@ -20,7 +20,8 @@ include-flate = { version = "0.2.0", features = ["stable"] }
md5 = "0.7.0" md5 = "0.7.0"
panic-message = "0.3.0" panic-message = "0.3.0"
rust-ini = "0.18.0" rust-ini = "0.18.0"
serialport = { git = "https://github.com/serialport/serialport-rs", branch = "main" } serial2 = "0.1.7"
serialport = "4.2.0"
[profile.release] [profile.release]
lto = true lto = true

View File

@ -1,22 +1,24 @@
use crate::sc64; use crate::sc64;
use chrono::Local; use chrono::Local;
use colored::Colorize; use colored::Colorize;
use panic_message::panic_message; use encoding_rs::EUC_JP;
use std::{ use std::{
fs::File, fs::File,
io::{stdin, ErrorKind, Read, Write}, io::{stdin, Read, Write},
net::{TcpListener, TcpStream}, path::PathBuf,
panic,
sync::mpsc::{channel, Receiver, Sender}, sync::mpsc::{channel, Receiver, Sender},
thread::{sleep, spawn}, thread::spawn,
time::Duration,
}; };
pub enum Encoding {
UTF8,
EUCJP,
}
pub struct Handler { pub struct Handler {
header: Option<Vec<u8>>, header: Option<Vec<u8>>,
line_rx: Receiver<String>, line_rx: Receiver<String>,
gdb_tx: Sender<Vec<u8>>, encoding: Encoding,
gdb_rx: Receiver<Vec<u8>>,
} }
enum DataType { enum DataType {
@ -24,7 +26,6 @@ enum DataType {
RawBinary, RawBinary,
Header, Header,
Screenshot, Screenshot,
GDB,
Unknown, Unknown,
} }
@ -35,7 +36,6 @@ impl From<u8> for DataType {
0x02 => Self::RawBinary, 0x02 => Self::RawBinary,
0x03 => Self::Header, 0x03 => Self::Header,
0x04 => Self::Screenshot, 0x04 => Self::Screenshot,
0xDB => Self::GDB,
_ => Self::Unknown, _ => Self::Unknown,
} }
} }
@ -48,7 +48,6 @@ impl From<DataType> for u8 {
DataType::RawBinary => 0x02, DataType::RawBinary => 0x02,
DataType::Header => 0x03, DataType::Header => 0x03,
DataType::Screenshot => 0x04, DataType::Screenshot => 0x04,
DataType::GDB => 0xDB,
DataType::Unknown => 0xFF, DataType::Unknown => 0xFF,
} }
} }
@ -115,68 +114,103 @@ impl TryFrom<Vec<u8>> for ScreenshotMetadata {
} }
} }
macro_rules! success {
($($a: tt)*) => {
println!("{}", format!($($a)*).bright_blue());
};
}
macro_rules! error {
($($a: tt)*) => {{
println!("{}", format!("Error: {}", format!($($a)*)).bright_red());
}};
}
macro_rules! stop {
($r: expr, $($a: tt)*) => {{
error!($($a)*);
$r
}};
}
const MAX_PACKET_LENGTH: usize = 8 * 1024 * 1024;
impl Handler { impl Handler {
pub fn set_text_encoding(&mut self, encoding: Encoding) {
self.encoding = encoding;
}
pub fn process_user_input(&self) -> Option<sc64::DebugPacket> { pub fn process_user_input(&self) -> Option<sc64::DebugPacket> {
if let Ok(line) = self.line_rx.try_recv() { let line = match self.line_rx.try_recv() {
Ok(line) => {
if line.len() == 0 { if line.len() == 0 {
return None; return None;
}
let mut data: Vec<u8> = Vec::new();
if line.matches("@").count() != 2 {
data.append(&mut line.as_bytes().to_vec());
data.append(&mut [b'\0'].to_vec());
return Some(sc64::DebugPacket {
datatype: DataType::Text.into(),
data,
});
} else { } else {
let start = line.find("@").unwrap(); line
let end = line.rfind("@").unwrap();
let path = &line[start + 1..end];
if path.len() == 0 {
println!("Invalid path provided");
return None;
} }
let mut file = match File::open(path) {
Ok(file) => file,
Err(error) => {
println!("Couldn't open file: {error}");
return None;
} }
Err(_) => return None,
}; };
let length = match file.metadata() {
Ok(metadata) => metadata.len(), let token_count = line.matches("@").count();
Err(error) => {
println!("Couldn't get file length: {error}"); if (token_count % 2) != 0 {
return None; return stop!(None, "Missing closing '@' token");
} }
};
let mut data = vec![0u8; length as usize]; let packet = if token_count == 2 && line.starts_with("@") && line.ends_with("@") {
if let Err(error) = file.read_exact(&mut data) { sc64::DebugPacket {
println!("Couldn't read file contents: {error}");
return None;
}
if line.starts_with("@") && line.ends_with("@") {
return Some(sc64::DebugPacket {
datatype: DataType::RawBinary.into(), datatype: DataType::RawBinary.into(),
data, data: match load_file(line.trim_matches('@')) {
}); Ok(data) => data,
Err(error) => return stop!(None, "{error}"),
},
}
} else { } else {
let mut combined_data: Vec<u8> = Vec::new(); let mut is_text = true;
combined_data.append(&mut line[0..start].as_bytes().to_vec()); let mut path = String::new();
combined_data.append(&mut [b'@'].to_vec()); let mut character_buffer = vec![0u8; 4];
combined_data.append(&mut format!("{length}").into_bytes()); let mut data = vec![0u8; 0];
combined_data.append(&mut [b'@'].to_vec()); for character in line.chars() {
combined_data.append(&mut data); if character == '@' {
combined_data.append(&mut [b'\0'].to_vec()); if is_text {
return Some(sc64::DebugPacket { is_text = false;
} else {
let mut file = match load_file(&path) {
Ok(data) => data,
Err(error) => return stop!(None, "{error}"),
};
let length = file.len();
data.append(&mut format!("@{length}@").into_bytes());
data.append(&mut file);
is_text = true;
path = String::new();
}
} else {
if is_text {
let encoded = character.encode_utf8(&mut character_buffer);
data.append(&mut encoded.as_bytes().to_vec());
} else {
path.push(character);
}
}
}
sc64::DebugPacket {
datatype: DataType::Text.into(), datatype: DataType::Text.into(),
data: combined_data, data,
});
} }
};
if packet.data.len() > MAX_PACKET_LENGTH {
return stop!(
None,
"Debug packet size too big ({}), exceeding maximum size of {}",
packet.data.len(),
MAX_PACKET_LENGTH
);
} }
}
None Some(packet)
} }
pub fn handle_debug_packet(&mut self, debug_packet: sc64::DebugPacket) { pub fn handle_debug_packet(&mut self, debug_packet: sc64::DebugPacket) {
@ -186,29 +220,59 @@ impl Handler {
DataType::RawBinary => self.handle_datatype_raw_binary(&data), DataType::RawBinary => self.handle_datatype_raw_binary(&data),
DataType::Header => self.handle_datatype_header(&data), DataType::Header => self.handle_datatype_header(&data),
DataType::Screenshot => self.handle_datatype_screenshot(&data), DataType::Screenshot => self.handle_datatype_screenshot(&data),
DataType::GDB => self.handle_datatype_gdb(&data), _ => error!("Received unknown debug packet datatype: 0x{datatype:02X}"),
_ => {
println!("Unknown debug packet datatype: 0x{datatype:02X}");
} }
} }
pub fn handle_is_viewer_64(&self, data: &[u8]) {
self.print_text(data);
}
pub fn handle_save_writeback(
&self,
save_writeback: sc64::SaveWriteback,
path: &Option<PathBuf>,
) {
let filename = &if let Some(path) = path {
path.to_string_lossy().to_string()
} else {
generate_filename(
"save",
match save_writeback.save {
sc64::SaveType::Eeprom4k | sc64::SaveType::Eeprom16k => "eep",
sc64::SaveType::Sram | sc64::SaveType::SramBanked | sc64::SaveType::Sram1m => {
"srm"
}
sc64::SaveType::Flashram => "fla",
_ => "sav",
},
)
};
match File::create(filename) {
Ok(mut file) => {
if let Err(error) = file.write_all(&save_writeback.data) {
error!("Couldn't write save [{filename}]: {error}");
}
success!("Wrote [{}] save to [{filename}]", save_writeback.save);
}
Err(error) => error!("Couldn't create save writeback file [{filename}]: {error}"),
}
} }
fn handle_datatype_text(&self, data: &[u8]) { fn handle_datatype_text(&self, data: &[u8]) {
print!("{}", String::from_utf8_lossy(data)); self.print_text(data);
} }
fn handle_datatype_raw_binary(&self, data: &[u8]) { fn handle_datatype_raw_binary(&self, data: &[u8]) {
let filename = &self.generate_filename("binaryout", "bin"); let filename = &generate_filename("binaryout", "bin");
match File::create(filename) { match File::create(filename) {
Ok(mut file) => { Ok(mut file) => {
if let Err(error) = file.write_all(data) { if let Err(error) = file.write_all(data) {
println!("Error during raw binary save: {error}"); error!("Couldn't write raw binary [{filename}]: {error}");
} }
println!("Wrote {} bytes to [{}]", data.len(), filename); success!("Wrote [{}] bytes to [{filename}]", data.len());
}
Err(error) => {
println!("Error during raw binary file creation: {error}");
} }
Err(error) => error!("Couldn't create raw binary file [{filename}]: {error}"),
} }
} }
@ -219,10 +283,7 @@ impl Handler {
fn handle_datatype_screenshot(&mut self, data: &[u8]) { fn handle_datatype_screenshot(&mut self, data: &[u8]) {
let header = match self.header.take() { let header = match self.header.take() {
Some(header) => header, Some(header) => header,
None => { None => return error!("Got screenshot packet without header data"),
println!("Got screenshot packet without header data");
return;
}
}; };
let ScreenshotMetadata { let ScreenshotMetadata {
format, format,
@ -230,15 +291,11 @@ impl Handler {
width, width,
} = match header.try_into() { } = match header.try_into() {
Ok(data) => data, Ok(data) => data,
Err(error) => { Err(error) => return error!("{error}"),
println!("{error}");
return;
}
}; };
let format_size: u32 = format.into(); let format_size: u32 = format.into();
if data.len() as u32 != format_size * width * height { if data.len() as u32 != format_size * width * height {
println!("Data length did not match header data for screenshot datatype"); return error!("Data length did not match header data for screenshot datatype");
return;
} }
let mut image = image::RgbaImage::new(width, height); let mut image = image::RgbaImage::new(width, height);
for (x, y, pixel) in image.enumerate_pixels_mut() { for (x, y, pixel) in image.enumerate_pixels_mut() {
@ -255,142 +312,67 @@ impl Handler {
ScreenshotPixelFormat::Rgba32 => [p[0], p[1], p[2], p[3]], ScreenshotPixelFormat::Rgba32 => [p[0], p[1], p[2], p[3]],
} }
} }
let filename = &self.generate_filename("screenshot", "png"); let filename = &generate_filename("screenshot", "png");
if let Some(error) = image.save(filename).err() { if let Some(error) = image.save(filename).err() {
println!("Error during image save: {error}"); return error!("Couldn't save screenshot [{filename}]: {error}");
return;
} }
println!("Wrote {width}x{height} pixels to [{filename}]"); success!("Wrote {width}x{height} pixels to [{filename}]");
} }
fn handle_datatype_gdb(&self, data: &[u8]) { fn print_text(&self, data: &[u8]) {
self.gdb_tx.send(data.to_vec()).ok(); match self.encoding {
} Encoding::UTF8 => print!("{}", String::from_utf8_lossy(&data)),
Encoding::EUCJP => print!("{}", EUC_JP.decode(&data).0),
fn generate_filename(&self, prefix: &str, extension: &str) -> String {
format!(
"{prefix}-{}.{extension}",
Local::now().format("%y%m%d%H%M%S.%f")
)
}
pub fn receive_gdb_packet(&self) -> Option<sc64::DebugPacket> {
if let Some(data) = self.gdb_rx.try_recv().ok() {
Some(sc64::DebugPacket {
datatype: DataType::GDB.into(),
data,
})
} else {
None
} }
} }
} }
pub fn new(gdb_port: Option<u16>) -> Result<Handler, sc64::Error> { pub fn new() -> Handler {
let (line_tx, line_rx) = channel::<String>(); let (line_tx, line_rx) = channel::<String>();
let (gdb_tx, gdb_loop_rx) = channel::<Vec<u8>>();
let (gdb_loop_tx, gdb_rx) = channel::<Vec<u8>>();
spawn(move || stdin_thread(line_tx)); spawn(move || stdin_thread(line_tx));
Handler {
if let Some(port) = gdb_port {
let listener = TcpListener::bind(format!("0.0.0.0:{port}"))
.map_err(|_| sc64::Error::new("Couldn't open GDB TCP socket port"))?;
listener.set_nonblocking(true).map_err(|_| {
sc64::Error::new("Couldn't set GDB TCP socket listener as non-blocking")
})?;
spawn(move || gdb_thread(listener, gdb_loop_tx, gdb_loop_rx));
}
Ok(Handler {
header: None, header: None,
line_rx, line_rx,
gdb_tx, encoding: Encoding::UTF8,
gdb_rx, }
}) }
fn load_file(path: &str) -> Result<Vec<u8>, String> {
if path.len() == 0 {
return Err(format!("Couldn't open file: Specified path is empty"));
}
let mut file = match File::open(path) {
Ok(file) => file,
Err(error) => return Err(format!("Couldn't open file [{path}]: {error}")),
};
let length = match file.metadata() {
Ok(metadata) => metadata.len(),
Err(error) => return Err(format!("Couldn't get file [{path}] length: {error}")),
};
if length > MAX_PACKET_LENGTH as u64 {
return Err(format!("File [{path}] size too big"));
}
let mut data = vec![0u8; length as usize];
match file.read_exact(&mut data) {
Ok(()) => Ok(data),
Err(error) => Err(format!("Couldn't read file [{path}] contents: {error}")),
}
}
fn generate_filename(prefix: &str, extension: &str) -> String {
format!(
"{prefix}-{}.{extension}",
Local::now().format("%y%m%d%H%M%S.%f")
)
} }
fn stdin_thread(line_tx: Sender<String>) { fn stdin_thread(line_tx: Sender<String>) {
loop { loop {
let mut line = String::new(); let mut line = String::new();
if stdin().read_line(&mut line).is_ok() { if stdin().read_line(&mut line).is_ok() {
if line_tx.send(line).is_err() { if line_tx.send(line.trim_end().to_string()).is_err() {
return; return;
} }
} }
} }
} }
fn gdb_thread(listener: TcpListener, gdb_tx: Sender<Vec<u8>>, gdb_rx: Receiver<Vec<u8>>) {
match panic::catch_unwind(|| gdb_loop(listener, gdb_tx, gdb_rx)) {
Ok(_) => {}
Err(payload) => {
eprintln!("{}", panic_message(&payload).red());
}
};
}
fn gdb_loop(listener: TcpListener, gdb_tx: Sender<Vec<u8>>, gdb_rx: Receiver<Vec<u8>>) {
for tcp_stream in listener.incoming() {
match tcp_stream {
Ok(mut stream) => {
handle_gdb_connection(&mut stream, &gdb_tx, &gdb_rx);
}
Err(error) => {
if error.kind() == ErrorKind::WouldBlock {
sleep(Duration::from_millis(1));
} else {
panic!("{error}");
}
}
}
}
}
fn handle_gdb_connection(
stream: &mut TcpStream,
gdb_tx: &Sender<Vec<u8>>,
gdb_rx: &Receiver<Vec<u8>>,
) {
const GDB_DATA_BUFFER: usize = 64 * 1024;
let mut buffer = vec![0u8; GDB_DATA_BUFFER];
let peer = stream.peer_addr().unwrap();
println!("[GDB]: New connection ({peer})");
loop {
match stream.read(&mut buffer) {
Ok(length) => {
if length > 0 {
gdb_tx.send(buffer[0..length].to_vec()).ok();
continue;
} else {
println!("[GDB]: Connection closed ({peer})");
break;
}
}
Err(e) => {
if e.kind() != ErrorKind::WouldBlock {
println!("[GDB]: Connection closed ({peer}), read IO error: {e}");
break;
}
}
}
if let Ok(data) = gdb_rx.try_recv() {
match stream.write_all(&data) {
Ok(()) => {
continue;
}
Err(e) => {
println!("[GDB]: Connection closed ({peer}), write IO error: {e}");
break;
}
}
}
sleep(Duration::from_millis(1));
}
}

View File

@ -16,8 +16,7 @@ use std::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Arc,
}, },
time::Duration, {panic, process},
{panic, process, thread},
}; };
#[derive(Parser)] #[derive(Parser)]
@ -147,13 +146,17 @@ struct _64DDArgs {
#[derive(Args)] #[derive(Args)]
struct DebugArgs { struct DebugArgs {
/// Path to the save file to use by the save writeback mechanism
#[arg(short, long)]
save: Option<PathBuf>,
/// Enable IS-Viewer64 and set listening address at ROM offset (in most cases it's fixed at 0x03FF0000) /// Enable IS-Viewer64 and set listening address at ROM offset (in most cases it's fixed at 0x03FF0000)
#[arg(long, value_name = "offset", value_parser = |s: &str| maybe_hex_range::<u32>(s, 0x00000004, 0x03FF0000))] #[arg(long, value_name = "offset", value_parser = |s: &str| maybe_hex_range::<u32>(s, 0x00000004, 0x03FF0000))]
isv: Option<u32>, isv: Option<u32>,
/// Expose TCP socket port for GDB debugging /// Use EUC-JP encoding for text printing
#[arg(long, value_name = "port", value_parser = clap::value_parser!(u16).range(1..))] #[arg(long)]
gdb: Option<u16>, euc_jp: bool,
} }
#[derive(Args)] #[derive(Args)]
@ -214,6 +217,7 @@ enum SaveType {
Eeprom16k, Eeprom16k,
Sram, Sram,
SramBanked, SramBanked,
Sram1m,
Flashram, Flashram,
} }
@ -225,8 +229,8 @@ impl From<n64::SaveType> for SaveType {
n64::SaveType::Eeprom16k => Self::Eeprom16k, n64::SaveType::Eeprom16k => Self::Eeprom16k,
n64::SaveType::Sram => Self::Sram, n64::SaveType::Sram => Self::Sram,
n64::SaveType::SramBanked => Self::SramBanked, n64::SaveType::SramBanked => Self::SramBanked,
n64::SaveType::Sram1m => Self::Sram1m,
n64::SaveType::Flashram => Self::Flashram, n64::SaveType::Flashram => Self::Flashram,
n64::SaveType::Sram128kB => Self::Sram,
} }
} }
} }
@ -239,6 +243,7 @@ impl From<SaveType> for sc64::SaveType {
SaveType::Eeprom16k => Self::Eeprom16k, SaveType::Eeprom16k => Self::Eeprom16k,
SaveType::Sram => Self::Sram, SaveType::Sram => Self::Sram,
SaveType::SramBanked => Self::SramBanked, SaveType::SramBanked => Self::SramBanked,
SaveType::Sram1m => Self::Sram1m,
SaveType::Flashram => Self::Flashram, SaveType::Flashram => Self::Flashram,
} }
} }
@ -483,46 +488,51 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
disk::Format::Development => sc64::DdDriveType::Development, disk::Format::Development => sc64::DdDriveType::Development,
}; };
sc64.configure_64dd(sc64::DdMode::Full, drive_type)?; let dd_mode = sc64::DdMode::Full;
println!("64DD mode set to [{dd_mode} / {drive_type}]");
sc64.configure_64dd(dd_mode, drive_type)?;
println!( println!(
"{}", "{}: {}",
"Press button on the SC64 device to cycle through provided disks".bold() "[64DD]".bold(),
"Press button on the SC64 device to cycle through provided disks"
.bold()
.bright_green()
); );
let exit = setup_exit_flag(); let exit = setup_exit_flag();
while !exit.load(Ordering::Relaxed) { while !exit.load(Ordering::Relaxed) {
if let Some(data_packet) = sc64.receive_data_packet()? { if let Some(data_packet) = sc64.receive_data_packet()? {
match data_packet { match data_packet {
sc64::DataPacket::Disk(mut packet) => { sc64::DataPacket::DiskRequest(mut disk_packet) => {
let track = packet.info.track; let track = disk_packet.info.track;
let head = packet.info.head; let head = disk_packet.info.head;
let block = packet.info.block; let block = disk_packet.info.block;
if let Some(ref mut disk) = selected_disk { if let Some(ref mut disk) = selected_disk {
let reply_packet = match packet.kind { let (reply_packet, rw) = match disk_packet.kind {
sc64::DiskPacketKind::Read => { sc64::DiskPacketKind::Read => (
print!("{}", "[R]".cyan());
disk.read_block(track, head, block)?.map(|data| { disk.read_block(track, head, block)?.map(|data| {
packet.info.set_data(&data); disk_packet.info.set_data(&data);
packet disk_packet
}) }),
} "[R]".bright_blue(),
sc64::DiskPacketKind::Write => { ),
print!("{}", "[W]".yellow()); sc64::DiskPacketKind::Write => (
let data = &packet.info.data; disk.write_block(track, head, block, &disk_packet.info.data)?
disk.write_block(track, head, block, data)?.map(|_| packet) .map(|_| disk_packet),
} "[W]".bright_yellow(),
),
}; };
let lba = if let Some(lba) = disk.get_lba(track, head, block) { let lba = if let Some(lba) = disk.get_lba(track, head, block) {
format!("{lba}") format!("{lba}")
} else { } else {
"Invalid".to_string() "Invalid".to_string()
}; };
let message = format!(" {track:4}:{head}:{block} / LBA: {lba}"); let message = format!("{track:4}:{head}:{block} | LBA: {lba}");
if reply_packet.is_some() { if reply_packet.is_some() {
println!("{}", message.green()); println!("{}: {} {}", "[64DD]".bold(), rw, message.green());
} else { } else {
println!("{}", message.red()); println!("{}: {} {}", "[64DD]".bold(), rw, message.red());
} }
sc64.reply_disk_packet(reply_packet)?; sc64.reply_disk_packet(reply_packet)?;
} else { } else {
@ -533,7 +543,11 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
if selected_disk.is_some() { if selected_disk.is_some() {
sc64.set_64dd_disk_state(sc64::DdDiskState::Ejected)?; sc64.set_64dd_disk_state(sc64::DdDiskState::Ejected)?;
selected_disk = None; selected_disk = None;
println!("64DD disk ejected [{}]", disk_names[selected_disk_index]); println!(
"{}: Disk ejected [{}]",
"[64DD]".bold(),
disk_names[selected_disk_index].green()
);
} else { } else {
selected_disk_index += 1; selected_disk_index += 1;
if selected_disk_index >= disks.len() { if selected_disk_index >= disks.len() {
@ -541,64 +555,74 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
} }
selected_disk = Some(&mut disks[selected_disk_index]); selected_disk = Some(&mut disks[selected_disk_index]);
sc64.set_64dd_disk_state(sc64::DdDiskState::Inserted)?; sc64.set_64dd_disk_state(sc64::DdDiskState::Inserted)?;
println!("64DD disk inserted [{}]", disk_names[selected_disk_index]); println!(
"{}: Disk inserted [{}]",
"[64DD]".bold(),
disk_names[selected_disk_index].bright_green()
);
} }
} }
_ => {} _ => {}
} }
} else {
thread::sleep(Duration::from_millis(1));
} }
} }
sc64.reset_state()?;
Ok(()) Ok(())
} }
fn handle_debug_command(connection: Connection, args: &DebugArgs) -> Result<(), sc64::Error> { fn handle_debug_command(connection: Connection, args: &DebugArgs) -> Result<(), sc64::Error> {
let mut sc64 = init_sc64(connection, true)?; let mut sc64 = init_sc64(connection, true)?;
let mut debug_handler = debug::new(args.gdb)?; let mut debug_handler = debug::new();
if let Some(port) = args.gdb {
println!("GDB TCP socket listening at [0.0.0.0:{port}]"); if args.euc_jp {
debug_handler.set_text_encoding(debug::Encoding::EUCJP);
} }
if args.isv.is_some() { if args.isv.is_some() {
sc64.configure_is_viewer_64(args.isv)?; sc64.configure_is_viewer_64(args.isv)?;
println!( println!(
"IS-Viewer 64 configured and listening at ROM offset [0x{:08X}]", "{}: Listening on ROM offset [{}]",
args.isv.unwrap() "[IS-Viewer 64]".bold(),
format!("0x{:08X}", args.isv.unwrap())
.to_string()
.bright_blue()
); );
} }
sc64.set_save_writeback(true)?;
println!("{}", "Debug mode started".bold()); println!("{}: Started", "[Debug]".bold());
let exit = setup_exit_flag(); let exit = setup_exit_flag();
while !exit.load(Ordering::Relaxed) { while !exit.load(Ordering::Relaxed) {
if let Some(data_packet) = sc64.receive_data_packet()? { if let Some(data_packet) = sc64.receive_data_packet()? {
match data_packet { match data_packet {
sc64::DataPacket::IsViewer(message) => { sc64::DataPacket::DebugData(debug_packet) => {
print!("{message}") debug_handler.handle_debug_packet(debug_packet);
} }
sc64::DataPacket::Debug(debug_packet) => { sc64::DataPacket::IsViewer64(message) => {
debug_handler.handle_debug_packet(debug_packet) debug_handler.handle_is_viewer_64(&message);
}
sc64::DataPacket::SaveWriteback(save_writeback) => {
debug_handler.handle_save_writeback(save_writeback, &args.save);
} }
_ => {} _ => {}
} }
} else if let Some(gdb_packet) = debug_handler.receive_gdb_packet() {
sc64.send_debug_packet(gdb_packet)?;
} else if let Some(debug_packet) = debug_handler.process_user_input() { } else if let Some(debug_packet) = debug_handler.process_user_input() {
sc64.send_debug_packet(debug_packet)?; sc64.send_debug_packet(debug_packet)?;
} else {
thread::sleep(Duration::from_millis(1));
} }
} }
println!("{}", "Debug mode ended".bold()); sc64.set_save_writeback(false)?;
if args.isv.is_some() { if args.isv.is_some() {
sc64.configure_is_viewer_64(None)?; sc64.configure_is_viewer_64(None)?;
println!("{}: Stopped listening", "[IS-Viewer 64]".bold());
} }
println!("{}: Stopped", "[Debug]".bold());
Ok(()) Ok(())
} }
@ -636,7 +660,6 @@ fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
println!(" ROM write: {}", state.rom_write_enable); println!(" ROM write: {}", state.rom_write_enable);
println!(" ROM shadow: {}", state.rom_shadow_enable); println!(" ROM shadow: {}", state.rom_shadow_enable);
println!(" ROM extended: {}", state.rom_extended_enable); println!(" ROM extended: {}", state.rom_extended_enable);
println!(" IS-Viewer 64 offset: 0x{:08X}", state.isv_address);
println!(" 64DD mode: {}", state.dd_mode); println!(" 64DD mode: {}", state.dd_mode);
println!(" 64DD SD card mode: {}", state.dd_sd_enable); println!(" 64DD SD card mode: {}", state.dd_sd_enable);
println!(" 64DD drive type: {}", state.dd_drive_type); println!(" 64DD drive type: {}", state.dd_drive_type);
@ -644,6 +667,7 @@ fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
println!(" Button mode: {}", state.button_mode); println!(" Button mode: {}", state.button_mode);
println!(" Button state: {}", state.button_state); println!(" Button state: {}", state.button_state);
println!(" LED blink: {}", state.led_enable); println!(" LED blink: {}", state.led_enable);
println!(" IS-Viewer 64 offset: 0x{:08X}", state.isv_address);
println!(" FPGA debug data: {}", state.fpga_debug_data); println!(" FPGA debug data: {}", state.fpga_debug_data);
println!(" MCU stack usage: {}", state.mcu_stack_usage); println!(" MCU stack usage: {}", state.mcu_stack_usage);
@ -693,7 +717,7 @@ fn handle_firmware_command(
let metadata = sc64::firmware::verify(&firmware)?; let metadata = sc64::firmware::verify(&firmware)?;
println!("{}", "Firmware metadata:".bold()); println!("{}", "Firmware metadata:".bold());
println!("{}", format!("{}", metadata).cyan().to_string()); println!("{}", format!("{}", metadata).bright_blue().to_string());
Ok(()) Ok(())
} }
@ -703,8 +727,6 @@ fn handle_firmware_command(
let (mut backup_file, backup_name) = create_file(&args.firmware)?; let (mut backup_file, backup_name) = create_file(&args.firmware)?;
sc64.reset_state()?;
let firmware = log_wait( let firmware = log_wait(
format!("Generating firmware backup, this might take a while [{backup_name}]"), format!("Generating firmware backup, this might take a while [{backup_name}]"),
|| sc64.backup_firmware(), || sc64.backup_firmware(),
@ -712,7 +734,7 @@ fn handle_firmware_command(
let metadata = sc64::firmware::verify(&firmware)?; let metadata = sc64::firmware::verify(&firmware)?;
println!("{}", "Firmware metadata:".bold()); println!("{}", "Firmware metadata:".bold());
println!("{}", format!("{}", metadata).cyan().to_string()); println!("{}", format!("{}", metadata).bright_blue().to_string());
backup_file.write_all(&firmware)?; backup_file.write_all(&firmware)?;
@ -729,7 +751,7 @@ fn handle_firmware_command(
let metadata = sc64::firmware::verify(&firmware)?; let metadata = sc64::firmware::verify(&firmware)?;
println!("{}", "Firmware metadata:".bold()); println!("{}", "Firmware metadata:".bold());
println!("{}", format!("{}", metadata).cyan().to_string()); println!("{}", format!("{}", metadata).bright_blue().to_string());
println!("{}", "Firmware file verification was successful".green()); println!("{}", "Firmware file verification was successful".green());
let answer = prompt(format!("{}", "Continue with update process? [y/N] ".bold())); let answer = prompt(format!("{}", "Continue with update process? [y/N] ".bold()));
if answer.to_ascii_lowercase() != "y" { if answer.to_ascii_lowercase() != "y" {
@ -741,8 +763,6 @@ fn handle_firmware_command(
"Do not unplug SC64 from the computer, doing so might brick your device".yellow() "Do not unplug SC64 from the computer, doing so might brick your device".yellow()
); );
sc64.reset_state()?;
log_wait( log_wait(
format!("Updating firmware, this might take a while [{update_name}]"), format!("Updating firmware, this might take a while [{update_name}]"),
|| sc64.update_firmware(&firmware), || sc64.update_firmware(&firmware),
@ -762,19 +782,31 @@ fn handle_server_command(connection: Connection, args: &ServerArgs) -> Result<()
sc64::run_server(port, args.address.clone(), |event| match event { sc64::run_server(port, args.address.clone(), |event| match event {
sc64::ServerEvent::Listening(address) => { sc64::ServerEvent::Listening(address) => {
println!("{}: Listening on address [{}]", "[Server]".bold(), address) println!(
"{}: Listening on address [{}]",
"[Server]".bold(),
address.bright_blue()
)
} }
sc64::ServerEvent::Connection(peer) => { sc64::ServerEvent::Connected(peer) => {
println!("{}: New connection from [{}]", "[Server]".bold(), peer); println!(
"{}: New connection from [{}]",
"[Server]".bold(),
peer.bright_green()
);
} }
sc64::ServerEvent::Disconnected(peer) => { sc64::ServerEvent::Disconnected(peer) => {
println!("{}: Client disconnected [{}]", "[Server]".bold(), peer); println!(
"{}: Client disconnected [{}]",
"[Server]".bold(),
peer.green()
);
} }
sc64::ServerEvent::Err(error) => { sc64::ServerEvent::Err(error) => {
println!( println!(
"{}: Client disconnected with error: {}", "{}: Client disconnected - server error: {}",
"[Server]".bold(), "[Server]".bold(),
error error.red()
); );
} }
})?; })?;
@ -800,9 +832,9 @@ fn log_wait<F: FnOnce() -> Result<T, E>, T, E>(message: String, operation: F) ->
stdout().flush().unwrap(); stdout().flush().unwrap();
let result = operation(); let result = operation();
if result.is_ok() { if result.is_ok() {
println!("done"); println!("{}", "done".bold().bright_green());
} else { } else {
println!("error!"); println!("{}", "error!".bold().bright_red());
} }
result result
} }

View File

@ -10,7 +10,7 @@ pub enum SaveType {
Sram, Sram,
SramBanked, SramBanked,
Flashram, Flashram,
Sram128kB, Sram1m,
} }
const HASH_CHUNK_LENGTH: usize = 1 * 1024 * 1024; const HASH_CHUNK_LENGTH: usize = 1 * 1024 * 1024;
@ -31,7 +31,7 @@ pub fn guess_save_type<T: Read + Seek>(
3 => SaveType::Sram, 3 => SaveType::Sram,
4 => SaveType::SramBanked, 4 => SaveType::SramBanked,
5 => SaveType::Flashram, 5 => SaveType::Flashram,
6 => SaveType::Sram128kB, 6 => SaveType::Sram1m,
_ => SaveType::None, _ => SaveType::None,
}, },
None, None,

View File

@ -17,7 +17,7 @@ impl std::error::Error for Error {}
impl Display for Error { impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result { fn fmt(&self, f: &mut Formatter) -> Result {
write!(f, "SC64 error: {}", self.description.as_str()) write!(f, "{}", self.description.as_str())
} }
} }

View File

@ -1,12 +1,14 @@
use super::error::Error; use super::error::Error;
use serial2::SerialPort;
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
io::{BufRead, BufReader, BufWriter, ErrorKind, Read, Write}, io::{BufReader, BufWriter, ErrorKind, Read, Write},
net::{TcpListener, TcpStream}, net::TcpStream,
thread,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
enum DataType { pub enum DataType {
Command, Command,
Response, Response,
Packet, Packet,
@ -37,10 +39,10 @@ impl TryFrom<u32> for DataType {
} }
} }
pub struct Command<'a> { pub struct Command {
pub id: u8, pub id: u8,
pub args: [u32; 2], pub args: [u32; 2],
pub data: &'a [u8], pub data: Vec<u8>,
} }
pub struct Response { pub struct Response {
@ -54,29 +56,20 @@ pub struct Packet {
pub data: Vec<u8>, pub data: Vec<u8>,
} }
trait Backend { pub struct Serial {
fn send_command(&mut self, command: &Command) -> Result<(), Error>; serial: SerialPort,
fn process_incoming_data(
&mut self,
data_type: DataType,
packets: &mut VecDeque<Packet>,
) -> Result<Option<Response>, Error>;
} }
struct SerialBackend { impl Serial {
serial: Box<dyn serialport::SerialPort>, fn reset(&self) -> Result<(), Error> {
}
impl SerialBackend {
fn reset(&mut self) -> Result<(), Error> {
const WAIT_DURATION: Duration = Duration::from_millis(10); const WAIT_DURATION: Duration = Duration::from_millis(10);
const RETRY_COUNT: i32 = 100; const RETRY_COUNT: i32 = 100;
self.serial.write_data_terminal_ready(true)?; self.serial.set_dtr(true)?;
for n in 0..=RETRY_COUNT { for n in 0..=RETRY_COUNT {
self.serial.clear(serialport::ClearBuffer::All)?; self.serial.discard_buffers()?;
std::thread::sleep(WAIT_DURATION); thread::sleep(WAIT_DURATION);
if self.serial.read_data_set_ready()? { if self.serial.read_dsr()? {
break; break;
} }
if n == RETRY_COUNT { if n == RETRY_COUNT {
@ -84,10 +77,10 @@ impl SerialBackend {
} }
} }
self.serial.write_data_terminal_ready(false)?; self.serial.set_dtr(false)?;
for n in 0..=RETRY_COUNT { for n in 0..=RETRY_COUNT {
std::thread::sleep(WAIT_DURATION); thread::sleep(WAIT_DURATION);
if !self.serial.read_data_set_ready()? { if !self.serial.read_dsr()? {
break; break;
} }
if n == RETRY_COUNT { if n == RETRY_COUNT {
@ -97,10 +90,44 @@ impl SerialBackend {
Ok(()) Ok(())
} }
}
impl Backend for SerialBackend { fn read_data(&self, buffer: &mut [u8], block: bool) -> Result<Option<()>, Error> {
fn send_command(&mut self, command: &Command) -> Result<(), Error> { let timeout = Instant::now();
let mut position = 0;
let length = buffer.len();
while position < length {
if timeout.elapsed() > Duration::from_secs(10) {
return Err(Error::new("Serial read timeout"));
}
match self.serial.read(&mut buffer[position..length]) {
Ok(0) => return Err(Error::new("Unexpected end of serial data")),
Ok(bytes) => position += bytes,
Err(error) => match error.kind() {
ErrorKind::Interrupted | ErrorKind::TimedOut | ErrorKind::WouldBlock => {
if !block && position == 0 {
return Ok(None);
}
}
_ => return Err(error.into()),
},
}
}
Ok(Some(()))
}
fn read_exact(&self, buffer: &mut [u8]) -> Result<(), Error> {
match self.read_data(buffer, true)? {
Some(()) => Ok(()),
None => Err(Error::new("Unexpected end of serial data")),
}
}
fn read_header(&self, block: bool) -> Result<Option<[u8; 4]>, Error> {
let mut header = [0u8; 4];
Ok(self.read_data(&mut header, block)?.map(|_| header))
}
pub fn send_command(&self, command: &Command) -> Result<(), Error> {
self.serial.write_all(b"CMD")?; self.serial.write_all(b"CMD")?;
self.serial.write_all(&command.id.to_be_bytes())?; self.serial.write_all(&command.id.to_be_bytes())?;
self.serial.write_all(&command.args[0].to_be_bytes())?; self.serial.write_all(&command.args[0].to_be_bytes())?;
@ -113,30 +140,28 @@ impl Backend for SerialBackend {
Ok(()) Ok(())
} }
fn process_incoming_data( pub fn process_incoming_data(
&mut self, &self,
data_type: DataType, data_type: DataType,
packets: &mut VecDeque<Packet>, packets: &mut VecDeque<Packet>,
) -> Result<Option<Response>, Error> { ) -> Result<Option<Response>, Error> {
let mut buffer = [0u8; 4]; let block = matches!(data_type, DataType::Response);
while let Some(header) = self.read_header(block)? {
while matches!(data_type, DataType::Response) let (packet_token, error) = (match &header[0..3] {
|| self.serial.bytes_to_read()? as usize >= buffer.len()
{
self.serial.read_exact(&mut buffer)?;
let (packet_token, error) = (match &buffer[0..3] {
b"CMP" => Ok((false, false)), b"CMP" => Ok((false, false)),
b"PKT" => Ok((true, false)), b"PKT" => Ok((true, false)),
b"ERR" => Ok((false, true)), b"ERR" => Ok((false, true)),
_ => Err(Error::new("Unknown response token")), _ => Err(Error::new("Unknown response token")),
})?; })?;
let id = buffer[3]; let id = header[3];
self.serial.read_exact(&mut buffer)?; let mut buffer = [0u8; 4];
self.read_exact(&mut buffer)?;
let length = u32::from_be_bytes(buffer) as usize; let length = u32::from_be_bytes(buffer) as usize;
let mut data = vec![0u8; length]; let mut data = vec![0u8; length];
self.serial.read_exact(&mut data)?; self.read_exact(&mut data)?;
if packet_token { if packet_token {
packets.push_back(Packet { id, data }); packets.push_back(Packet { id, data });
@ -152,37 +177,89 @@ impl Backend for SerialBackend {
} }
} }
fn new_serial_backend(port: &str) -> Result<SerialBackend, Error> { pub fn new_serial(port: &str) -> Result<Serial, Error> {
let serial = serialport::new(port, 115_200) let mut serial = SerialPort::open(port, 115_200)?;
.timeout(Duration::from_secs(10)) serial.set_write_timeout(Duration::from_secs(10))?;
.open()?; serial.set_read_timeout(Duration::from_millis(10))?;
let mut backend = SerialBackend { serial }; let backend = Serial { serial };
backend.reset()?; backend.reset()?;
Ok(backend) Ok(backend)
} }
trait Backend {
fn send_command(&mut self, command: &Command) -> Result<(), Error>;
fn process_incoming_data(
&mut self,
data_type: DataType,
packets: &mut VecDeque<Packet>,
) -> Result<Option<Response>, Error>;
}
struct SerialBackend {
inner: Serial,
}
impl Backend for SerialBackend {
fn send_command(&mut self, command: &Command) -> Result<(), Error> {
self.inner.send_command(command)
}
fn process_incoming_data(
&mut self,
data_type: DataType,
packets: &mut VecDeque<Packet>,
) -> Result<Option<Response>, Error> {
self.inner.process_incoming_data(data_type, packets)
}
}
fn new_serial_backend(port: &str) -> Result<SerialBackend, Error> {
let backend = SerialBackend {
inner: new_serial(port)?,
};
Ok(backend)
}
struct TcpBackend { struct TcpBackend {
stream: TcpStream,
reader: BufReader<TcpStream>, reader: BufReader<TcpStream>,
writer: BufWriter<TcpStream>, writer: BufWriter<TcpStream>,
} }
impl TcpBackend { impl TcpBackend {
fn bytes_to_read(&mut self) -> Result<usize, Error> { fn read_data(&mut self, buffer: &mut [u8], block: bool) -> Result<Option<()>, Error> {
self.stream.set_nonblocking(true)?; let timeout = Instant::now();
let result = self.reader.fill_buf(); let mut position = 0;
let length = match result { let length = buffer.len();
Ok(buffer) => buffer.len(), while position < length {
Err(error) => { if timeout.elapsed() > Duration::from_secs(10) {
if error.kind() == ErrorKind::WouldBlock { return Err(Error::new("Stream read timeout"));
0 }
} else { match self.reader.read(&mut buffer[position..length]) {
return Err(error.into()); Ok(0) => return Err(Error::new("Unexpected end of stream data")),
Ok(bytes) => position += bytes,
Err(error) => match error.kind() {
ErrorKind::Interrupted | ErrorKind::TimedOut | ErrorKind::WouldBlock => {
if !block && position == 0 {
return Ok(None);
} }
} }
}; _ => return Err(error.into()),
self.stream.set_nonblocking(false)?; },
return Ok(length); }
}
Ok(Some(()))
}
fn read_exact(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
match self.read_data(buffer, true)? {
Some(()) => Ok(()),
None => Err(Error::new("Unexpected end of stream data")),
}
}
fn read_header(&mut self, block: bool) -> Result<Option<[u8; 4]>, Error> {
let mut header = [0u8; 4];
Ok(self.read_data(&mut header, block)?.map(|_| header))
} }
} }
@ -209,22 +286,20 @@ impl Backend for TcpBackend {
data_type: DataType, data_type: DataType,
packets: &mut VecDeque<Packet>, packets: &mut VecDeque<Packet>,
) -> Result<Option<Response>, Error> { ) -> Result<Option<Response>, Error> {
let block = matches!(data_type, DataType::Response);
while let Some(header) = self.read_header(block)? {
let payload_data_type: DataType = u32::from_be_bytes(header).try_into()?;
let mut buffer = [0u8; 4]; let mut buffer = [0u8; 4];
while matches!(data_type, DataType::Response) || self.bytes_to_read()? >= 4 {
self.reader.read_exact(&mut buffer)?;
let payload_data_type: DataType = u32::from_be_bytes(buffer).try_into()?;
match payload_data_type { match payload_data_type {
DataType::Response => { DataType::Response => {
let mut response_info = vec![0u8; 2]; let mut response_info = vec![0u8; 2];
self.reader.read_exact(&mut response_info)?; self.read_exact(&mut response_info)?;
self.reader.read_exact(&mut buffer)?; self.read_exact(&mut buffer)?;
let response_data_length = u32::from_be_bytes(buffer) as usize; let response_data_length = u32::from_be_bytes(buffer) as usize;
let mut data = vec![0u8; response_data_length]; let mut data = vec![0u8; response_data_length];
self.reader.read_exact(&mut data)?; self.read_exact(&mut data)?;
return Ok(Some(Response { return Ok(Some(Response {
id: response_info[0], id: response_info[0],
@ -234,13 +309,13 @@ impl Backend for TcpBackend {
} }
DataType::Packet => { DataType::Packet => {
let mut packet_info = vec![0u8; 1]; let mut packet_info = vec![0u8; 1];
self.reader.read_exact(&mut packet_info)?; self.read_exact(&mut packet_info)?;
self.reader.read_exact(&mut buffer)?; self.read_exact(&mut buffer)?;
let packet_data_length = u32::from_be_bytes(buffer) as usize; let packet_data_length = u32::from_be_bytes(buffer) as usize;
let mut data = vec![0u8; packet_data_length]; let mut data = vec![0u8; packet_data_length];
self.reader.read_exact(&mut data)?; self.read_exact(&mut data)?;
packets.push_back(Packet { packets.push_back(Packet {
id: packet_info[0], id: packet_info[0],
@ -263,7 +338,7 @@ fn new_tcp_backend(address: &str) -> Result<TcpBackend, Error> {
let stream = match TcpStream::connect(address) { let stream = match TcpStream::connect(address) {
Ok(stream) => { Ok(stream) => {
stream.set_write_timeout(Some(Duration::from_secs(10)))?; stream.set_write_timeout(Some(Duration::from_secs(10)))?;
stream.set_read_timeout(Some(Duration::from_secs(10)))?; stream.set_read_timeout(Some(Duration::from_millis(10)))?;
stream stream
} }
Err(error) => { Err(error) => {
@ -274,11 +349,7 @@ fn new_tcp_backend(address: &str) -> Result<TcpBackend, Error> {
}; };
let reader = BufReader::new(stream.try_clone()?); let reader = BufReader::new(stream.try_clone()?);
let writer = BufWriter::new(stream.try_clone()?); let writer = BufWriter::new(stream.try_clone()?);
Ok(TcpBackend { Ok(TcpBackend { reader, writer })
stream,
reader,
writer,
})
} }
pub struct Link { pub struct Link {
@ -383,123 +454,3 @@ pub fn list_local_devices() -> Result<Vec<LocalDevice>, Error> {
return Ok(serial_devices); return Ok(serial_devices);
} }
pub enum ServerEvent {
Listening(String),
Connection(String),
Disconnected(String),
Err(String),
}
pub fn run_server(
port: &str,
address: String,
event_callback: fn(ServerEvent),
) -> Result<(), Error> {
let listener = TcpListener::bind(address)?;
event_callback(ServerEvent::Listening(listener.local_addr()?.to_string()));
for stream in listener.incoming() {
match stream {
Ok(mut stream) => match server_accept_connection(port, event_callback, &mut stream) {
Ok(()) => {}
Err(error) => event_callback(ServerEvent::Err(error.to_string())),
},
Err(error) => return Err(error.into()),
}
}
Ok(())
}
fn server_accept_connection(
port: &str,
event_callback: fn(ServerEvent),
stream: &mut TcpStream,
) -> Result<(), Error> {
let peer = stream.peer_addr()?.to_string();
stream.set_write_timeout(Some(Duration::from_secs(10)))?;
stream.set_read_timeout(Some(Duration::from_secs(10)))?;
let mut reader = BufReader::new(stream.try_clone()?);
let mut writer = BufWriter::new(stream.try_clone()?);
let mut serial_backend = new_serial_backend(port)?;
serial_backend.reset()?;
let mut packets: VecDeque<Packet> = VecDeque::new();
let mut buffer = [0u8; 4];
let mut keepalive = Instant::now();
event_callback(ServerEvent::Connection(peer.clone()));
loop {
stream.set_nonblocking(true)?;
match reader.read_exact(&mut buffer) {
Ok(()) => {
stream.set_nonblocking(false)?;
let data_type: DataType = u32::from_be_bytes(buffer).try_into()?;
if !matches!(data_type, DataType::Command) {
return Err(Error::new("Received data type wasn't a command data type"));
}
let mut id_buffer = [0u8; 1];
let mut args = [0u32; 2];
reader.read_exact(&mut id_buffer)?;
reader.read_exact(&mut buffer)?;
args[0] = u32::from_be_bytes(buffer);
reader.read_exact(&mut buffer)?;
args[1] = u32::from_be_bytes(buffer);
reader.read_exact(&mut buffer)?;
let command_data_length = u32::from_be_bytes(buffer) as usize;
let mut data = vec![0u8; command_data_length];
reader.read_exact(&mut data)?;
serial_backend.send_command(&Command {
id: id_buffer[0],
args,
data: &data,
})?;
continue;
}
Err(error) => {
if error.kind() != ErrorKind::WouldBlock {
event_callback(ServerEvent::Disconnected(peer.clone()));
return Ok(());
}
stream.set_nonblocking(false)?;
}
}
if let Some(response) =
serial_backend.process_incoming_data(DataType::Packet, &mut packets)?
{
writer.write_all(&u32::to_be_bytes(DataType::Response.into()))?;
writer.write_all(&[response.id])?;
writer.write_all(&[response.error as u8])?;
writer.write_all(&(response.data.len() as u32).to_be_bytes())?;
writer.write_all(&response.data)?;
writer.flush()?;
} else if let Some(packet) = packets.pop_front() {
writer.write_all(&u32::to_be_bytes(DataType::Packet.into()))?;
writer.write_all(&[packet.id])?;
writer.write_all(&(packet.data.len() as u32).to_be_bytes())?;
writer.write_all(&packet.data)?;
writer.flush()?;
} else if keepalive.elapsed() > Duration::from_secs(5) {
keepalive = Instant::now();
writer.write_all(&u32::to_be_bytes(DataType::KeepAlive.into()))?;
writer.flush()?;
} else {
std::thread::sleep(Duration::from_millis(1));
}
}
}

View File

@ -2,26 +2,28 @@ mod cic;
mod error; mod error;
pub mod firmware; pub mod firmware;
mod link; mod link;
mod server;
mod time;
mod types; mod types;
mod utils;
pub use self::{ pub use self::{
error::Error, error::Error,
link::{list_local_devices, ServerEvent}, link::list_local_devices,
server::ServerEvent,
types::{ types::{
BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState, DdDriveType, DdMode, BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState, DdDriveType, DdMode,
DebugPacket, DiskPacket, DiskPacketKind, FpgaDebugData, McuStackUsage, SaveType, Switch, DebugPacket, DiskPacket, DiskPacketKind, FpgaDebugData, McuStackUsage, SaveType,
TvType, SaveWriteback, Switch, TvType,
}, },
}; };
use self::{ use self::{
cic::{calculate_ipl3_checksum, guess_ipl3_seed, IPL3_LENGTH, IPL3_OFFSET}, cic::{calculate_ipl3_checksum, guess_ipl3_seed, IPL3_LENGTH, IPL3_OFFSET},
link::{Command, Link}, link::{Command, Link},
time::{convert_from_datetime, convert_to_datetime},
types::{ types::{
get_config, get_setting, Config, ConfigId, FirmwareStatus, Setting, SettingId, UpdateStatus, get_config, get_setting, Config, ConfigId, FirmwareStatus, Setting, SettingId, UpdateStatus,
}, },
utils::{convert_from_datetime, convert_to_datetime},
}; };
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
use std::{ use std::{
@ -81,8 +83,9 @@ const EEPROM_ADDRESS: u32 = 0x0500_2000;
const EEPROM_4K_LENGTH: usize = 512; const EEPROM_4K_LENGTH: usize = 512;
const EEPROM_16K_LENGTH: usize = 2 * 1024; const EEPROM_16K_LENGTH: usize = 2 * 1024;
const SRAM_LENGTH: usize = 32 * 1024; const SRAM_LENGTH: usize = 32 * 1024;
const SRAM_BANKED_LENGTH: usize = 3 * 32 * 1024;
const FLASHRAM_LENGTH: usize = 128 * 1024; const FLASHRAM_LENGTH: usize = 128 * 1024;
const SRAM_BANKED_LENGTH: usize = 3 * 32 * 1024;
const SRAM_1M_LENGTH: usize = 128 * 1024;
const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000; const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000;
@ -100,7 +103,7 @@ impl SC64 {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&Command {
id: b'v', id: b'v',
args: [0, 0], args: [0, 0],
data: &[], data: vec![],
})?; })?;
if data.len() != 4 { if data.len() != 4 {
return Err(Error::new( return Err(Error::new(
@ -114,7 +117,7 @@ impl SC64 {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&Command {
id: b'V', id: b'V',
args: [0, 0], args: [0, 0],
data: &[], data: vec![],
})?; })?;
if data.len() != 8 { if data.len() != 8 {
return Err(Error::new( return Err(Error::new(
@ -131,7 +134,7 @@ impl SC64 {
self.link.execute_command(&Command { self.link.execute_command(&Command {
id: b'R', id: b'R',
args: [0, 0], args: [0, 0],
data: &[], data: vec![],
})?; })?;
Ok(()) Ok(())
} }
@ -149,7 +152,7 @@ impl SC64 {
self.link.execute_command(&Command { self.link.execute_command(&Command {
id: b'B', id: b'B',
args, args,
data: &[], data: vec![],
})?; })?;
Ok(()) Ok(())
} }
@ -158,7 +161,7 @@ impl SC64 {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&Command {
id: b'c', id: b'c',
args: [config_id.into(), 0], args: [config_id.into(), 0],
data: &[], data: vec![],
})?; })?;
if data.len() != 4 { if data.len() != 4 {
return Err(Error::new( return Err(Error::new(
@ -173,7 +176,7 @@ impl SC64 {
self.link.execute_command(&Command { self.link.execute_command(&Command {
id: b'C', id: b'C',
args: config.into(), args: config.into(),
data: &[], data: vec![],
})?; })?;
Ok(()) Ok(())
} }
@ -182,7 +185,7 @@ impl SC64 {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&Command {
id: b'a', id: b'a',
args: [setting_id.into(), 0], args: [setting_id.into(), 0],
data: &[], data: vec![],
})?; })?;
if data.len() != 4 { if data.len() != 4 {
return Err(Error::new( return Err(Error::new(
@ -197,7 +200,7 @@ impl SC64 {
self.link.execute_command(&Command { self.link.execute_command(&Command {
id: b'A', id: b'A',
args: setting.into(), args: setting.into(),
data: &[], data: vec![],
})?; })?;
Ok(()) Ok(())
} }
@ -206,7 +209,7 @@ impl SC64 {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&Command {
id: b't', id: b't',
args: [0, 0], args: [0, 0],
data: &[], data: vec![],
})?; })?;
if data.len() != 8 { if data.len() != 8 {
return Err(Error::new( return Err(Error::new(
@ -220,7 +223,7 @@ impl SC64 {
self.link.execute_command(&Command { self.link.execute_command(&Command {
id: b'T', id: b'T',
args: convert_from_datetime(datetime), args: convert_from_datetime(datetime),
data: &[], data: vec![],
})?; })?;
Ok(()) Ok(())
} }
@ -229,7 +232,7 @@ impl SC64 {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&Command {
id: b'm', id: b'm',
args: [address, length as u32], args: [address, length as u32],
data: &[], data: vec![],
})?; })?;
if data.len() != length { if data.len() != length {
return Err(Error::new( return Err(Error::new(
@ -243,7 +246,7 @@ impl SC64 {
self.link.execute_command(&Command { self.link.execute_command(&Command {
id: b'M', id: b'M',
args: [address, data.len() as u32], args: [address, data.len() as u32],
data, data: data.to_vec(),
})?; })?;
Ok(()) Ok(())
} }
@ -253,7 +256,7 @@ impl SC64 {
&Command { &Command {
id: b'U', id: b'U',
args: [datatype as u32, data.len() as u32], args: [datatype as u32, data.len() as u32],
data, data: data.to_vec(),
}, },
true, true,
false, false,
@ -265,7 +268,16 @@ impl SC64 {
self.link.execute_command(&Command { self.link.execute_command(&Command {
id: b'D', id: b'D',
args: [error as u32, 0], args: [error as u32, 0],
data: &[], data: vec![],
})?;
Ok(())
}
fn command_writeback_enable(&mut self) -> Result<(), Error> {
self.link.execute_command(&Command {
id: b'W',
args: [0, 0],
data: vec![],
})?; })?;
Ok(()) Ok(())
} }
@ -274,7 +286,7 @@ impl SC64 {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&Command {
id: b'p', id: b'p',
args: [wait as u32, 0], args: [wait as u32, 0],
data: &[], data: vec![],
})?; })?;
if data.len() != 4 { if data.len() != 4 {
return Err(Error::new( return Err(Error::new(
@ -289,7 +301,7 @@ impl SC64 {
self.link.execute_command(&Command { self.link.execute_command(&Command {
id: b'P', id: b'P',
args: [address, 0], args: [address, 0],
data: &[], data: vec![],
})?; })?;
Ok(()) Ok(())
} }
@ -299,7 +311,7 @@ impl SC64 {
&Command { &Command {
id: b'f', id: b'f',
args: [address, 0], args: [address, 0],
data: &[], data: vec![],
}, },
false, false,
true, true,
@ -323,7 +335,7 @@ impl SC64 {
&Command { &Command {
id: b'F', id: b'F',
args: [address, length as u32], args: [address, length as u32],
data: &[], data: vec![],
}, },
false, false,
true, true,
@ -340,7 +352,7 @@ impl SC64 {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&Command {
id: b'?', id: b'?',
args: [0, 0], args: [0, 0],
data: &[], data: vec![],
})?; })?;
Ok(data.try_into()?) Ok(data.try_into()?)
} }
@ -349,7 +361,7 @@ impl SC64 {
let data = self.link.execute_command(&Command { let data = self.link.execute_command(&Command {
id: b'%', id: b'%',
args: [0, 0], args: [0, 0],
data: &[], data: vec![],
})?; })?;
Ok(data.try_into()?) Ok(data.try_into()?)
} }
@ -439,8 +451,9 @@ impl SC64 {
SaveType::Eeprom4k => (EEPROM_ADDRESS, EEPROM_4K_LENGTH), SaveType::Eeprom4k => (EEPROM_ADDRESS, EEPROM_4K_LENGTH),
SaveType::Eeprom16k => (EEPROM_ADDRESS, EEPROM_16K_LENGTH), SaveType::Eeprom16k => (EEPROM_ADDRESS, EEPROM_16K_LENGTH),
SaveType::Sram => (SAVE_ADDRESS, SRAM_LENGTH), SaveType::Sram => (SAVE_ADDRESS, SRAM_LENGTH),
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH), SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
SaveType::Sram1m => (SAVE_ADDRESS, SRAM_1M_LENGTH),
}; };
if length != save_length { if length != save_length {
@ -462,8 +475,9 @@ impl SC64 {
SaveType::Eeprom4k => (EEPROM_ADDRESS, EEPROM_4K_LENGTH), SaveType::Eeprom4k => (EEPROM_ADDRESS, EEPROM_4K_LENGTH),
SaveType::Eeprom16k => (EEPROM_ADDRESS, EEPROM_16K_LENGTH), SaveType::Eeprom16k => (EEPROM_ADDRESS, EEPROM_16K_LENGTH),
SaveType::Sram => (SAVE_ADDRESS, SRAM_LENGTH), SaveType::Sram => (SAVE_ADDRESS, SRAM_LENGTH),
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH), SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
SaveType::Sram1m => (SAVE_ADDRESS, SRAM_1M_LENGTH),
}; };
self.memory_read_chunked(writer, address, save_length) self.memory_read_chunked(writer, address, save_length)
@ -580,6 +594,16 @@ impl SC64 {
Ok(()) Ok(())
} }
pub fn set_save_writeback(&mut self, enabled: bool) -> Result<(), Error> {
if enabled {
self.command_writeback_enable()?;
} else {
let save_type = get_config!(self, SaveType)?;
self.set_save_type(save_type)?;
}
Ok(())
}
pub fn receive_data_packet(&mut self) -> Result<Option<DataPacket>, Error> { pub fn receive_data_packet(&mut self) -> Result<Option<DataPacket>, Error> {
if let Some(packet) = self.link.receive_packet()? { if let Some(packet) = self.link.receive_packet()? {
return Ok(Some(packet.try_into()?)); return Ok(Some(packet.try_into()?));
@ -617,13 +641,15 @@ impl SC64 {
} }
pub fn check_firmware_version(&mut self) -> Result<(u16, u16, u32), Error> { pub fn check_firmware_version(&mut self) -> Result<(u16, u16, u32), Error> {
let unsupported_version_message = format!(
"Unsupported SC64 firmware version, minimum supported version: {}.{}.x",
SUPPORTED_MAJOR_VERSION, SUPPORTED_MINOR_VERSION
);
let (major, minor, revision) = self let (major, minor, revision) = self
.command_version_get() .command_version_get()
.map_err(|_| Error::new("Outdated SC64 firmware version, please update firmware"))?; .map_err(|_| Error::new(unsupported_version_message.as_str()))?;
if major != SUPPORTED_MAJOR_VERSION || minor < SUPPORTED_MINOR_VERSION { if major != SUPPORTED_MAJOR_VERSION || minor < SUPPORTED_MINOR_VERSION {
return Err(Error::new( return Err(Error::new(unsupported_version_message.as_str()));
"Unsupported SC64 firmware version, please update firmware",
));
} }
Ok((major, minor, revision)) Ok((major, minor, revision))
} }
@ -784,5 +810,5 @@ pub fn run_server(
} else { } else {
list_local_devices()?[0].port.clone() list_local_devices()?[0].port.clone()
}; };
link::run_server(&port, address, event_callback) server::run_server(&port, address, event_callback)
} }

View File

@ -0,0 +1,253 @@
use super::{
error::Error,
link::{new_serial, Command, DataType, Packet, Response, Serial},
};
use std::{
collections::VecDeque,
io::{BufReader, BufWriter, ErrorKind, Read, Write},
net::{TcpListener, TcpStream},
sync::{
atomic::{AtomicBool, Ordering},
mpsc::{channel, Receiver, Sender},
Arc,
},
thread,
time::{Duration, Instant},
};
pub enum ServerEvent {
Listening(String),
Connected(String),
Disconnected(String),
Err(String),
}
pub fn run_server(
port: &str,
address: String,
event_callback: fn(ServerEvent),
) -> Result<(), Error> {
let listener = TcpListener::bind(address)?;
let listening_address = listener.local_addr()?;
event_callback(ServerEvent::Listening(listening_address.to_string()));
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
let peer = stream.peer_addr()?.to_string();
event_callback(ServerEvent::Connected(peer.clone()));
match server_accept_connection(port, &mut stream) {
Ok(()) => event_callback(ServerEvent::Disconnected(peer.clone())),
Err(error) => event_callback(ServerEvent::Err(error.to_string())),
}
}
Err(error) => match error.kind() {
_ => return Err(error.into()),
},
}
}
Ok(())
}
enum Event {
Command(Command),
Response(Response),
Packet(Packet),
KeepAlive,
Closed(Option<Error>),
}
fn server_accept_connection(port: &str, stream: &mut TcpStream) -> Result<(), Error> {
let (event_sender, event_receiver) = channel::<Event>();
let exit_flag = Arc::new(AtomicBool::new(false));
let mut stream_writer = BufWriter::new(stream.try_clone()?);
let mut stream_reader = stream.try_clone()?;
let serial = Arc::new(new_serial(port)?);
let serial_writer = serial.clone();
let serial_reader = serial.clone();
let stream_event_sender = event_sender.clone();
let stream_exit_flag = exit_flag.clone();
let stream_thread = thread::spawn(move || {
let closed_sender = stream_event_sender.clone();
match server_stream_thread(&mut stream_reader, stream_event_sender, stream_exit_flag) {
Ok(()) => closed_sender.send(Event::Closed(None)),
Err(error) => closed_sender.send(Event::Closed(Some(error))),
}
.ok();
});
let serial_event_sender = event_sender.clone();
let serial_exit_flag = exit_flag.clone();
let serial_thread = thread::spawn(move || {
let closed_sender = serial_event_sender.clone();
match server_serial_thread(serial_reader, serial_event_sender, serial_exit_flag) {
Ok(()) => closed_sender.send(Event::Closed(None)),
Err(error) => closed_sender.send(Event::Closed(Some(error))),
}
.ok();
});
let keepalive_event_sender = event_sender.clone();
let keepalive_exit_flag = exit_flag.clone();
let keepalive_thread = thread::spawn(move || {
server_keepalive_thread(keepalive_event_sender, keepalive_exit_flag);
});
let result = server_process_events(&mut stream_writer, serial_writer, event_receiver);
exit_flag.store(true, Ordering::Relaxed);
stream_thread.join().ok();
serial_thread.join().ok();
keepalive_thread.join().ok();
result
}
fn server_process_events(
stream_writer: &mut BufWriter<TcpStream>,
serial_writer: Arc<Serial>,
event_receiver: Receiver<Event>,
) -> Result<(), Error> {
for event in event_receiver.into_iter() {
match event {
Event::Command(command) => {
serial_writer.send_command(&command)?;
}
Event::Response(response) => {
stream_writer.write_all(&u32::to_be_bytes(DataType::Response.into()))?;
stream_writer.write_all(&[response.id])?;
stream_writer.write_all(&[response.error as u8])?;
stream_writer.write_all(&(response.data.len() as u32).to_be_bytes())?;
stream_writer.write_all(&response.data)?;
stream_writer.flush()?;
}
Event::Packet(packet) => {
stream_writer.write_all(&u32::to_be_bytes(DataType::Packet.into()))?;
stream_writer.write_all(&[packet.id])?;
stream_writer.write_all(&(packet.data.len() as u32).to_be_bytes())?;
stream_writer.write_all(&packet.data)?;
stream_writer.flush()?;
}
Event::KeepAlive => {
stream_writer.write_all(&u32::to_be_bytes(DataType::KeepAlive.into()))?;
stream_writer.flush()?;
}
Event::Closed(result) => match result {
Some(error) => return Err(error),
None => {
break;
}
},
}
}
Ok(())
}
fn server_stream_thread(
stream: &mut TcpStream,
event_sender: Sender<Event>,
exit_flag: Arc<AtomicBool>,
) -> Result<(), Error> {
let mut stream_reader = BufReader::new(stream.try_clone()?);
let mut header = [0u8; 4];
let header_length = header.len();
loop {
let mut header_position = 0;
let timeout = stream.read_timeout()?;
stream.set_read_timeout(Some(Duration::from_millis(10)))?;
while header_position < header_length {
if exit_flag.load(Ordering::Relaxed) {
return Ok(());
}
match stream.read(&mut header[header_position..header_length]) {
Ok(0) => return Ok(()),
Ok(bytes) => header_position += bytes,
Err(error) => match error.kind() {
ErrorKind::Interrupted | ErrorKind::TimedOut | ErrorKind::WouldBlock => {}
_ => return Err(error.into()),
},
}
}
stream.set_read_timeout(timeout)?;
let data_type: DataType = u32::from_be_bytes(header).try_into()?;
if !matches!(data_type, DataType::Command) {
return Err(Error::new("Received data type was not a command data type"));
}
let mut buffer = [0u8; 4];
let mut id_buffer = [0u8; 1];
let mut args = [0u32; 2];
stream_reader.read_exact(&mut id_buffer)?;
let id = id_buffer[0];
stream_reader.read_exact(&mut buffer)?;
args[0] = u32::from_be_bytes(buffer);
stream_reader.read_exact(&mut buffer)?;
args[1] = u32::from_be_bytes(buffer);
stream_reader.read_exact(&mut buffer)?;
let command_data_length = u32::from_be_bytes(buffer) as usize;
let mut data = vec![0u8; command_data_length];
stream_reader.read_exact(&mut data)?;
if event_sender
.send(Event::Command(Command { id, args, data }))
.is_err()
{
break;
}
}
Ok(())
}
fn server_serial_thread(
serial_reader: Arc<Serial>,
event_sender: Sender<Event>,
exit_flag: Arc<AtomicBool>,
) -> Result<(), Error> {
let mut packets: VecDeque<Packet> = VecDeque::new();
while !exit_flag.load(Ordering::Relaxed) {
let response = serial_reader.process_incoming_data(DataType::Packet, &mut packets)?;
if let Some(response) = response {
if event_sender.send(Event::Response(response)).is_err() {
break;
}
}
if let Some(packet) = packets.pop_front() {
if event_sender.send(Event::Packet(packet)).is_err() {
break;
}
}
}
Ok(())
}
fn server_keepalive_thread(event_sender: Sender<Event>, exit_flag: Arc<AtomicBool>) {
let mut keepalive = Instant::now();
while !exit_flag.load(Ordering::Relaxed) {
if keepalive.elapsed() >= Duration::from_secs(5) {
keepalive = Instant::now();
if event_sender.send(Event::KeepAlive).is_err() {
break;
}
} else {
thread::sleep(Duration::from_millis(10));
}
}
}

View File

@ -1,5 +1,4 @@
use super::{link::Packet, Error}; use super::{link::Packet, Error};
use encoding_rs::EUC_JP;
use std::fmt::Display; use std::fmt::Display;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -253,6 +252,7 @@ pub enum SaveType {
Sram, Sram,
Flashram, Flashram,
SramBanked, SramBanked,
Sram1m,
} }
impl Display for SaveType { impl Display for SaveType {
@ -261,9 +261,10 @@ impl Display for SaveType {
Self::None => "None", Self::None => "None",
Self::Eeprom4k => "EEPROM 4k", Self::Eeprom4k => "EEPROM 4k",
Self::Eeprom16k => "EEPROM 16k", Self::Eeprom16k => "EEPROM 16k",
Self::Sram => "SRAM", Self::Sram => "SRAM 256k",
Self::SramBanked => "SRAM banked", Self::SramBanked => "SRAM 768k",
Self::Flashram => "FlashRAM", Self::Flashram => "FlashRAM 1M",
Self::Sram1m => "SRAM 1M",
}) })
} }
} }
@ -278,6 +279,7 @@ impl TryFrom<u32> for SaveType {
3 => Self::Sram, 3 => Self::Sram,
4 => Self::Flashram, 4 => Self::Flashram,
5 => Self::SramBanked, 5 => Self::SramBanked,
6 => Self::Sram1m,
_ => return Err(Error::new("Unknown save type code")), _ => return Err(Error::new("Unknown save type code")),
}) })
} }
@ -292,6 +294,7 @@ impl From<SaveType> for u32 {
SaveType::Sram => 3, SaveType::Sram => 3,
SaveType::Flashram => 4, SaveType::Flashram => 4,
SaveType::SramBanked => 5, SaveType::SramBanked => 5,
SaveType::Sram1m => 6,
} }
} }
} }
@ -337,7 +340,7 @@ pub enum TvType {
PAL, PAL,
NTSC, NTSC,
MPAL, MPAL,
Auto, Passthrough,
} }
impl Display for TvType { impl Display for TvType {
@ -346,7 +349,7 @@ impl Display for TvType {
Self::PAL => "PAL", Self::PAL => "PAL",
Self::NTSC => "NTSC", Self::NTSC => "NTSC",
Self::MPAL => "MPAL", Self::MPAL => "MPAL",
Self::Auto => "Auto", Self::Passthrough => "Passthrough",
}) })
} }
} }
@ -358,7 +361,7 @@ impl TryFrom<u32> for TvType {
0 => Self::PAL, 0 => Self::PAL,
1 => Self::NTSC, 1 => Self::NTSC,
2 => Self::MPAL, 2 => Self::MPAL,
3 => Self::Auto, 3 => Self::Passthrough,
_ => return Err(Error::new("Unknown TV type code")), _ => return Err(Error::new("Unknown TV type code")),
}) })
} }
@ -370,7 +373,7 @@ impl From<TvType> for u32 {
TvType::PAL => 0, TvType::PAL => 0,
TvType::NTSC => 1, TvType::NTSC => 1,
TvType::MPAL => 2, TvType::MPAL => 2,
TvType::Auto => 3, TvType::Passthrough => 3,
} }
} }
} }
@ -577,9 +580,10 @@ impl From<Setting> for [u32; 2] {
pub enum DataPacket { pub enum DataPacket {
Button, Button,
Debug(DebugPacket), DebugData(DebugPacket),
Disk(DiskPacket), DiskRequest(DiskPacket),
IsViewer(String), IsViewer64(Vec<u8>),
SaveWriteback(SaveWriteback),
UpdateStatus(UpdateStatus), UpdateStatus(UpdateStatus),
} }
@ -588,9 +592,10 @@ impl TryFrom<Packet> for DataPacket {
fn try_from(value: Packet) -> Result<Self, Self::Error> { fn try_from(value: Packet) -> Result<Self, Self::Error> {
Ok(match value.id { Ok(match value.id {
b'B' => Self::Button, b'B' => Self::Button,
b'U' => Self::Debug(value.data.try_into()?), b'U' => Self::DebugData(value.data.try_into()?),
b'D' => Self::Disk(value.data.try_into()?), b'D' => Self::DiskRequest(value.data.try_into()?),
b'I' => Self::IsViewer(EUC_JP.decode(&value.data).0.into()), b'I' => Self::IsViewer64(value.data),
b'S' => Self::SaveWriteback(value.data.try_into()?),
b'F' => { b'F' => {
if value.data.len() != 4 { if value.data.len() != 4 {
return Err(Error::new( return Err(Error::new(
@ -682,6 +687,25 @@ impl DiskBlock {
} }
} }
pub struct SaveWriteback {
pub save: SaveType,
pub data: Vec<u8>,
}
impl TryFrom<Vec<u8>> for SaveWriteback {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
if value.len() < 4 {
return Err(Error::new(
"Couldn't extract save info from save writeback packet",
));
}
let save: SaveType = u32::from_be_bytes(value[0..4].try_into().unwrap()).try_into()?;
let data = value[4..].to_vec();
Ok(SaveWriteback { save, data })
}
}
pub enum FirmwareStatus { pub enum FirmwareStatus {
Ok, Ok,
ErrToken, ErrToken,
@ -782,7 +806,10 @@ impl TryFrom<Vec<u8>> for FpgaDebugData {
impl Display for FpgaDebugData { impl Display for FpgaDebugData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("PI address: 0x{:08X}", self.last_pi_address))?; f.write_fmt(format_args!(
"Last PI address: 0x{:08X}",
self.last_pi_address
))?;
if self.read_fifo_wait { if self.read_fifo_wait {
f.write_str(" RW")?; f.write_str(" RW")?;
} }