mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2024-11-24 14:46:53 +01:00
[SC64][SW] Server performance increase / USB save writeback / bootloader fixes (#37)
This commit is contained in:
parent
066f3b0485
commit
7afb0ef05c
@ -20,6 +20,7 @@
|
||||
| `M` | **MEMORY_WRITE** | address | length | data | --- | Write data to specified memory address |
|
||||
| `U` | **USB_WRITE** | type | length | data | N/A | Send data to be received by app running on N64 (no response!) |
|
||||
| `D` | **DD_SET_BLOCK_READY** | success | --- | --- | --- | Notify flashcart about 64DD block readiness |
|
||||
| `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_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase |
|
||||
| `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address |
|
||||
|
@ -132,9 +132,10 @@ type: *enum* | default: `0`
|
||||
- `3` - SRAM 256 kib save is enabled
|
||||
- `4` - FlashRAM 1 Mib 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.
|
||||
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
|
||||
- `1` - NTSC 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.
|
||||
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).
|
||||
|
||||
---
|
||||
|
@ -37,33 +37,6 @@ static io32_t *boot_get_device_base (boot_info_t *info) {
|
||||
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) {
|
||||
io32_t *base = boot_get_device_base(info);
|
||||
|
||||
@ -83,12 +56,10 @@ static bool boot_get_cic_seed (boot_info_t *info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void boot (boot_info_t *info, bool detect_tv_type, bool detect_cic_seed) {
|
||||
if (detect_tv_type) {
|
||||
if (!boot_get_tv_type(info)) {
|
||||
void boot (boot_info_t *info, bool detect_cic_seed) {
|
||||
if (info->tv_type == BOOT_TV_TYPE_PASSTHROUGH) {
|
||||
info->tv_type = OS_INFO->tv_type;
|
||||
}
|
||||
}
|
||||
|
||||
if (detect_cic_seed) {
|
||||
if (!boot_get_cic_seed(info)) {
|
||||
|
@ -20,6 +20,7 @@ typedef enum {
|
||||
BOOT_TV_TYPE_PAL = 0,
|
||||
BOOT_TV_TYPE_NTSC = 1,
|
||||
BOOT_TV_TYPE_MPAL = 2,
|
||||
BOOT_TV_TYPE_PASSTHROUGH = 3,
|
||||
} boot_tv_type_t;
|
||||
|
||||
|
||||
@ -31,7 +32,7 @@ typedef struct {
|
||||
} 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
|
||||
|
@ -13,18 +13,14 @@
|
||||
#define TEXT_COLOR (0xFFFFFFFFUL)
|
||||
#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 int char_x;
|
||||
static int char_y;
|
||||
static const vi_regs_t vi_config[] = {{
|
||||
.CR = (
|
||||
VI_CR_PIXEL_ADVANCE_1 |
|
||||
VI_CR_PIXEL_ADVANCE_0 |
|
||||
VI_CR_ANTIALIAS_1 |
|
||||
VI_CR_ANTIALIAS_0 |
|
||||
VI_CR_TYPE_32
|
||||
),
|
||||
.CR = VI_CR,
|
||||
.H_WIDTH = SCREEN_WIDTH,
|
||||
.V_INTR = 0x000003FF,
|
||||
.CURR_LINE = 0x00000000,
|
||||
@ -38,13 +34,7 @@ static const vi_regs_t vi_config[] = {{
|
||||
.H_SCALE = 0x00000400,
|
||||
.V_SCALE = 0x00000400,
|
||||
}, {
|
||||
.CR = (
|
||||
VI_CR_PIXEL_ADVANCE_1 |
|
||||
VI_CR_PIXEL_ADVANCE_0 |
|
||||
VI_CR_ANTIALIAS_1 |
|
||||
VI_CR_ANTIALIAS_0 |
|
||||
VI_CR_TYPE_32
|
||||
),
|
||||
.CR = VI_CR,
|
||||
.H_WIDTH = SCREEN_WIDTH,
|
||||
.V_INTR = 0x000003FF,
|
||||
.CURR_LINE = 0x00000000,
|
||||
@ -57,6 +47,20 @@ static const vi_regs_t vi_config[] = {{
|
||||
.COLOR_BURST = 0x000E0204,
|
||||
.H_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,
|
||||
}};
|
||||
|
||||
|
||||
|
@ -30,14 +30,12 @@ void main (void) {
|
||||
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.tv_type = sc64_boot_info.tv_type;
|
||||
boot_info.cic_seed = (sc64_boot_info.cic_seed & 0xFF);
|
||||
bool detect_cic_seed = (sc64_boot_info.cic_seed == CIC_SEED_AUTO);
|
||||
|
||||
deinit();
|
||||
|
||||
boot(&boot_info, detect_tv_type, detect_cic_seed);
|
||||
boot(&boot_info, detect_cic_seed);
|
||||
}
|
||||
|
@ -59,18 +59,19 @@ typedef enum {
|
||||
SAVE_TYPE_EEPROM_16K = 2,
|
||||
SAVE_TYPE_SRAM = 3,
|
||||
SAVE_TYPE_FLASHRAM = 4,
|
||||
SAVE_TYPE_SRAM_BANKED = 5
|
||||
SAVE_TYPE_SRAM_BANKED = 5,
|
||||
SAVE_TYPE_SRAM_1M = 6
|
||||
} sc64_save_type_t;
|
||||
|
||||
typedef enum {
|
||||
CIC_SEED_UNKNOWN = 0xFFFF
|
||||
CIC_SEED_AUTO = 0xFFFF
|
||||
} sc64_cic_seed_t;
|
||||
|
||||
typedef enum {
|
||||
TV_TYPE_PAL = 0,
|
||||
TV_TYPE_NTSC = 1,
|
||||
TV_TYPE_MPAL = 2,
|
||||
TV_TYPE_UNKNOWN = 3
|
||||
TV_TYPE_PASSTHROUGH = 3
|
||||
} sc64_tv_type_t;
|
||||
|
||||
typedef enum {
|
||||
|
@ -54,14 +54,14 @@ typedef enum {
|
||||
} boot_mode_t;
|
||||
|
||||
typedef enum {
|
||||
CIC_SEED_UNKNOWN = 0xFFFF
|
||||
CIC_SEED_AUTO = 0xFFFF
|
||||
} cic_seed_t;
|
||||
|
||||
typedef enum {
|
||||
TV_TYPE_PAL = 0,
|
||||
TV_TYPE_NTSC = 1,
|
||||
TV_TYPE_MPAL = 2,
|
||||
TV_TYPE_UNKNOWN = 3
|
||||
TV_TYPE_PASSTHROUGH = 3
|
||||
} tv_type_t;
|
||||
|
||||
typedef enum {
|
||||
@ -210,6 +210,9 @@ static bool cfg_set_save_type (save_type_t save_type) {
|
||||
case SAVE_TYPE_SRAM_BANKED:
|
||||
cfg_change_scr_bits(CFG_SCR_SRAM_BANKED | CFG_SCR_SRAM_ENABLED, true);
|
||||
break;
|
||||
case SAVE_TYPE_SRAM_1M:
|
||||
cfg_change_scr_bits(CFG_SCR_SRAM_ENABLED, true);
|
||||
break;
|
||||
default:
|
||||
save_type = SAVE_TYPE_NONE;
|
||||
break;
|
||||
@ -337,7 +340,7 @@ bool cfg_update (uint32_t *args) {
|
||||
p.cic_seed = (cic_seed_t) (args[1] & 0xFFFF);
|
||||
break;
|
||||
case CFG_ID_TV_TYPE:
|
||||
if (args[1] > TV_TYPE_UNKNOWN) {
|
||||
if (args[1] > TV_TYPE_PASSTHROUGH) {
|
||||
return true;
|
||||
}
|
||||
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_sd_mode(false);
|
||||
isv_set_address(0);
|
||||
p.cic_seed = CIC_SEED_UNKNOWN;
|
||||
p.tv_type = TV_TYPE_UNKNOWN;
|
||||
p.cic_seed = CIC_SEED_AUTO;
|
||||
p.tv_type = TV_TYPE_PASSTHROUGH;
|
||||
p.boot_mode = BOOT_MODE_MENU;
|
||||
}
|
||||
|
||||
@ -628,7 +631,7 @@ void cfg_process (void) {
|
||||
return;
|
||||
}
|
||||
writeback_load_sector_table(args[0]);
|
||||
writeback_enable();
|
||||
writeback_enable(WRITEBACK_SD);
|
||||
break;
|
||||
|
||||
case 'K':
|
||||
|
@ -12,7 +12,8 @@ typedef enum {
|
||||
SAVE_TYPE_EEPROM_16K = 2,
|
||||
SAVE_TYPE_SRAM = 3,
|
||||
SAVE_TYPE_FLASHRAM = 4,
|
||||
SAVE_TYPE_SRAM_BANKED = 5
|
||||
SAVE_TYPE_SRAM_BANKED = 5,
|
||||
SAVE_TYPE_SRAM_1M = 6,
|
||||
} save_type_t;
|
||||
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "update.h"
|
||||
#include "usb.h"
|
||||
#include "version.h"
|
||||
#include "writeback.h"
|
||||
|
||||
|
||||
#define BOOTLOADER_ADDRESS (0x04E00000UL)
|
||||
@ -307,6 +308,12 @@ static void usb_rx_process (void) {
|
||||
p.response_pending = true;
|
||||
break;
|
||||
|
||||
case 'W':
|
||||
writeback_enable(WRITEBACK_USB);
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
if (p.rx_args[0]) {
|
||||
flash_wait_busy();
|
||||
|
@ -11,6 +11,7 @@ typedef enum packet_cmd {
|
||||
PACKET_CMD_DD_REQUEST = 'D',
|
||||
PACKET_CMD_DEBUG_OUTPUT = 'U',
|
||||
PACKET_CMD_ISV_OUTPUT = 'I',
|
||||
PACKET_CMD_SAVE_WRITEBACK = 'S',
|
||||
PACKET_CMD_UPDATE_STATUS = 'F',
|
||||
} usb_packet_cmd_e;
|
||||
|
||||
|
@ -2,23 +2,26 @@
|
||||
#include "fpga.h"
|
||||
#include "sd.h"
|
||||
#include "timer.h"
|
||||
#include "usb.h"
|
||||
#include "writeback.h"
|
||||
|
||||
|
||||
#define SAVE_MAX_SECTOR_COUNT (256)
|
||||
#define EEPROM_ADDRESS (0x05002000)
|
||||
#define SRAM_FLASHRAM_ADDRESS (0x03FE0000)
|
||||
#define EEPROM_4K_SECTOR_COUNT (1)
|
||||
#define EEPROM_16K_SECTOR_COUNT (4)
|
||||
#define SRAM_SECTOR_COUNT (64)
|
||||
#define FLASHRAM_SECTOR_COUNT (256)
|
||||
#define SRAM_BANKED_SECTOR_COUNT (192)
|
||||
#define EEPROM_4K_LENGTH (512)
|
||||
#define EEPROM_16K_LENGTH (2048)
|
||||
#define SRAM_LENGTH (32 * 1024)
|
||||
#define FLASHRAM_LENGTH (128 * 1024)
|
||||
#define SRAM_BANKED_LENGTH (3 * 32 * 1024)
|
||||
#define SRAM_1M_LENGTH (128 * 1024)
|
||||
#define WRITEBACK_DELAY_TICKS (100)
|
||||
|
||||
|
||||
struct process {
|
||||
bool enabled;
|
||||
bool pending;
|
||||
writeback_mode_t mode;
|
||||
uint16_t last_save_count;
|
||||
uint32_t sectors[SAVE_MAX_SECTOR_COUNT];
|
||||
};
|
||||
@ -27,41 +30,79 @@ struct process {
|
||||
static struct process p;
|
||||
|
||||
|
||||
static void writeback_save_to_sd (void) {
|
||||
uint32_t address;
|
||||
uint32_t count;
|
||||
static save_type_t writeback_get_address_length (uint32_t *address, uint32_t *length) {
|
||||
save_type_t save = cfg_get_save_type();
|
||||
|
||||
switch (cfg_get_save_type()) {
|
||||
switch (save) {
|
||||
case SAVE_TYPE_EEPROM_4K:
|
||||
address = EEPROM_ADDRESS;
|
||||
count = EEPROM_4K_SECTOR_COUNT;
|
||||
*address = EEPROM_ADDRESS;
|
||||
*length = EEPROM_4K_LENGTH;
|
||||
break;
|
||||
case SAVE_TYPE_EEPROM_16K:
|
||||
address = EEPROM_ADDRESS;
|
||||
count = EEPROM_16K_SECTOR_COUNT;
|
||||
*address = EEPROM_ADDRESS;
|
||||
*length = EEPROM_16K_LENGTH;
|
||||
break;
|
||||
case SAVE_TYPE_SRAM:
|
||||
address = SRAM_FLASHRAM_ADDRESS;
|
||||
count = SRAM_SECTOR_COUNT;
|
||||
*address = SRAM_FLASHRAM_ADDRESS;
|
||||
*length = SRAM_LENGTH;
|
||||
break;
|
||||
case SAVE_TYPE_FLASHRAM:
|
||||
address = SRAM_FLASHRAM_ADDRESS;
|
||||
count = FLASHRAM_SECTOR_COUNT;
|
||||
*address = SRAM_FLASHRAM_ADDRESS;
|
||||
*length = FLASHRAM_LENGTH;
|
||||
break;
|
||||
case SAVE_TYPE_SRAM_BANKED:
|
||||
address = SRAM_FLASHRAM_ADDRESS;
|
||||
count = SRAM_BANKED_SECTOR_COUNT;
|
||||
*address = SRAM_FLASHRAM_ADDRESS;
|
||||
*length = SRAM_BANKED_LENGTH;
|
||||
break;
|
||||
case SAVE_TYPE_SRAM_1M:
|
||||
*address = SRAM_FLASHRAM_ADDRESS;
|
||||
*length = SRAM_1M_LENGTH;
|
||||
break;
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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.pending = false;
|
||||
p.mode = mode;
|
||||
p.last_save_count = fpga_reg_get(REG_SAVE_COUNT);
|
||||
}
|
||||
|
||||
@ -85,13 +127,14 @@ void writeback_disable (void) {
|
||||
void writeback_init (void) {
|
||||
p.enabled = false;
|
||||
p.pending = false;
|
||||
p.mode = WRITEBACK_SD;
|
||||
for (int i = 0; i < SAVE_MAX_SECTOR_COUNT; i++) {
|
||||
p.sectors[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void writeback_process (void) {
|
||||
if (p.enabled && !sd_card_is_inserted()) {
|
||||
if (p.enabled && (p.mode == WRITEBACK_SD) && !sd_card_is_inserted()) {
|
||||
writeback_disable();
|
||||
}
|
||||
|
||||
@ -105,7 +148,19 @@ void writeback_process (void) {
|
||||
}
|
||||
|
||||
if (p.pending && (timer_get(TIMER_ID_WRITEBACK) == 0)) {
|
||||
p.pending = false;
|
||||
switch (p.mode) {
|
||||
case WRITEBACK_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,14 @@
|
||||
#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_enable (void);
|
||||
void writeback_enable (writeback_mode_t mode);
|
||||
void writeback_disable (void);
|
||||
void writeback_init (void);
|
||||
void writeback_process (void);
|
||||
|
119
sw/deployer/Cargo.lock
generated
119
sw/deployer/Cargo.lock
generated
@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mach",
|
||||
"mach 0.1.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -20,7 +20,7 @@ checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a"
|
||||
dependencies = [
|
||||
"CoreFoundation-sys",
|
||||
"libc",
|
||||
"mach",
|
||||
"mach 0.1.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -125,9 +125,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.23"
|
||||
version = "0.4.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
|
||||
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
@ -281,7 +281,7 @@ version = "3.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"nix 0.26.2",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
@ -373,17 +373,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "exr"
|
||||
version = "1.5.3"
|
||||
version = "1.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8af5ef47e2ed89d23d0ecbc1b681b30390069de70260937877514377fc24feb"
|
||||
checksum = "bdd2162b720141a91a054640662d3edce3d50a944a50ffca5313cd951abb35b4"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
"flume",
|
||||
"half",
|
||||
"lebe",
|
||||
"miniz_oxide",
|
||||
"rayon-core",
|
||||
"smallvec",
|
||||
"threadpool",
|
||||
"zune-inflate",
|
||||
]
|
||||
|
||||
@ -412,15 +412,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.26"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
|
||||
checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.26"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
|
||||
checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
@ -718,10 +718,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.4.1"
|
||||
name = "mach"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8"
|
||||
checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -765,6 +765,17 @@ dependencies = [
|
||||
"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]]
|
||||
name = "nix"
|
||||
version = "0.26.2"
|
||||
@ -915,18 +926,18 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.51"
|
||||
version = "1.0.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||
checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -1017,6 +1028,7 @@ dependencies = [
|
||||
"md5",
|
||||
"panic-message",
|
||||
"rust-ini",
|
||||
"serial2",
|
||||
"serialport",
|
||||
]
|
||||
|
||||
@ -1038,27 +1050,39 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "serialport"
|
||||
version = "4.2.1-alpha.0"
|
||||
source = "git+https://github.com/serialport/serialport-rs?branch=main#e1f46eef5af7df2430f0a595681243e46f721b2a"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aab92efb5cf60ad310548bc3f16fa6b0d950019cb7ed8ff41968c3d03721cf12"
|
||||
dependencies = [
|
||||
"CoreFoundation-sys",
|
||||
"IOKit-sys",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libudev",
|
||||
"mach2",
|
||||
"nix",
|
||||
"mach 0.3.2",
|
||||
"nix 0.24.3",
|
||||
"regex",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14a5df39617d7c8558154693a1bb8157a4aab8179209540cc0b10e5dc24e0b18"
|
||||
checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
@ -1068,9 +1092,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.5"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dccf47db1b41fa1573ed27ccf5e08e3ca771cb994f776668c5ebda893b248fc"
|
||||
checksum = "b5d6e0250b93c8427a177b849d144a96d5acc57006149479403d7861ab721e34"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
@ -1107,15 +1131,6 @@ dependencies = [
|
||||
"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]]
|
||||
name = "tiff"
|
||||
version = "0.8.1"
|
||||
@ -1270,9 +1285,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
@ -1285,45 +1300,45 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "zune-inflate"
|
||||
|
@ -20,7 +20,8 @@ include-flate = { version = "0.2.0", features = ["stable"] }
|
||||
md5 = "0.7.0"
|
||||
panic-message = "0.3.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]
|
||||
lto = true
|
||||
|
@ -1,22 +1,24 @@
|
||||
use crate::sc64;
|
||||
use chrono::Local;
|
||||
use colored::Colorize;
|
||||
use panic_message::panic_message;
|
||||
use encoding_rs::EUC_JP;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{stdin, ErrorKind, Read, Write},
|
||||
net::{TcpListener, TcpStream},
|
||||
panic,
|
||||
io::{stdin, Read, Write},
|
||||
path::PathBuf,
|
||||
sync::mpsc::{channel, Receiver, Sender},
|
||||
thread::{sleep, spawn},
|
||||
time::Duration,
|
||||
thread::spawn,
|
||||
};
|
||||
|
||||
pub enum Encoding {
|
||||
UTF8,
|
||||
EUCJP,
|
||||
}
|
||||
|
||||
pub struct Handler {
|
||||
header: Option<Vec<u8>>,
|
||||
line_rx: Receiver<String>,
|
||||
gdb_tx: Sender<Vec<u8>>,
|
||||
gdb_rx: Receiver<Vec<u8>>,
|
||||
encoding: Encoding,
|
||||
}
|
||||
|
||||
enum DataType {
|
||||
@ -24,7 +26,6 @@ enum DataType {
|
||||
RawBinary,
|
||||
Header,
|
||||
Screenshot,
|
||||
GDB,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
@ -35,7 +36,6 @@ impl From<u8> for DataType {
|
||||
0x02 => Self::RawBinary,
|
||||
0x03 => Self::Header,
|
||||
0x04 => Self::Screenshot,
|
||||
0xDB => Self::GDB,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
@ -48,7 +48,6 @@ impl From<DataType> for u8 {
|
||||
DataType::RawBinary => 0x02,
|
||||
DataType::Header => 0x03,
|
||||
DataType::Screenshot => 0x04,
|
||||
DataType::GDB => 0xDB,
|
||||
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 {
|
||||
pub fn set_text_encoding(&mut self, encoding: Encoding) {
|
||||
self.encoding = encoding;
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
let start = line.find("@").unwrap();
|
||||
let end = line.rfind("@").unwrap();
|
||||
let path = &line[start + 1..end];
|
||||
if path.len() == 0 {
|
||||
println!("Invalid path provided");
|
||||
return None;
|
||||
line
|
||||
}
|
||||
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(),
|
||||
Err(error) => {
|
||||
println!("Couldn't get file length: {error}");
|
||||
return None;
|
||||
|
||||
let token_count = line.matches("@").count();
|
||||
|
||||
if (token_count % 2) != 0 {
|
||||
return stop!(None, "Missing closing '@' token");
|
||||
}
|
||||
};
|
||||
let mut data = vec![0u8; length as usize];
|
||||
if let Err(error) = file.read_exact(&mut data) {
|
||||
println!("Couldn't read file contents: {error}");
|
||||
return None;
|
||||
}
|
||||
if line.starts_with("@") && line.ends_with("@") {
|
||||
return Some(sc64::DebugPacket {
|
||||
|
||||
let packet = if token_count == 2 && line.starts_with("@") && line.ends_with("@") {
|
||||
sc64::DebugPacket {
|
||||
datatype: DataType::RawBinary.into(),
|
||||
data,
|
||||
});
|
||||
data: match load_file(line.trim_matches('@')) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return stop!(None, "{error}"),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
let mut combined_data: Vec<u8> = Vec::new();
|
||||
combined_data.append(&mut line[0..start].as_bytes().to_vec());
|
||||
combined_data.append(&mut [b'@'].to_vec());
|
||||
combined_data.append(&mut format!("{length}").into_bytes());
|
||||
combined_data.append(&mut [b'@'].to_vec());
|
||||
combined_data.append(&mut data);
|
||||
combined_data.append(&mut [b'\0'].to_vec());
|
||||
return Some(sc64::DebugPacket {
|
||||
let mut is_text = true;
|
||||
let mut path = String::new();
|
||||
let mut character_buffer = vec![0u8; 4];
|
||||
let mut data = vec![0u8; 0];
|
||||
for character in line.chars() {
|
||||
if character == '@' {
|
||||
if is_text {
|
||||
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(),
|
||||
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) {
|
||||
@ -186,29 +220,59 @@ impl Handler {
|
||||
DataType::RawBinary => self.handle_datatype_raw_binary(&data),
|
||||
DataType::Header => self.handle_datatype_header(&data),
|
||||
DataType::Screenshot => self.handle_datatype_screenshot(&data),
|
||||
DataType::GDB => self.handle_datatype_gdb(&data),
|
||||
_ => {
|
||||
println!("Unknown debug packet datatype: 0x{datatype:02X}");
|
||||
_ => error!("Received 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]) {
|
||||
print!("{}", String::from_utf8_lossy(data));
|
||||
self.print_text(data);
|
||||
}
|
||||
|
||||
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) {
|
||||
Ok(mut file) => {
|
||||
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);
|
||||
}
|
||||
Err(error) => {
|
||||
println!("Error during raw binary file creation: {error}");
|
||||
success!("Wrote [{}] bytes to [{filename}]", data.len());
|
||||
}
|
||||
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]) {
|
||||
let header = match self.header.take() {
|
||||
Some(header) => header,
|
||||
None => {
|
||||
println!("Got screenshot packet without header data");
|
||||
return;
|
||||
}
|
||||
None => return error!("Got screenshot packet without header data"),
|
||||
};
|
||||
let ScreenshotMetadata {
|
||||
format,
|
||||
@ -230,15 +291,11 @@ impl Handler {
|
||||
width,
|
||||
} = match header.try_into() {
|
||||
Ok(data) => data,
|
||||
Err(error) => {
|
||||
println!("{error}");
|
||||
return;
|
||||
}
|
||||
Err(error) => return error!("{error}"),
|
||||
};
|
||||
let format_size: u32 = format.into();
|
||||
if data.len() as u32 != format_size * width * height {
|
||||
println!("Data length did not match header data for screenshot datatype");
|
||||
return;
|
||||
return error!("Data length did not match header data for screenshot datatype");
|
||||
}
|
||||
let mut image = image::RgbaImage::new(width, height);
|
||||
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]],
|
||||
}
|
||||
}
|
||||
let filename = &self.generate_filename("screenshot", "png");
|
||||
let filename = &generate_filename("screenshot", "png");
|
||||
if let Some(error) = image.save(filename).err() {
|
||||
println!("Error during image save: {error}");
|
||||
return;
|
||||
return error!("Couldn't save screenshot [{filename}]: {error}");
|
||||
}
|
||||
println!("Wrote {width}x{height} pixels to [{filename}]");
|
||||
success!("Wrote {width}x{height} pixels to [{filename}]");
|
||||
}
|
||||
|
||||
fn handle_datatype_gdb(&self, data: &[u8]) {
|
||||
self.gdb_tx.send(data.to_vec()).ok();
|
||||
fn print_text(&self, data: &[u8]) {
|
||||
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 {
|
||||
pub fn new() -> Handler {
|
||||
let (line_tx, line_rx) = channel::<String>();
|
||||
spawn(move || stdin_thread(line_tx));
|
||||
Handler {
|
||||
header: None,
|
||||
line_rx,
|
||||
encoding: Encoding::UTF8,
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
)
|
||||
}
|
||||
|
||||
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> {
|
||||
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));
|
||||
|
||||
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,
|
||||
line_rx,
|
||||
gdb_tx,
|
||||
gdb_rx,
|
||||
})
|
||||
}
|
||||
|
||||
fn stdin_thread(line_tx: Sender<String>) {
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,7 @@ use std::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
time::Duration,
|
||||
{panic, process, thread},
|
||||
{panic, process},
|
||||
};
|
||||
|
||||
#[derive(Parser)]
|
||||
@ -147,13 +146,17 @@ struct _64DDArgs {
|
||||
|
||||
#[derive(Args)]
|
||||
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)
|
||||
#[arg(long, value_name = "offset", value_parser = |s: &str| maybe_hex_range::<u32>(s, 0x00000004, 0x03FF0000))]
|
||||
isv: Option<u32>,
|
||||
|
||||
/// Expose TCP socket port for GDB debugging
|
||||
#[arg(long, value_name = "port", value_parser = clap::value_parser!(u16).range(1..))]
|
||||
gdb: Option<u16>,
|
||||
/// Use EUC-JP encoding for text printing
|
||||
#[arg(long)]
|
||||
euc_jp: bool,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
@ -214,6 +217,7 @@ enum SaveType {
|
||||
Eeprom16k,
|
||||
Sram,
|
||||
SramBanked,
|
||||
Sram1m,
|
||||
Flashram,
|
||||
}
|
||||
|
||||
@ -225,8 +229,8 @@ impl From<n64::SaveType> for SaveType {
|
||||
n64::SaveType::Eeprom16k => Self::Eeprom16k,
|
||||
n64::SaveType::Sram => Self::Sram,
|
||||
n64::SaveType::SramBanked => Self::SramBanked,
|
||||
n64::SaveType::Sram1m => Self::Sram1m,
|
||||
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::Sram => Self::Sram,
|
||||
SaveType::SramBanked => Self::SramBanked,
|
||||
SaveType::Sram1m => Self::Sram1m,
|
||||
SaveType::Flashram => Self::Flashram,
|
||||
}
|
||||
}
|
||||
@ -483,46 +488,51 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
|
||||
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!(
|
||||
"{}",
|
||||
"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();
|
||||
while !exit.load(Ordering::Relaxed) {
|
||||
if let Some(data_packet) = sc64.receive_data_packet()? {
|
||||
match data_packet {
|
||||
sc64::DataPacket::Disk(mut packet) => {
|
||||
let track = packet.info.track;
|
||||
let head = packet.info.head;
|
||||
let block = packet.info.block;
|
||||
sc64::DataPacket::DiskRequest(mut disk_packet) => {
|
||||
let track = disk_packet.info.track;
|
||||
let head = disk_packet.info.head;
|
||||
let block = disk_packet.info.block;
|
||||
if let Some(ref mut disk) = selected_disk {
|
||||
let reply_packet = match packet.kind {
|
||||
sc64::DiskPacketKind::Read => {
|
||||
print!("{}", "[R]".cyan());
|
||||
let (reply_packet, rw) = match disk_packet.kind {
|
||||
sc64::DiskPacketKind::Read => (
|
||||
disk.read_block(track, head, block)?.map(|data| {
|
||||
packet.info.set_data(&data);
|
||||
packet
|
||||
})
|
||||
}
|
||||
sc64::DiskPacketKind::Write => {
|
||||
print!("{}", "[W]".yellow());
|
||||
let data = &packet.info.data;
|
||||
disk.write_block(track, head, block, data)?.map(|_| packet)
|
||||
}
|
||||
disk_packet.info.set_data(&data);
|
||||
disk_packet
|
||||
}),
|
||||
"[R]".bright_blue(),
|
||||
),
|
||||
sc64::DiskPacketKind::Write => (
|
||||
disk.write_block(track, head, block, &disk_packet.info.data)?
|
||||
.map(|_| disk_packet),
|
||||
"[W]".bright_yellow(),
|
||||
),
|
||||
};
|
||||
let lba = if let Some(lba) = disk.get_lba(track, head, block) {
|
||||
format!("{lba}")
|
||||
} else {
|
||||
"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() {
|
||||
println!("{}", message.green());
|
||||
println!("{}: {} {}", "[64DD]".bold(), rw, message.green());
|
||||
} else {
|
||||
println!("{}", message.red());
|
||||
println!("{}: {} {}", "[64DD]".bold(), rw, message.red());
|
||||
}
|
||||
sc64.reply_disk_packet(reply_packet)?;
|
||||
} else {
|
||||
@ -533,7 +543,11 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
|
||||
if selected_disk.is_some() {
|
||||
sc64.set_64dd_disk_state(sc64::DdDiskState::Ejected)?;
|
||||
selected_disk = None;
|
||||
println!("64DD disk ejected [{}]", disk_names[selected_disk_index]);
|
||||
println!(
|
||||
"{}: Disk ejected [{}]",
|
||||
"[64DD]".bold(),
|
||||
disk_names[selected_disk_index].green()
|
||||
);
|
||||
} else {
|
||||
selected_disk_index += 1;
|
||||
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]);
|
||||
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(())
|
||||
}
|
||||
|
||||
fn handle_debug_command(connection: Connection, args: &DebugArgs) -> Result<(), sc64::Error> {
|
||||
let mut sc64 = init_sc64(connection, true)?;
|
||||
|
||||
let mut debug_handler = debug::new(args.gdb)?;
|
||||
if let Some(port) = args.gdb {
|
||||
println!("GDB TCP socket listening at [0.0.0.0:{port}]");
|
||||
let mut debug_handler = debug::new();
|
||||
|
||||
if args.euc_jp {
|
||||
debug_handler.set_text_encoding(debug::Encoding::EUCJP);
|
||||
}
|
||||
|
||||
if args.isv.is_some() {
|
||||
sc64.configure_is_viewer_64(args.isv)?;
|
||||
println!(
|
||||
"IS-Viewer 64 configured and listening at ROM offset [0x{:08X}]",
|
||||
args.isv.unwrap()
|
||||
"{}: Listening on ROM offset [{}]",
|
||||
"[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();
|
||||
while !exit.load(Ordering::Relaxed) {
|
||||
if let Some(data_packet) = sc64.receive_data_packet()? {
|
||||
match data_packet {
|
||||
sc64::DataPacket::IsViewer(message) => {
|
||||
print!("{message}")
|
||||
sc64::DataPacket::DebugData(debug_packet) => {
|
||||
debug_handler.handle_debug_packet(debug_packet);
|
||||
}
|
||||
sc64::DataPacket::Debug(debug_packet) => {
|
||||
debug_handler.handle_debug_packet(debug_packet)
|
||||
sc64::DataPacket::IsViewer64(message) => {
|
||||
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() {
|
||||
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() {
|
||||
sc64.configure_is_viewer_64(None)?;
|
||||
println!("{}: Stopped listening", "[IS-Viewer 64]".bold());
|
||||
}
|
||||
|
||||
println!("{}: Stopped", "[Debug]".bold());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -636,7 +660,6 @@ fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
|
||||
println!(" ROM write: {}", state.rom_write_enable);
|
||||
println!(" ROM shadow: {}", state.rom_shadow_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 SD card mode: {}", state.dd_sd_enable);
|
||||
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 state: {}", state.button_state);
|
||||
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!(" MCU stack usage: {}", state.mcu_stack_usage);
|
||||
|
||||
@ -693,7 +717,7 @@ fn handle_firmware_command(
|
||||
|
||||
let metadata = sc64::firmware::verify(&firmware)?;
|
||||
println!("{}", "Firmware metadata:".bold());
|
||||
println!("{}", format!("{}", metadata).cyan().to_string());
|
||||
println!("{}", format!("{}", metadata).bright_blue().to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -703,8 +727,6 @@ fn handle_firmware_command(
|
||||
|
||||
let (mut backup_file, backup_name) = create_file(&args.firmware)?;
|
||||
|
||||
sc64.reset_state()?;
|
||||
|
||||
let firmware = log_wait(
|
||||
format!("Generating firmware backup, this might take a while [{backup_name}]"),
|
||||
|| sc64.backup_firmware(),
|
||||
@ -712,7 +734,7 @@ fn handle_firmware_command(
|
||||
|
||||
let metadata = sc64::firmware::verify(&firmware)?;
|
||||
println!("{}", "Firmware metadata:".bold());
|
||||
println!("{}", format!("{}", metadata).cyan().to_string());
|
||||
println!("{}", format!("{}", metadata).bright_blue().to_string());
|
||||
|
||||
backup_file.write_all(&firmware)?;
|
||||
|
||||
@ -729,7 +751,7 @@ fn handle_firmware_command(
|
||||
|
||||
let metadata = sc64::firmware::verify(&firmware)?;
|
||||
println!("{}", "Firmware metadata:".bold());
|
||||
println!("{}", format!("{}", metadata).cyan().to_string());
|
||||
println!("{}", format!("{}", metadata).bright_blue().to_string());
|
||||
println!("{}", "Firmware file verification was successful".green());
|
||||
let answer = prompt(format!("{}", "Continue with update process? [y/N] ".bold()));
|
||||
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()
|
||||
);
|
||||
|
||||
sc64.reset_state()?;
|
||||
|
||||
log_wait(
|
||||
format!("Updating firmware, this might take a while [{update_name}]"),
|
||||
|| 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::ServerEvent::Listening(address) => {
|
||||
println!("{}: Listening on address [{}]", "[Server]".bold(), address)
|
||||
println!(
|
||||
"{}: Listening on address [{}]",
|
||||
"[Server]".bold(),
|
||||
address.bright_blue()
|
||||
)
|
||||
}
|
||||
sc64::ServerEvent::Connection(peer) => {
|
||||
println!("{}: New connection from [{}]", "[Server]".bold(), peer);
|
||||
sc64::ServerEvent::Connected(peer) => {
|
||||
println!(
|
||||
"{}: New connection from [{}]",
|
||||
"[Server]".bold(),
|
||||
peer.bright_green()
|
||||
);
|
||||
}
|
||||
sc64::ServerEvent::Disconnected(peer) => {
|
||||
println!("{}: Client disconnected [{}]", "[Server]".bold(), peer);
|
||||
println!(
|
||||
"{}: Client disconnected [{}]",
|
||||
"[Server]".bold(),
|
||||
peer.green()
|
||||
);
|
||||
}
|
||||
sc64::ServerEvent::Err(error) => {
|
||||
println!(
|
||||
"{}: Client disconnected with error: {}",
|
||||
"{}: Client disconnected - server error: {}",
|
||||
"[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();
|
||||
let result = operation();
|
||||
if result.is_ok() {
|
||||
println!("done");
|
||||
println!("{}", "done".bold().bright_green());
|
||||
} else {
|
||||
println!("error!");
|
||||
println!("{}", "error!".bold().bright_red());
|
||||
}
|
||||
result
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ pub enum SaveType {
|
||||
Sram,
|
||||
SramBanked,
|
||||
Flashram,
|
||||
Sram128kB,
|
||||
Sram1m,
|
||||
}
|
||||
|
||||
const HASH_CHUNK_LENGTH: usize = 1 * 1024 * 1024;
|
||||
@ -31,7 +31,7 @@ pub fn guess_save_type<T: Read + Seek>(
|
||||
3 => SaveType::Sram,
|
||||
4 => SaveType::SramBanked,
|
||||
5 => SaveType::Flashram,
|
||||
6 => SaveType::Sram128kB,
|
||||
6 => SaveType::Sram1m,
|
||||
_ => SaveType::None,
|
||||
},
|
||||
None,
|
||||
|
@ -17,7 +17,7 @@ impl std::error::Error for Error {}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
write!(f, "SC64 error: {}", self.description.as_str())
|
||||
write!(f, "{}", self.description.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
use super::error::Error;
|
||||
use serial2::SerialPort;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
io::{BufRead, BufReader, BufWriter, ErrorKind, Read, Write},
|
||||
net::{TcpListener, TcpStream},
|
||||
io::{BufReader, BufWriter, ErrorKind, Read, Write},
|
||||
net::TcpStream,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
enum DataType {
|
||||
pub enum DataType {
|
||||
Command,
|
||||
Response,
|
||||
Packet,
|
||||
@ -37,10 +39,10 @@ impl TryFrom<u32> for DataType {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Command<'a> {
|
||||
pub struct Command {
|
||||
pub id: u8,
|
||||
pub args: [u32; 2],
|
||||
pub data: &'a [u8],
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct Response {
|
||||
@ -54,29 +56,20 @@ pub struct Packet {
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
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>;
|
||||
pub struct Serial {
|
||||
serial: SerialPort,
|
||||
}
|
||||
|
||||
struct SerialBackend {
|
||||
serial: Box<dyn serialport::SerialPort>,
|
||||
}
|
||||
|
||||
impl SerialBackend {
|
||||
fn reset(&mut self) -> Result<(), Error> {
|
||||
impl Serial {
|
||||
fn reset(&self) -> Result<(), Error> {
|
||||
const WAIT_DURATION: Duration = Duration::from_millis(10);
|
||||
const RETRY_COUNT: i32 = 100;
|
||||
|
||||
self.serial.write_data_terminal_ready(true)?;
|
||||
self.serial.set_dtr(true)?;
|
||||
for n in 0..=RETRY_COUNT {
|
||||
self.serial.clear(serialport::ClearBuffer::All)?;
|
||||
std::thread::sleep(WAIT_DURATION);
|
||||
if self.serial.read_data_set_ready()? {
|
||||
self.serial.discard_buffers()?;
|
||||
thread::sleep(WAIT_DURATION);
|
||||
if self.serial.read_dsr()? {
|
||||
break;
|
||||
}
|
||||
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 {
|
||||
std::thread::sleep(WAIT_DURATION);
|
||||
if !self.serial.read_data_set_ready()? {
|
||||
thread::sleep(WAIT_DURATION);
|
||||
if !self.serial.read_dsr()? {
|
||||
break;
|
||||
}
|
||||
if n == RETRY_COUNT {
|
||||
@ -97,10 +90,44 @@ impl SerialBackend {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_data(&self, buffer: &mut [u8], block: bool) -> Result<Option<()>, 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(()))
|
||||
}
|
||||
|
||||
impl Backend for SerialBackend {
|
||||
fn send_command(&mut self, command: &Command) -> Result<(), Error> {
|
||||
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(&command.id.to_be_bytes())?;
|
||||
self.serial.write_all(&command.args[0].to_be_bytes())?;
|
||||
@ -113,30 +140,28 @@ impl Backend for SerialBackend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_incoming_data(
|
||||
&mut self,
|
||||
pub fn process_incoming_data(
|
||||
&self,
|
||||
data_type: DataType,
|
||||
packets: &mut VecDeque<Packet>,
|
||||
) -> Result<Option<Response>, Error> {
|
||||
let mut buffer = [0u8; 4];
|
||||
|
||||
while matches!(data_type, DataType::Response)
|
||||
|| self.serial.bytes_to_read()? as usize >= buffer.len()
|
||||
{
|
||||
self.serial.read_exact(&mut buffer)?;
|
||||
let (packet_token, error) = (match &buffer[0..3] {
|
||||
let block = matches!(data_type, DataType::Response);
|
||||
while let Some(header) = self.read_header(block)? {
|
||||
let (packet_token, error) = (match &header[0..3] {
|
||||
b"CMP" => Ok((false, false)),
|
||||
b"PKT" => Ok((true, false)),
|
||||
b"ERR" => Ok((false, true)),
|
||||
_ => Err(Error::new("Unknown response token")),
|
||||
})?;
|
||||
let id = buffer[3];
|
||||
let 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 mut data = vec![0u8; length];
|
||||
self.serial.read_exact(&mut data)?;
|
||||
self.read_exact(&mut data)?;
|
||||
|
||||
if packet_token {
|
||||
packets.push_back(Packet { id, data });
|
||||
@ -152,37 +177,89 @@ impl Backend for SerialBackend {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_serial_backend(port: &str) -> Result<SerialBackend, Error> {
|
||||
let serial = serialport::new(port, 115_200)
|
||||
.timeout(Duration::from_secs(10))
|
||||
.open()?;
|
||||
let mut backend = SerialBackend { serial };
|
||||
pub fn new_serial(port: &str) -> Result<Serial, Error> {
|
||||
let mut serial = SerialPort::open(port, 115_200)?;
|
||||
serial.set_write_timeout(Duration::from_secs(10))?;
|
||||
serial.set_read_timeout(Duration::from_millis(10))?;
|
||||
let backend = Serial { serial };
|
||||
backend.reset()?;
|
||||
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 {
|
||||
stream: TcpStream,
|
||||
reader: BufReader<TcpStream>,
|
||||
writer: BufWriter<TcpStream>,
|
||||
}
|
||||
|
||||
impl TcpBackend {
|
||||
fn bytes_to_read(&mut self) -> Result<usize, Error> {
|
||||
self.stream.set_nonblocking(true)?;
|
||||
let result = self.reader.fill_buf();
|
||||
let length = match result {
|
||||
Ok(buffer) => buffer.len(),
|
||||
Err(error) => {
|
||||
if error.kind() == ErrorKind::WouldBlock {
|
||||
0
|
||||
} else {
|
||||
return Err(error.into());
|
||||
fn read_data(&mut self, buffer: &mut [u8], block: bool) -> Result<Option<()>, 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("Stream read timeout"));
|
||||
}
|
||||
match self.reader.read(&mut buffer[position..length]) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
self.stream.set_nonblocking(false)?;
|
||||
return Ok(length);
|
||||
_ => return Err(error.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
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,
|
||||
packets: &mut VecDeque<Packet>,
|
||||
) -> 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];
|
||||
|
||||
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 {
|
||||
DataType::Response => {
|
||||
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 mut data = vec![0u8; response_data_length];
|
||||
self.reader.read_exact(&mut data)?;
|
||||
self.read_exact(&mut data)?;
|
||||
|
||||
return Ok(Some(Response {
|
||||
id: response_info[0],
|
||||
@ -234,13 +309,13 @@ impl Backend for TcpBackend {
|
||||
}
|
||||
DataType::Packet => {
|
||||
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 mut data = vec![0u8; packet_data_length];
|
||||
self.reader.read_exact(&mut data)?;
|
||||
self.read_exact(&mut data)?;
|
||||
|
||||
packets.push_back(Packet {
|
||||
id: packet_info[0],
|
||||
@ -263,7 +338,7 @@ fn new_tcp_backend(address: &str) -> Result<TcpBackend, Error> {
|
||||
let stream = match TcpStream::connect(address) {
|
||||
Ok(stream) => {
|
||||
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
|
||||
}
|
||||
Err(error) => {
|
||||
@ -274,11 +349,7 @@ fn new_tcp_backend(address: &str) -> Result<TcpBackend, Error> {
|
||||
};
|
||||
let reader = BufReader::new(stream.try_clone()?);
|
||||
let writer = BufWriter::new(stream.try_clone()?);
|
||||
Ok(TcpBackend {
|
||||
stream,
|
||||
reader,
|
||||
writer,
|
||||
})
|
||||
Ok(TcpBackend { reader, writer })
|
||||
}
|
||||
|
||||
pub struct Link {
|
||||
@ -383,123 +454,3 @@ pub fn list_local_devices() -> Result<Vec<LocalDevice>, Error> {
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,26 +2,28 @@ mod cic;
|
||||
mod error;
|
||||
pub mod firmware;
|
||||
mod link;
|
||||
mod server;
|
||||
mod time;
|
||||
mod types;
|
||||
mod utils;
|
||||
|
||||
pub use self::{
|
||||
error::Error,
|
||||
link::{list_local_devices, ServerEvent},
|
||||
link::list_local_devices,
|
||||
server::ServerEvent,
|
||||
types::{
|
||||
BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState, DdDriveType, DdMode,
|
||||
DebugPacket, DiskPacket, DiskPacketKind, FpgaDebugData, McuStackUsage, SaveType, Switch,
|
||||
TvType,
|
||||
DebugPacket, DiskPacket, DiskPacketKind, FpgaDebugData, McuStackUsage, SaveType,
|
||||
SaveWriteback, Switch, TvType,
|
||||
},
|
||||
};
|
||||
|
||||
use self::{
|
||||
cic::{calculate_ipl3_checksum, guess_ipl3_seed, IPL3_LENGTH, IPL3_OFFSET},
|
||||
link::{Command, Link},
|
||||
time::{convert_from_datetime, convert_to_datetime},
|
||||
types::{
|
||||
get_config, get_setting, Config, ConfigId, FirmwareStatus, Setting, SettingId, UpdateStatus,
|
||||
},
|
||||
utils::{convert_from_datetime, convert_to_datetime},
|
||||
};
|
||||
use chrono::{DateTime, Local};
|
||||
use std::{
|
||||
@ -81,8 +83,9 @@ const EEPROM_ADDRESS: u32 = 0x0500_2000;
|
||||
const EEPROM_4K_LENGTH: usize = 512;
|
||||
const EEPROM_16K_LENGTH: usize = 2 * 1024;
|
||||
const SRAM_LENGTH: usize = 32 * 1024;
|
||||
const SRAM_BANKED_LENGTH: usize = 3 * 32 * 1024;
|
||||
const FLASHRAM_LENGTH: usize = 128 * 1024;
|
||||
const SRAM_BANKED_LENGTH: usize = 3 * 32 * 1024;
|
||||
const SRAM_1M_LENGTH: usize = 128 * 1024;
|
||||
|
||||
const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000;
|
||||
|
||||
@ -100,7 +103,7 @@ impl SC64 {
|
||||
let data = self.link.execute_command(&Command {
|
||||
id: b'v',
|
||||
args: [0, 0],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
if data.len() != 4 {
|
||||
return Err(Error::new(
|
||||
@ -114,7 +117,7 @@ impl SC64 {
|
||||
let data = self.link.execute_command(&Command {
|
||||
id: b'V',
|
||||
args: [0, 0],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
if data.len() != 8 {
|
||||
return Err(Error::new(
|
||||
@ -131,7 +134,7 @@ impl SC64 {
|
||||
self.link.execute_command(&Command {
|
||||
id: b'R',
|
||||
args: [0, 0],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@ -149,7 +152,7 @@ impl SC64 {
|
||||
self.link.execute_command(&Command {
|
||||
id: b'B',
|
||||
args,
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@ -158,7 +161,7 @@ impl SC64 {
|
||||
let data = self.link.execute_command(&Command {
|
||||
id: b'c',
|
||||
args: [config_id.into(), 0],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
if data.len() != 4 {
|
||||
return Err(Error::new(
|
||||
@ -173,7 +176,7 @@ impl SC64 {
|
||||
self.link.execute_command(&Command {
|
||||
id: b'C',
|
||||
args: config.into(),
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@ -182,7 +185,7 @@ impl SC64 {
|
||||
let data = self.link.execute_command(&Command {
|
||||
id: b'a',
|
||||
args: [setting_id.into(), 0],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
if data.len() != 4 {
|
||||
return Err(Error::new(
|
||||
@ -197,7 +200,7 @@ impl SC64 {
|
||||
self.link.execute_command(&Command {
|
||||
id: b'A',
|
||||
args: setting.into(),
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@ -206,7 +209,7 @@ impl SC64 {
|
||||
let data = self.link.execute_command(&Command {
|
||||
id: b't',
|
||||
args: [0, 0],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
if data.len() != 8 {
|
||||
return Err(Error::new(
|
||||
@ -220,7 +223,7 @@ impl SC64 {
|
||||
self.link.execute_command(&Command {
|
||||
id: b'T',
|
||||
args: convert_from_datetime(datetime),
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@ -229,7 +232,7 @@ impl SC64 {
|
||||
let data = self.link.execute_command(&Command {
|
||||
id: b'm',
|
||||
args: [address, length as u32],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
if data.len() != length {
|
||||
return Err(Error::new(
|
||||
@ -243,7 +246,7 @@ impl SC64 {
|
||||
self.link.execute_command(&Command {
|
||||
id: b'M',
|
||||
args: [address, data.len() as u32],
|
||||
data,
|
||||
data: data.to_vec(),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@ -253,7 +256,7 @@ impl SC64 {
|
||||
&Command {
|
||||
id: b'U',
|
||||
args: [datatype as u32, data.len() as u32],
|
||||
data,
|
||||
data: data.to_vec(),
|
||||
},
|
||||
true,
|
||||
false,
|
||||
@ -265,7 +268,16 @@ impl SC64 {
|
||||
self.link.execute_command(&Command {
|
||||
id: b'D',
|
||||
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(())
|
||||
}
|
||||
@ -274,7 +286,7 @@ impl SC64 {
|
||||
let data = self.link.execute_command(&Command {
|
||||
id: b'p',
|
||||
args: [wait as u32, 0],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
if data.len() != 4 {
|
||||
return Err(Error::new(
|
||||
@ -289,7 +301,7 @@ impl SC64 {
|
||||
self.link.execute_command(&Command {
|
||||
id: b'P',
|
||||
args: [address, 0],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@ -299,7 +311,7 @@ impl SC64 {
|
||||
&Command {
|
||||
id: b'f',
|
||||
args: [address, 0],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
},
|
||||
false,
|
||||
true,
|
||||
@ -323,7 +335,7 @@ impl SC64 {
|
||||
&Command {
|
||||
id: b'F',
|
||||
args: [address, length as u32],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
},
|
||||
false,
|
||||
true,
|
||||
@ -340,7 +352,7 @@ impl SC64 {
|
||||
let data = self.link.execute_command(&Command {
|
||||
id: b'?',
|
||||
args: [0, 0],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
Ok(data.try_into()?)
|
||||
}
|
||||
@ -349,7 +361,7 @@ impl SC64 {
|
||||
let data = self.link.execute_command(&Command {
|
||||
id: b'%',
|
||||
args: [0, 0],
|
||||
data: &[],
|
||||
data: vec![],
|
||||
})?;
|
||||
Ok(data.try_into()?)
|
||||
}
|
||||
@ -439,8 +451,9 @@ impl SC64 {
|
||||
SaveType::Eeprom4k => (EEPROM_ADDRESS, EEPROM_4K_LENGTH),
|
||||
SaveType::Eeprom16k => (EEPROM_ADDRESS, EEPROM_16K_LENGTH),
|
||||
SaveType::Sram => (SAVE_ADDRESS, SRAM_LENGTH),
|
||||
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_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 {
|
||||
@ -462,8 +475,9 @@ impl SC64 {
|
||||
SaveType::Eeprom4k => (EEPROM_ADDRESS, EEPROM_4K_LENGTH),
|
||||
SaveType::Eeprom16k => (EEPROM_ADDRESS, EEPROM_16K_LENGTH),
|
||||
SaveType::Sram => (SAVE_ADDRESS, SRAM_LENGTH),
|
||||
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_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)
|
||||
@ -580,6 +594,16 @@ impl SC64 {
|
||||
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> {
|
||||
if let Some(packet) = self.link.receive_packet()? {
|
||||
return Ok(Some(packet.try_into()?));
|
||||
@ -617,13 +641,15 @@ impl SC64 {
|
||||
}
|
||||
|
||||
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
|
||||
.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 {
|
||||
return Err(Error::new(
|
||||
"Unsupported SC64 firmware version, please update firmware",
|
||||
));
|
||||
return Err(Error::new(unsupported_version_message.as_str()));
|
||||
}
|
||||
Ok((major, minor, revision))
|
||||
}
|
||||
@ -784,5 +810,5 @@ pub fn run_server(
|
||||
} else {
|
||||
list_local_devices()?[0].port.clone()
|
||||
};
|
||||
link::run_server(&port, address, event_callback)
|
||||
server::run_server(&port, address, event_callback)
|
||||
}
|
||||
|
253
sw/deployer/src/sc64/server.rs
Normal file
253
sw/deployer/src/sc64/server.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
use super::{link::Packet, Error};
|
||||
use encoding_rs::EUC_JP;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@ -253,6 +252,7 @@ pub enum SaveType {
|
||||
Sram,
|
||||
Flashram,
|
||||
SramBanked,
|
||||
Sram1m,
|
||||
}
|
||||
|
||||
impl Display for SaveType {
|
||||
@ -261,9 +261,10 @@ impl Display for SaveType {
|
||||
Self::None => "None",
|
||||
Self::Eeprom4k => "EEPROM 4k",
|
||||
Self::Eeprom16k => "EEPROM 16k",
|
||||
Self::Sram => "SRAM",
|
||||
Self::SramBanked => "SRAM banked",
|
||||
Self::Flashram => "FlashRAM",
|
||||
Self::Sram => "SRAM 256k",
|
||||
Self::SramBanked => "SRAM 768k",
|
||||
Self::Flashram => "FlashRAM 1M",
|
||||
Self::Sram1m => "SRAM 1M",
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -278,6 +279,7 @@ impl TryFrom<u32> for SaveType {
|
||||
3 => Self::Sram,
|
||||
4 => Self::Flashram,
|
||||
5 => Self::SramBanked,
|
||||
6 => Self::Sram1m,
|
||||
_ => return Err(Error::new("Unknown save type code")),
|
||||
})
|
||||
}
|
||||
@ -292,6 +294,7 @@ impl From<SaveType> for u32 {
|
||||
SaveType::Sram => 3,
|
||||
SaveType::Flashram => 4,
|
||||
SaveType::SramBanked => 5,
|
||||
SaveType::Sram1m => 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -337,7 +340,7 @@ pub enum TvType {
|
||||
PAL,
|
||||
NTSC,
|
||||
MPAL,
|
||||
Auto,
|
||||
Passthrough,
|
||||
}
|
||||
|
||||
impl Display for TvType {
|
||||
@ -346,7 +349,7 @@ impl Display for TvType {
|
||||
Self::PAL => "PAL",
|
||||
Self::NTSC => "NTSC",
|
||||
Self::MPAL => "MPAL",
|
||||
Self::Auto => "Auto",
|
||||
Self::Passthrough => "Passthrough",
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -358,7 +361,7 @@ impl TryFrom<u32> for TvType {
|
||||
0 => Self::PAL,
|
||||
1 => Self::NTSC,
|
||||
2 => Self::MPAL,
|
||||
3 => Self::Auto,
|
||||
3 => Self::Passthrough,
|
||||
_ => return Err(Error::new("Unknown TV type code")),
|
||||
})
|
||||
}
|
||||
@ -370,7 +373,7 @@ impl From<TvType> for u32 {
|
||||
TvType::PAL => 0,
|
||||
TvType::NTSC => 1,
|
||||
TvType::MPAL => 2,
|
||||
TvType::Auto => 3,
|
||||
TvType::Passthrough => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -577,9 +580,10 @@ impl From<Setting> for [u32; 2] {
|
||||
|
||||
pub enum DataPacket {
|
||||
Button,
|
||||
Debug(DebugPacket),
|
||||
Disk(DiskPacket),
|
||||
IsViewer(String),
|
||||
DebugData(DebugPacket),
|
||||
DiskRequest(DiskPacket),
|
||||
IsViewer64(Vec<u8>),
|
||||
SaveWriteback(SaveWriteback),
|
||||
UpdateStatus(UpdateStatus),
|
||||
}
|
||||
|
||||
@ -588,9 +592,10 @@ impl TryFrom<Packet> for DataPacket {
|
||||
fn try_from(value: Packet) -> Result<Self, Self::Error> {
|
||||
Ok(match value.id {
|
||||
b'B' => Self::Button,
|
||||
b'U' => Self::Debug(value.data.try_into()?),
|
||||
b'D' => Self::Disk(value.data.try_into()?),
|
||||
b'I' => Self::IsViewer(EUC_JP.decode(&value.data).0.into()),
|
||||
b'U' => Self::DebugData(value.data.try_into()?),
|
||||
b'D' => Self::DiskRequest(value.data.try_into()?),
|
||||
b'I' => Self::IsViewer64(value.data),
|
||||
b'S' => Self::SaveWriteback(value.data.try_into()?),
|
||||
b'F' => {
|
||||
if value.data.len() != 4 {
|
||||
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 {
|
||||
Ok,
|
||||
ErrToken,
|
||||
@ -782,7 +806,10 @@ impl TryFrom<Vec<u8>> for FpgaDebugData {
|
||||
|
||||
impl Display for FpgaDebugData {
|
||||
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 {
|
||||
f.write_str(" RW")?;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user