[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 |
| `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 |

View File

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

View File

@ -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,11 +56,9 @@ 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)) {
info->tv_type = OS_INFO->tv_type;
}
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) {

View File

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

View File

@ -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,7 +47,21 @@ 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,
}};
static void display_decompress_background (uint32_t *background) {

View File

@ -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);
}

View File

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

View File

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

View File

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

View File

@ -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();

View File

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

View File

@ -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:
writeback_disable();
return;
*address = 0;
*length = 0;
break;
}
if(sd_optimize_sectors(address, p.sectors, count, sd_write_sectors)) {
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, 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;
writeback_save_to_sd();
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;
}
}
}

View File

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

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

View File

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

View File

@ -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() {
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");
let line = match self.line_rx.try_recv() {
Ok(line) => {
if line.len() == 0 {
return None;
}
let mut file = match File::open(path) {
Ok(file) => file,
Err(error) => {
println!("Couldn't open file: {error}");
return None;
}
};
let length = match file.metadata() {
Ok(metadata) => metadata.len(),
Err(error) => {
println!("Couldn't get file length: {error}");
return None;
}
};
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 {
datatype: DataType::RawBinary.into(),
data,
});
} 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 {
datatype: DataType::Text.into(),
data: combined_data,
});
line
}
}
Err(_) => return None,
};
let token_count = line.matches("@").count();
if (token_count % 2) != 0 {
return stop!(None, "Missing closing '@' token");
}
None
let packet = if token_count == 2 && line.starts_with("@") && line.ends_with("@") {
sc64::DebugPacket {
datatype: DataType::RawBinary.into(),
data: match load_file(line.trim_matches('@')) {
Ok(data) => data,
Err(error) => return stop!(None, "{error}"),
},
}
} else {
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,
}
};
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
);
}
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 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
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),
}
}
}
pub fn new(gdb_port: Option<u16>) -> Result<Handler, sc64::Error> {
pub fn new() -> Handler {
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 {
Handler {
header: None,
line_rx,
gdb_tx,
gdb_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")
)
}
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));
}
}

View File

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

View File

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

View File

@ -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())
}
}

View File

@ -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(())
}
}
impl Backend for SerialBackend {
fn send_command(&mut self, command: &Command) -> Result<(), Error> {
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(()))
}
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"));
}
};
self.stream.set_nonblocking(false)?;
return Ok(length);
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);
}
}
_ => 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 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()?;
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];
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));
}
}
}

View File

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

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 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")?;
}