diff --git a/docs/02_usb_commands.md b/docs/02_usb_commands.md index 0e26692..56d6cf3 100644 --- a/docs/02_usb_commands.md +++ b/docs/02_usb_commands.md @@ -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 | diff --git a/docs/04_config_options.md b/docs/04_config_options.md index 3c94e3c..c84efd4 100644 --- a/docs/04_config_options.md +++ b/docs/04_config_options.md @@ -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). --- diff --git a/sw/bootloader/src/boot.c b/sw/bootloader/src/boot.c index 5e29156..8ab0e5e 100644 --- a/sw/bootloader/src/boot.c +++ b/sw/bootloader/src/boot.c @@ -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) { diff --git a/sw/bootloader/src/boot.h b/sw/bootloader/src/boot.h index 07a7616..7563dfc 100644 --- a/sw/bootloader/src/boot.h +++ b/sw/bootloader/src/boot.h @@ -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 diff --git a/sw/bootloader/src/display.c b/sw/bootloader/src/display.c index 462d8ce..d67a34e 100644 --- a/sw/bootloader/src/display.c +++ b/sw/bootloader/src/display.c @@ -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) { diff --git a/sw/bootloader/src/main.c b/sw/bootloader/src/main.c index 4d1ede7..431cb2b 100644 --- a/sw/bootloader/src/main.c +++ b/sw/bootloader/src/main.c @@ -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); } diff --git a/sw/bootloader/src/sc64.h b/sw/bootloader/src/sc64.h index ae47463..030f43d 100644 --- a/sw/bootloader/src/sc64.h +++ b/sw/bootloader/src/sc64.h @@ -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 { diff --git a/sw/controller/src/cfg.c b/sw/controller/src/cfg.c index dcc1939..1ff6a66 100644 --- a/sw/controller/src/cfg.c +++ b/sw/controller/src/cfg.c @@ -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': diff --git a/sw/controller/src/cfg.h b/sw/controller/src/cfg.h index bd67d20..bc3611c 100644 --- a/sw/controller/src/cfg.h +++ b/sw/controller/src/cfg.h @@ -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; diff --git a/sw/controller/src/usb.c b/sw/controller/src/usb.c index 52d531c..e301056 100644 --- a/sw/controller/src/usb.c +++ b/sw/controller/src/usb.c @@ -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(); diff --git a/sw/controller/src/usb.h b/sw/controller/src/usb.h index 2e5273b..0819a46 100644 --- a/sw/controller/src/usb.h +++ b/sw/controller/src/usb.h @@ -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; diff --git a/sw/controller/src/writeback.c b/sw/controller/src/writeback.c index 0fd9485..39ded8e 100644 --- a/sw/controller/src/writeback.c +++ b/sw/controller/src/writeback.c @@ -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; + } } } diff --git a/sw/controller/src/writeback.h b/sw/controller/src/writeback.h index 41e038d..f1bff21 100644 --- a/sw/controller/src/writeback.h +++ b/sw/controller/src/writeback.h @@ -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); diff --git a/sw/deployer/Cargo.lock b/sw/deployer/Cargo.lock index 8fd0126..686903a 100644 --- a/sw/deployer/Cargo.lock +++ b/sw/deployer/Cargo.lock @@ -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" diff --git a/sw/deployer/Cargo.toml b/sw/deployer/Cargo.toml index 7758404..a08ec17 100644 --- a/sw/deployer/Cargo.toml +++ b/sw/deployer/Cargo.toml @@ -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 diff --git a/sw/deployer/src/debug.rs b/sw/deployer/src/debug.rs index cfa69d1..2e440c5 100644 --- a/sw/deployer/src/debug.rs +++ b/sw/deployer/src/debug.rs @@ -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>, line_rx: Receiver, - gdb_tx: Sender>, - gdb_rx: Receiver>, + encoding: Encoding, } enum DataType { @@ -24,7 +26,6 @@ enum DataType { RawBinary, Header, Screenshot, - GDB, Unknown, } @@ -35,7 +36,6 @@ impl From for DataType { 0x02 => Self::RawBinary, 0x03 => Self::Header, 0x04 => Self::Screenshot, - 0xDB => Self::GDB, _ => Self::Unknown, } } @@ -48,7 +48,6 @@ impl From for u8 { DataType::RawBinary => 0x02, DataType::Header => 0x03, DataType::Screenshot => 0x04, - DataType::GDB => 0xDB, DataType::Unknown => 0xFF, } } @@ -115,68 +114,103 @@ impl TryFrom> 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 { - if let Ok(line) = self.line_rx.try_recv() { - if line.len() == 0 { - return None; - } - let mut data: Vec = 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 = 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, + ) { + 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 { - 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) -> Result { +pub fn new() -> Handler { let (line_tx, line_rx) = channel::(); - let (gdb_tx, gdb_loop_rx) = channel::>(); - let (gdb_loop_tx, gdb_rx) = channel::>(); - 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, 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) { 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>, gdb_rx: Receiver>) { - 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>, gdb_rx: Receiver>) { - 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>, - gdb_rx: &Receiver>, -) { - 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)); - } -} diff --git a/sw/deployer/src/main.rs b/sw/deployer/src/main.rs index a1c7aff..770c7e3 100644 --- a/sw/deployer/src/main.rs +++ b/sw/deployer/src/main.rs @@ -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, + /// 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::(s, 0x00000004, 0x03FF0000))] isv: Option, - /// Expose TCP socket port for GDB debugging - #[arg(long, value_name = "port", value_parser = clap::value_parser!(u16).range(1..))] - gdb: Option, + /// 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 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 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 Result, 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 } diff --git a/sw/deployer/src/n64.rs b/sw/deployer/src/n64.rs index 63537a6..aa8ff7d 100644 --- a/sw/deployer/src/n64.rs +++ b/sw/deployer/src/n64.rs @@ -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( 3 => SaveType::Sram, 4 => SaveType::SramBanked, 5 => SaveType::Flashram, - 6 => SaveType::Sram128kB, + 6 => SaveType::Sram1m, _ => SaveType::None, }, None, diff --git a/sw/deployer/src/sc64/error.rs b/sw/deployer/src/sc64/error.rs index f96444f..416bebb 100644 --- a/sw/deployer/src/sc64/error.rs +++ b/sw/deployer/src/sc64/error.rs @@ -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()) } } diff --git a/sw/deployer/src/sc64/link.rs b/sw/deployer/src/sc64/link.rs index cbdee88..0f489be 100644 --- a/sw/deployer/src/sc64/link.rs +++ b/sw/deployer/src/sc64/link.rs @@ -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 for DataType { } } -pub struct Command<'a> { +pub struct Command { pub id: u8, pub args: [u32; 2], - pub data: &'a [u8], + pub data: Vec, } pub struct Response { @@ -54,29 +56,20 @@ pub struct Packet { pub data: Vec, } -trait Backend { - fn send_command(&mut self, command: &Command) -> Result<(), Error>; - fn process_incoming_data( - &mut self, - data_type: DataType, - packets: &mut VecDeque, - ) -> Result, Error>; +pub struct Serial { + serial: SerialPort, } -struct SerialBackend { - serial: Box, -} - -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, 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, 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, ) -> Result, 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 { - 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 { + 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, + ) -> Result, 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, + ) -> Result, Error> { + self.inner.process_incoming_data(data_type, packets) + } +} + +fn new_serial_backend(port: &str) -> Result { + let backend = SerialBackend { + inner: new_serial(port)?, + }; + Ok(backend) +} + struct TcpBackend { - stream: TcpStream, reader: BufReader, writer: BufWriter, } impl TcpBackend { - fn bytes_to_read(&mut self) -> Result { - 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, 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, 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, ) -> Result, 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 { 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 { }; 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, 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 = 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)); - } - } -} diff --git a/sw/deployer/src/sc64/mod.rs b/sw/deployer/src/sc64/mod.rs index c5363b7..fce3d3c 100644 --- a/sw/deployer/src/sc64/mod.rs +++ b/sw/deployer/src/sc64/mod.rs @@ -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, 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) } diff --git a/sw/deployer/src/sc64/server.rs b/sw/deployer/src/sc64/server.rs new file mode 100644 index 0000000..091a479 --- /dev/null +++ b/sw/deployer/src/sc64/server.rs @@ -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), +} + +fn server_accept_connection(port: &str, stream: &mut TcpStream) -> Result<(), Error> { + let (event_sender, event_receiver) = channel::(); + 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, + serial_writer: Arc, + event_receiver: Receiver, +) -> 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, + exit_flag: Arc, +) -> 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, + event_sender: Sender, + exit_flag: Arc, +) -> Result<(), Error> { + let mut packets: VecDeque = 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, exit_flag: Arc) { + 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)); + } + } +} diff --git a/sw/deployer/src/sc64/utils.rs b/sw/deployer/src/sc64/time.rs similarity index 100% rename from sw/deployer/src/sc64/utils.rs rename to sw/deployer/src/sc64/time.rs diff --git a/sw/deployer/src/sc64/types.rs b/sw/deployer/src/sc64/types.rs index 2b9e625..3d0275d 100644 --- a/sw/deployer/src/sc64/types.rs +++ b/sw/deployer/src/sc64/types.rs @@ -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 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 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 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 for u32 { TvType::PAL => 0, TvType::NTSC => 1, TvType::MPAL => 2, - TvType::Auto => 3, + TvType::Passthrough => 3, } } } @@ -577,9 +580,10 @@ impl From for [u32; 2] { pub enum DataPacket { Button, - Debug(DebugPacket), - Disk(DiskPacket), - IsViewer(String), + DebugData(DebugPacket), + DiskRequest(DiskPacket), + IsViewer64(Vec), + SaveWriteback(SaveWriteback), UpdateStatus(UpdateStatus), } @@ -588,9 +592,10 @@ impl TryFrom for DataPacket { fn try_from(value: Packet) -> Result { 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, +} + +impl TryFrom> for SaveWriteback { + type Error = Error; + fn try_from(value: Vec) -> Result { + 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> 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")?; }