diff --git a/Makefile b/Makefile index e9e8ce00..0d52250c 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ SRCS = \ menu/rom_database.c \ menu/settings.c \ menu/sound.c \ + menu/usb_comm.c \ menu/views/browser.c \ menu/views/credits.c \ menu/views/error.c \ diff --git a/localdeploy.bat b/localdeploy.bat index f660783e..4f33fff4 100644 --- a/localdeploy.bat +++ b/localdeploy.bat @@ -21,8 +21,14 @@ echo !!! Now toggle power to the N64 !!! echo: echo: -if not "%1" == "/d" goto :exit +if not "%1" == "/d" goto :not_d %~dp0tools\sc64\sc64deployer debug --no-writeback -:exit +:not_d + +if not "%1" == "/du" goto :not_du + +%~dp0tools\sc64\sc64deployer debug --no-writeback --init "send-file /sc64menu.n64 @output/sc64menu.n64@;reboot" + +:not_du diff --git a/remotedeploy.sh b/remotedeploy.sh index 07a8036b..63504902 100755 --- a/remotedeploy.sh +++ b/remotedeploy.sh @@ -4,28 +4,12 @@ set -e REMOTE="--remote ${REMOTE:-host.docker.internal:9064}" -## FIXME: this does not work! -# Make sure we are connected -#echo Detecting SC64... -#sc64deployer $REMOTE list - -# Get the information -echo SC64 info...: -sc64deployer $REMOTE info -echo -echo - -# Load the ROM -echo Loading ROM...: sc64deployer $REMOTE upload ./output/N64FlashcartMenu.n64 -echo -echo -# Toggle the power of the N64 to boot the ROM. -echo !!! Now toggle power to the N64 !!! -echo -echo - if [ "$1" = "-d" ]; then sc64deployer $REMOTE debug --no-writeback fi + +if [ "$1" = "-du" ]; then + sc64deployer $REMOTE debug --no-writeback --init "send-file /sc64menu.n64 @output/sc64menu.n64@;reboot" +fi diff --git a/src/flashcart/flashcart.c b/src/flashcart/flashcart.c index 81052ca6..fe42ad93 100644 --- a/src/flashcart/flashcart.c +++ b/src/flashcart/flashcart.c @@ -11,7 +11,7 @@ #include "sc64/sc64.h" -#define WRITEBACK_MAX_SECTORS (256) +#define SAVE_WRITEBACK_MAX_SECTORS (256) static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = { @@ -24,6 +24,9 @@ static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = { KiB(128), }; +static uint32_t save_writeback_sectors[SAVE_WRITEBACK_MAX_SECTORS] __attribute__((aligned(8))); + + static flashcart_error_t dummy_init (void) { return FLASHCART_OK; } @@ -95,6 +98,7 @@ flashcart_error_t flashcart_deinit (void) { if (flashcart->deinit) { return flashcart->deinit(); } + return FLASHCART_OK; } @@ -124,9 +128,21 @@ flashcart_error_t flashcart_load_file (char *file_path, uint32_t rom_offset, uin return flashcart->load_file(file_path, rom_offset, file_offset); } +static void save_writeback_sectors_callback (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size) { + for (uint32_t i = 0; i < cluster_size; i++) { + uint32_t offset = file_sector + i; + uint32_t sector = cluster_sector + i; + + if ((offset > SAVE_WRITEBACK_MAX_SECTORS) || (offset > sector_count)) { + return; + } + + save_writeback_sectors[offset] = sector; + } +} + flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type) { flashcart_error_t error; - uint32_t sectors[WRITEBACK_MAX_SECTORS] __attribute__((aligned(8))); if (save_type >= __FLASHCART_SAVE_TYPE_END) { return FLASHCART_ERROR_ARGS; @@ -158,10 +174,13 @@ flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t sa } if (flashcart->set_save_writeback) { - if (file_get_sectors(save_path, sectors, WRITEBACK_MAX_SECTORS)) { + for (int i = 0; i < SAVE_WRITEBACK_MAX_SECTORS; i++) { + save_writeback_sectors[i] = 0; + } + if (file_get_sectors(save_path, save_writeback_sectors_callback)) { return FLASHCART_ERROR_LOAD; } - if ((error = flashcart->set_save_writeback(sectors)) != FLASHCART_OK) { + if ((error = flashcart->set_save_writeback(save_writeback_sectors)) != FLASHCART_OK) { return error; } } diff --git a/src/flashcart/sc64/sc64.c b/src/flashcart/sc64/sc64.c index 9cc554c4..716a46f3 100644 --- a/src/flashcart/sc64/sc64.c +++ b/src/flashcart/sc64/sc64.c @@ -85,7 +85,7 @@ static flashcart_error_t sc64_init (void) { uint32_t value; } default_config[] = { { CFG_ID_BOOTLOADER_SWITCH, false }, - { CFG_ID_ROM_WRITE_ENABLE, false }, + { CFG_ID_ROM_WRITE_ENABLE, true }, { CFG_ID_ROM_SHADOW_ENABLE, false }, { CFG_ID_DD_MODE, DD_MODE_DISABLED }, { CFG_ID_ISV_ADDRESS, 0x00000000 }, @@ -110,6 +110,8 @@ static flashcart_error_t sc64_init (void) { } static flashcart_error_t sc64_deinit (void) { + sc64_ll_set_config(CFG_ID_ROM_WRITE_ENABLE, false); + sc64_ll_lock(); return FLASHCART_OK; diff --git a/src/menu/menu.c b/src/menu/menu.c index f728d12e..3ce41cf1 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -14,6 +14,7 @@ #include "png_decoder.h" #include "settings.h" #include "sound.h" +#include "usb_comm.h" #include "utils/fs.h" #include "views/views.h" @@ -180,6 +181,8 @@ void menu_run (boot_params_t *boot_params) { sound_poll(); png_decoder_poll(); + + usb_comm_poll(menu); } menu_deinit(menu); diff --git a/src/menu/usb_comm.c b/src/menu/usb_comm.c new file mode 100644 index 00000000..5f81d457 --- /dev/null +++ b/src/menu/usb_comm.c @@ -0,0 +1,163 @@ +// NOTE: This code doesn't implement EverDrive-64 USB protocol. +// Main use of these functions is to aid menu development +// (for example replace files on the SD card or reboot menu). + +#include +#include +#include + +#include "usb_comm.h" +#include "utils/utils.h" + + +#define MAX_FILE_SIZE MiB(4) + + +typedef struct { + const char *id; + void (*op) (menu_t *menu); +} usb_comm_command_t; + + +static int usb_comm_get_char (void) { + char c; + + if (USBHEADER_GETSIZE(usb_poll()) <= 0) { + return -1; + } + + usb_read(&c, sizeof(c)); + + return (int) (c); +} + +static bool usb_comm_read_string (char *string, int length, char end) { + for (int i = 0; i < length; i++) { + int c = usb_comm_get_char(); + + if (c < 0) { + return true; + } + + string[i] = (char) (c); + + if (c == '\0' || c == end) { + string[i] = '\0'; + break; + } + + if (i == (length - 1)) { + return true; + } + } + + return false; +} + +static void usb_comm_send_error (const char *message) { + usb_purge(); + usb_write(DATATYPE_TEXT, message, strlen(message)); +} + + +static void command_reboot (menu_t *menu) { + menu->next_mode = MENU_MODE_BOOT; + + menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM; + menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH; + menu->boot_params->detect_cic_seed = true; +}; + +static void command_send_file (menu_t *menu) { + char path[256]; + char length[8]; + + FIL f; + int remaining; + uint8_t data[8192]; + UINT bytes_written; + + if (usb_comm_read_string(path, sizeof(path), ' ')) { + return usb_comm_send_error("Invalid path argument\n"); + } + + if (usb_comm_get_char() != '@') { + return usb_comm_send_error("Invalid argument\n"); + } + + if (usb_comm_read_string(length, sizeof(length), '@')) { + return usb_comm_send_error("Invalid file length argument\n"); + } + + if (f_open(&f, path, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK) { + return usb_comm_send_error("Couldn't create file\n"); + } + + remaining = atoi(length); + + if (remaining > MAX_FILE_SIZE) { + return usb_comm_send_error("File size too big\n"); + } + + while (remaining > 0) { + int block_size = MIN(remaining, sizeof(data)); + usb_read(data, block_size); + if (f_write(&f, data, block_size, &bytes_written) != FR_OK) { + f_close(&f); + return usb_comm_send_error("Couldn't write data to the file\n"); + } + if (bytes_written != block_size) { + f_close(&f); + return usb_comm_send_error("Couldn't write all required data to the file\n"); + } + remaining -= block_size; + } + + if (f_close(&f) != FR_OK) { + return usb_comm_send_error("Couldn't flush data to the file\n"); + } + + if (usb_comm_get_char() != '\0') { + return usb_comm_send_error("Invalid token at the end of data stream\n"); + } +} + +static usb_comm_command_t commands[] = { + { .id = "reboot", .op = command_reboot }, + { .id = "send-file", .op = command_send_file }, + { .id = NULL }, +}; + + +void usb_comm_poll (menu_t *menu) { + uint32_t header = usb_poll(); + + if (USBHEADER_GETTYPE(header) != DATATYPE_TEXT) { + usb_purge(); + return; + } + + if (USBHEADER_GETSIZE(header) > 0) { + char cmd_id[32]; + + if (usb_comm_read_string(cmd_id, sizeof(cmd_id), ' ')) { + usb_comm_send_error("Command id too long\n"); + } else { + usb_comm_command_t *cmd = commands; + + while (cmd->id != NULL) { + if (strcmp(cmd->id, cmd_id) == 0) { + cmd->op(menu); + break; + } + cmd++; + } + + usb_purge(); + + if (cmd->id == NULL) { + usb_comm_send_error("Unknown command\n"); + } + } + } +} diff --git a/src/menu/usb_comm.h b/src/menu/usb_comm.h new file mode 100644 index 00000000..c7f9a9ed --- /dev/null +++ b/src/menu/usb_comm.h @@ -0,0 +1,21 @@ +/** + * @file usb_comm.h + * @brief USB communication subsystem + * @ingroup menu + */ + +#ifndef USB_COMM_H__ +#define USB_COMM_H__ + + +#include "menu_state.h" + + +#ifndef NDEBUG +void usb_comm_poll (menu_t *menu); +#else +#define usb_comm_poll(menu) +#endif + + +#endif diff --git a/src/utils/fs.c b/src/utils/fs.c index 19c85814..648ecfdf 100644 --- a/src/utils/fs.c +++ b/src/utils/fs.c @@ -13,7 +13,7 @@ char *strip_sd_prefix (char *path) { char *found = strstr(path, prefix); if (found) { - return found + strlen(prefix); + return found + strlen(prefix) - 1; } return path; @@ -106,13 +106,13 @@ bool file_fill (char *path, uint8_t value) { return error; } -bool file_get_sectors (char *path, uint32_t *sectors, size_t entries) { +bool file_get_sectors (char *path, void (*callback) (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size)) { FATFS *fs; FIL fil; bool error = false; - for (int i = 0; i < entries; i++) { - sectors[i] = 0; + if (!callback) { + return true; } if (f_open(&fil, strip_sd_prefix(path), FA_READ) != FR_OK) { @@ -121,25 +121,22 @@ bool file_get_sectors (char *path, uint32_t *sectors, size_t entries) { fs = fil.obj.fs; - uint32_t file_sector_entries = (ALIGN(f_size(&fil), FS_SECTOR_SIZE) / FS_SECTOR_SIZE); - file_sector_entries = (file_sector_entries > entries) ? entries : file_sector_entries; + uint32_t sector_count = (ALIGN(f_size(&fil), FS_SECTOR_SIZE) / FS_SECTOR_SIZE); uint32_t cluster_sector = 0; - for (int file_sector = 0; file_sector < file_sector_entries; file_sector++) { - if ((file_sector % fs->csize) == 0) { - if ((f_lseek(&fil, (file_sector * FS_SECTOR_SIZE) + (FS_SECTOR_SIZE / 2))) != FR_OK) { - error = true; - break; - } - uint32_t cluster = fil.clust; - if (cluster >= fs->n_fatent) { - error = true; - break; - } - cluster_sector = (fs->database + ((LBA_t) (fs->csize) * (cluster - 2))); + for (int file_sector = 0; file_sector < sector_count; file_sector += fs->csize) { + if ((f_lseek(&fil, (file_sector * FS_SECTOR_SIZE) + (FS_SECTOR_SIZE / 2))) != FR_OK) { + error = true; + break; } - *sectors++ = (cluster_sector + (file_sector % fs->csize)); + uint32_t cluster = fil.clust; + if (cluster >= fs->n_fatent) { + error = true; + break; + } + cluster_sector = (fs->database + ((LBA_t) (fs->csize) * (cluster - 2))); + callback(sector_count, file_sector, cluster_sector, fs->csize); } if (f_close(&fil) != FR_OK) { diff --git a/src/utils/fs.h b/src/utils/fs.h index 25154437..7661ecb0 100644 --- a/src/utils/fs.h +++ b/src/utils/fs.h @@ -17,7 +17,7 @@ size_t file_get_size (char *path); bool file_delete (char *path); bool file_allocate (char *path, size_t size); bool file_fill (char *path, uint8_t value); -bool file_get_sectors (char *path, uint32_t *sectors, size_t entries); +bool file_get_sectors (char *path, void (*callback) (uint32_t sector_count, uint32_t file_sector, uint32_t cluster_sector, uint32_t cluster_size)); bool file_has_extensions (char *path, const char *extensions[]); bool directory_exists (char *path);