From fc2f3aa42cf750d55075b2e5846ed57e952099c8 Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Fri, 8 Sep 2023 21:49:50 +0200 Subject: [PATCH] 64drive support (#27) ## Description This PR implements 64drive support ## Motivation and Context Goal of this PR is to support more devices ## How Has This Been Tested? Not tested at all ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [x] My code follows the code style of this project. - [x] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- Makefile | 2 + README.md | 9 +- src/flashcart/64drive/64drive.c | 271 +++++++++++++++++++++++++++++ src/flashcart/64drive/64drive.h | 24 +++ src/flashcart/64drive/64drive_ll.c | 110 ++++++++++++ src/flashcart/64drive/64drive_ll.h | 99 +++++++++++ src/flashcart/64drive/README.md | 49 ++++++ src/flashcart/flashcart.c | 5 +- src/flashcart/flashcart.h | 1 + src/flashcart/sc64/sc64.c | 3 + src/menu/views/fault.c | 2 +- 11 files changed, 570 insertions(+), 5 deletions(-) create mode 100644 src/flashcart/64drive/64drive.c create mode 100644 src/flashcart/64drive/64drive.h create mode 100644 src/flashcart/64drive/64drive_ll.c create mode 100644 src/flashcart/64drive/64drive_ll.h create mode 100644 src/flashcart/64drive/README.md diff --git a/Makefile b/Makefile index 0d52250c..c71d657f 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,8 @@ SRCS = \ boot/boot.c \ boot/crc32.c \ boot/reboot.S \ + flashcart/64drive/64drive_ll.c \ + flashcart/64drive/64drive.c \ flashcart/flashcart_utils.c \ flashcart/flashcart.c \ flashcart/sc64/sc64_ll.c \ diff --git a/README.md b/README.md index 6dda5dcc..0a2e9978 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,13 @@ An open source menu for N64 flashcarts. ## Getting started +### 64drive +Ensure the cart has the latest [firmware](https://64drive.retroactive.be/support.php) installed. +Download the latest `menu.bin` file from the releases page, then put it in the root directory of your SD card. + ### SC64 -Ensure the cart is running the latest [firmware](https://github.com/Polprzewodnikowy/SummerCart64/releases/latest). -Download the `sc64menu.n64` ROM from the latest action run assets. -Add it to the root folder on your SD card. +Ensure the cart has the latest [firmware](https://github.com/Polprzewodnikowy/SummerCart64/releases/latest) installed. +Download the latest `sc64menu.n64` file from the releases page, then put it in the root directory of your SD card. ### ED64 & ED64P Currently not supported, but there is an aim to do so. diff --git a/src/flashcart/64drive/64drive.c b/src/flashcart/64drive/64drive.c new file mode 100644 index 00000000..82a0e1ea --- /dev/null +++ b/src/flashcart/64drive/64drive.c @@ -0,0 +1,271 @@ +#include +#include +#include + +#include +#include + +#include "utils/fs.h" +#include "utils/utils.h" + +#include "../flashcart_utils.h" +#include "64drive_ll.h" +#include "64drive.h" + + +#define ROM_ADDRESS (0x10000000) +#define SAVE_ADDRESS_DEV_A (0x13FE0000) +#define SAVE_ADDRESS_DEV_A_PKST2 (0x11606560) +#define SAVE_ADDRESS_DEV_B (0x1FFC0000) + +#define SUPPORTED_FPGA_REVISION (205) + + +static d64_device_variant_t device_variant = DEVICE_VARIANT_UNKNOWN; +static d64_save_type_t current_save_type = SAVE_TYPE_NONE; + + +static flashcart_error_t d64_init (void) { + uint16_t fpga_revision; + uint32_t bootloader_version; + + if (d64_ll_enable_extended_mode(false)) { + return FLASHCART_ERROR_INT; + } + + if (d64_ll_get_version(&device_variant, &fpga_revision, &bootloader_version)) { + return FLASHCART_ERROR_INT; + } + + if (fpga_revision < SUPPORTED_FPGA_REVISION) { + return FLASHCART_ERROR_OUTDATED; + } + + if (d64_ll_enable_save_writeback(false)) { + return FLASHCART_ERROR_INT; + } + + if (d64_ll_set_save_type(SAVE_TYPE_NONE)) { + return FLASHCART_ERROR_INT; + } + + current_save_type = SAVE_TYPE_NONE; + + if (d64_ll_enable_cartrom_writes(true)) { + return FLASHCART_ERROR_INT; + } + + if (d64_ll_set_persistent_variable_storage(false, 0, 0)) { + return FLASHCART_ERROR_INT; + } + + return FLASHCART_OK; +} + +static flashcart_error_t d64_deinit (void) { + if (d64_ll_enable_cartrom_writes(false)) { + return FLASHCART_ERROR_INT; + } + + return FLASHCART_OK; +} + +static flashcart_error_t d64_load_rom (char *rom_path, flashcart_progress_callback_t *progress) { + FIL fil; + UINT br; + + if (f_open(&fil, strip_sd_prefix(rom_path), FA_READ) != FR_OK) { + return FLASHCART_ERROR_LOAD; + } + + fix_file_size(&fil); + + size_t rom_size = f_size(&fil); + + if (rom_size > MiB(64)) { + f_close(&fil); + return FLASHCART_ERROR_LOAD; + } + + size_t sdram_size = MiB(64); + + size_t chunk_size = MiB(1); + for (int offset = 0; offset < sdram_size; offset += chunk_size) { + size_t block_size = MIN(sdram_size - offset, chunk_size); + if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) { + f_close(&fil); + return FLASHCART_ERROR_LOAD; + } + if (progress) { + progress(f_tell(&fil) / (float) (f_size(&fil))); + } + } + if (f_tell(&fil) != rom_size) { + f_close(&fil); + return FLASHCART_ERROR_LOAD; + } + + if (f_close(&fil) != FR_OK) { + return FLASHCART_ERROR_LOAD; + } + + return FLASHCART_OK; +} + +static flashcart_error_t d64_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) { + FIL fil; + UINT br; + + if (f_open(&fil, strip_sd_prefix(file_path), FA_READ) != FR_OK) { + return FLASHCART_ERROR_LOAD; + } + + fix_file_size(&fil); + + size_t file_size = f_size(&fil) - file_offset; + + if (file_size > (MiB(64) - rom_offset)) { + f_close(&fil); + return FLASHCART_ERROR_ARGS; + } + + if (f_lseek(&fil, file_offset) != FR_OK) { + f_close(&fil); + return FLASHCART_ERROR_LOAD; + } + + if (f_read(&fil, (void *) (ROM_ADDRESS + rom_offset), file_size, &br) != FR_OK) { + f_close(&fil); + return FLASHCART_ERROR_LOAD; + } + if (br != file_size) { + f_close(&fil); + return FLASHCART_ERROR_LOAD; + } + + if (f_close(&fil) != FR_OK) { + return FLASHCART_ERROR_LOAD; + } + + return FLASHCART_OK; +} + +static flashcart_error_t d64_load_save (char *save_path) { + uint8_t eeprom_contents[2048] __attribute__((aligned(8))); + FIL fil; + UINT br; + + if (f_open(&fil, strip_sd_prefix(save_path), FA_READ) != FR_OK) { + return FLASHCART_ERROR_LOAD; + } + + size_t save_size = f_size(&fil); + + bool is_eeprom_save = (current_save_type == SAVE_TYPE_EEPROM_4K || current_save_type == SAVE_TYPE_EEPROM_16K); + + void *address = (void *) (SAVE_ADDRESS_DEV_B); + if (is_eeprom_save) { + address = eeprom_contents; + } else if (device_variant == DEVICE_VARIANT_A) { + address = (void *) (SAVE_ADDRESS_DEV_A); + if (current_save_type == SAVE_TYPE_FLASHRAM_PKST2) { + address = (void *) (SAVE_ADDRESS_DEV_A_PKST2); + } + } + + if (f_read(&fil, address, save_size, &br) != FR_OK) { + f_close(&fil); + return FLASHCART_ERROR_LOAD; + } + + if (is_eeprom_save) { + if (d64_ll_write_eeprom_contents(eeprom_contents)) { + f_close(&fil); + return FLASHCART_ERROR_LOAD; + } + } + + if (f_close(&fil) != FR_OK) { + return FLASHCART_ERROR_LOAD; + } + + if (br != save_size) { + return FLASHCART_ERROR_LOAD; + } + + return FLASHCART_OK; +} + +static flashcart_error_t d64_set_save_type (flashcart_save_type_t save_type) { + d64_save_type_t type; + + switch (save_type) { + case FLASHCART_SAVE_TYPE_NONE: + type = SAVE_TYPE_NONE; + break; + case FLASHCART_SAVE_TYPE_EEPROM_4K: + type = SAVE_TYPE_EEPROM_4K; + break; + case FLASHCART_SAVE_TYPE_EEPROM_16K: + type = SAVE_TYPE_EEPROM_16K; + break; + case FLASHCART_SAVE_TYPE_SRAM: + type = SAVE_TYPE_SRAM; + break; + case FLASHCART_SAVE_TYPE_SRAM_BANKED: + type = SAVE_TYPE_SRAM_BANKED; + break; + case FLASHCART_SAVE_TYPE_SRAM_128K: + // NOTE: 64drive doesn't support 128 kiB SRAM save type, fallback to 32 kiB SRAM save type + type = SAVE_TYPE_SRAM; + break; + case FLASHCART_SAVE_TYPE_FLASHRAM: + type = SAVE_TYPE_FLASHRAM; + break; + case FLASHCART_SAVE_TYPE_FLASHRAM_PKST2: + type = (device_variant == DEVICE_VARIANT_A) ? SAVE_TYPE_FLASHRAM_PKST2 : SAVE_TYPE_FLASHRAM; + break; + default: + return FLASHCART_ERROR_ARGS; + } + + if (d64_ll_enable_save_writeback(false)) { + return FLASHCART_ERROR_INT; + } + + if (d64_ll_set_save_type(type)) { + return FLASHCART_ERROR_INT; + } + + current_save_type = type; + + return FLASHCART_OK; +} + +static flashcart_error_t d64_set_save_writeback (uint32_t *sectors) { + if (d64_ll_write_save_writeback_lba_list(sectors)) { + return FLASHCART_ERROR_INT; + } + + if (d64_ll_enable_save_writeback(true)) { + return FLASHCART_ERROR_INT; + } + + return FLASHCART_OK; +} + + +static flashcart_t flashcart_d64 = { + .init = d64_init, + .deinit = d64_deinit, + .load_rom = d64_load_rom, + .load_file = d64_load_file, + .load_save = d64_load_save, + .set_save_type = d64_set_save_type, + .set_save_writeback = d64_set_save_writeback, +}; + + +flashcart_t *d64_get_flashcart (void) { + return &flashcart_d64; +} diff --git a/src/flashcart/64drive/64drive.h b/src/flashcart/64drive/64drive.h new file mode 100644 index 00000000..4855b10f --- /dev/null +++ b/src/flashcart/64drive/64drive.h @@ -0,0 +1,24 @@ +/** + * @file 64drive.h + * @brief 64drive flashcart support + * @ingroup flashcart + */ + +#ifndef FLASHCART_64DRIVE_H__ +#define FLASHCART_64DRIVE_H__ + + +#include "../flashcart.h" + + +/** + * @addtogroup 64drive + * @{ + */ + +flashcart_t *d64_get_flashcart (void); + +/** @} */ /* 64drive */ + + +#endif diff --git a/src/flashcart/64drive/64drive_ll.c b/src/flashcart/64drive/64drive_ll.c new file mode 100644 index 00000000..fb00a89c --- /dev/null +++ b/src/flashcart/64drive/64drive_ll.c @@ -0,0 +1,110 @@ +#include + +#include "../flashcart_utils.h" +#include "64drive_ll.h" + + +#define CI_STATUS_BUSY (1 << 12) + +#define DEVICE_VARIANT_MASK (0xFFFF) +#define FPGA_REVISION_MASK (0xFFFF) + + +typedef enum { + CMD_ID_SET_SAVE_TYPE = 0xD0, + CMD_ID_DISABLE_SAVE_WRITEBACK = 0xD1, + CMD_ID_ENABLE_SAVE_WRITEBACK = 0xD2, + CMD_ID_DISABLE_BYTESWAP_ON_LOAD = 0xE0, + CMD_ID_ENABLE_BYTESWAP_ON_LOAD = 0xE1, + CMD_ID_ENABLE_CARTROM_WRITES = 0xF0, + CMD_ID_DISABLE_CARTROM_WRITES = 0xF1, + CMD_ID_ENABLE_EXTENDED_MODE = 0xF8, + CMD_ID_DISABLE_EXTENDED_MODE = 0xF9, +} d64_ci_cmd_id_t; + + +static d64_regs_t *d64_regs = D64_REGS; + + +static bool d64_ll_ci_wait (void) { + int timeout = 0; + do { + if (timeout++ >= 0x10000) { + return true; + } + } while (io_read((uint32_t) (&d64_regs->STATUS)) & CI_STATUS_BUSY); + return false; +} + +static bool d64_ll_ci_cmd (d64_ci_cmd_id_t id) { + io_write((uint32_t) (&d64_regs->COMMAND), id); + return d64_ll_ci_wait(); +} + + +bool d64_ll_get_version (d64_device_variant_t *device_variant, uint16_t *fpga_revision, uint32_t *bootloader_version) { + if (d64_ll_ci_wait()) { + return true; + } + *device_variant = (d64_device_variant_t) (io_read((uint32_t) (&d64_regs->VARIANT)) & DEVICE_VARIANT_MASK); + *fpga_revision = (io_read((uint32_t) (&d64_regs->REVISION)) & FPGA_REVISION_MASK); + *bootloader_version = io_read((uint32_t) (&d64_regs->PERSISTENT)); + return d64_ll_ci_wait(); +} + +bool d64_ll_set_persistent_variable_storage (bool quick_reboot, d64_tv_type_t force_tv_type, uint8_t cic_seed) { + if (d64_ll_ci_wait()) { + return true; + } + io_write((uint32_t) (&d64_regs->PERSISTENT), (quick_reboot << 16) | ((force_tv_type & 0x03) << 8) | (cic_seed & 0xFF)); + return d64_ll_ci_wait(); +} + +bool d64_ll_set_save_type (d64_save_type_t save_type) { + if (d64_ll_ci_wait()) { + return true; + } + io_write((uint32_t) (&d64_regs->BUFFER), save_type); + return d64_ll_ci_cmd(CMD_ID_SET_SAVE_TYPE); +} + +bool d64_ll_enable_save_writeback (bool enabled) { + if (d64_ll_ci_wait()) { + return true; + } + return d64_ll_ci_cmd(enabled ? CMD_ID_ENABLE_SAVE_WRITEBACK : CMD_ID_DISABLE_SAVE_WRITEBACK); +} + +bool d64_ll_enable_cartrom_writes (bool enabled) { + if (d64_ll_ci_wait()) { + return true; + } + return d64_ll_ci_cmd(enabled ? CMD_ID_ENABLE_CARTROM_WRITES : CMD_ID_DISABLE_CARTROM_WRITES); +} + +bool d64_ll_enable_extended_mode (bool enabled) { + d64_ll_ci_wait(); + if (enabled) { + io_write((uint32_t) (&D64_REGS->COMMAND), CMD_ID_ENABLE_EXTENDED_MODE); + } else { + io_write((uint32_t) (&D64_REGS_EXT->COMMAND), CMD_ID_DISABLE_EXTENDED_MODE); + } + d64_regs = enabled ? D64_REGS_EXT : D64_REGS; + return d64_ll_ci_wait(); +} + +bool d64_ll_write_eeprom_contents (void *contents) { + if (d64_ll_ci_wait()) { + return true; + } + pi_dma_write_data(contents, d64_regs->EEPROM, 2048); + return d64_ll_ci_wait(); +} + +bool d64_ll_write_save_writeback_lba_list (void *list) { + if (d64_ll_ci_wait()) { + return true; + } + pi_dma_write_data(list, d64_regs->WRITEBACK, 1024); + return d64_ll_ci_wait(); +} diff --git a/src/flashcart/64drive/64drive_ll.h b/src/flashcart/64drive/64drive_ll.h new file mode 100644 index 00000000..44d9ee5f --- /dev/null +++ b/src/flashcart/64drive/64drive_ll.h @@ -0,0 +1,99 @@ +/** + * @file 64drive_ll.h + * @brief 64drive flashcart low level access + * @ingroup flashcart + */ + +#ifndef FLASHCART_64DRIVE_LL_H__ +#define FLASHCART_64DRIVE_LL_H__ + + +#include +#include + + +/** + * @addtogroup 64drive + * @{ + */ + +typedef struct { + uint8_t BUFFER[512]; + uint32_t STATUS; + uint32_t __unused_1; + uint32_t COMMAND; + uint32_t __unused_2; + uint32_t LBA; + uint32_t __unused_3; + uint32_t LENGTH; + uint32_t __unused_4; + uint32_t RESULT; + + uint32_t __unused_5[49]; + + uint32_t SDRAM_SIZE; + uint32_t MAGIC; + uint32_t VARIANT; + uint32_t PERSISTENT; + uint32_t BUTTON_UPGRADE; + uint32_t REVISION; + + uint32_t __unused_6[64]; + + uint32_t USB_COMMAND_STATUS; + uint32_t USB_PARAM_RESULT[2]; + + uint32_t __unused_7[5]; + + uint32_t WIFI_COMMAND_STATUS; + uint32_t WIFI_PARAM_RESULT[2]; + + uint32_t __unused_8[757]; + + uint8_t EEPROM[2048]; + uint32_t WRITEBACK[256]; +} d64_regs_t; + +#define D64_REGS_BASE (0x18000000UL) +#define D64_REGS_BASE_EXT (0x1F800000UL) +#define D64_REGS ((d64_regs_t *) D64_REGS_BASE) +#define D64_REGS_EXT ((d64_regs_t *) D64_REGS_BASE_EXT) + + +typedef enum { + DEVICE_VARIANT_UNKNOWN = 0x0000, + DEVICE_VARIANT_A = 0x4100, + DEVICE_VARIANT_B = 0x4200, +} d64_device_variant_t; + +typedef enum { + TV_TYPE_PAL = 0, + TV_TYPE_NTSC = 1, + TV_TYPE_MPAL = 2, + TV_TYPE_UNKNOWN = 3, +} d64_tv_type_t; + +typedef enum { + SAVE_TYPE_NONE, + SAVE_TYPE_EEPROM_4K, + SAVE_TYPE_EEPROM_16K, + SAVE_TYPE_SRAM, + SAVE_TYPE_FLASHRAM, + SAVE_TYPE_SRAM_BANKED, + SAVE_TYPE_FLASHRAM_PKST2, +} d64_save_type_t; + + +bool d64_ll_get_version (d64_device_variant_t *device_variant, uint16_t *fpga_revision, uint32_t *bootloader_version); +bool d64_ll_set_persistent_variable_storage (bool quick_reboot, d64_tv_type_t force_tv_type, uint8_t cic_seed); +bool d64_ll_set_save_type (d64_save_type_t save_type); +bool d64_ll_enable_save_writeback (bool enabled); +bool d64_ll_enable_cartrom_writes (bool enabled); +bool d64_ll_enable_extended_mode (bool enabled); +bool d64_ll_write_eeprom_contents (void *contents); +bool d64_ll_write_save_writeback_lba_list (void *list); + +/** @} */ /* 64drive */ + + +#endif diff --git a/src/flashcart/64drive/README.md b/src/flashcart/64drive/README.md new file mode 100644 index 00000000..bd961b85 --- /dev/null +++ b/src/flashcart/64drive/README.md @@ -0,0 +1,49 @@ +## 64drive developer notes + +### Official documentation + +http://64drive.retroactive.be/64drive_hardware_spec.pdf + + +### Save location offset in SDRAM + +| Type | HW1 | HW2 | +| ---------------------------- | ------------ | ------------ | +| SRAM | `0x03FE0000` | `0x0FFC0000` | +| FlashRAM | `0x03FE0000` | `0x0FFC0000` | +| SRAM banked | `0x03FE0000` | `0x0FFC0000` | +| FlashRAM (Pokemon Stadium 2) | `0x01606560` | `0x0FFC0000` | + +EEPROM save types are stored in separate memory inside FPGA, rest of the save types are stored inside SDRAM memory. +EEPROM save types need manual load as this memory space can't be written with "Read multiple sectors to SDRAM" command. + + +### "Persistent variable storage" register + +| Bits | Meaning | +| --------- | ---------------------------------------------------------- | +| `[31:17]` | _Unused_ | +| `[16]` | Reset behavior: `0` - boot to menu / `1` - quick boot game | +| `[15:10]` | _Unused_ | +| `[9:8]` | Force TV type | +| `[7:0]` | CIC seed | + +It's used by the bootloader to quickly load game without running menu again. +Should contain bootloader version but it's zeroed if ROM wasn't ran through bootloader (e.g. ROM was loaded via USB). + + +### "Enable/disable save writeback" command + +Does not work when USB cable is connected - writeback is forced to be in disabled state. +Curiously, official 64drive menu never calls this command, save writeback might be enabled by default. + + +### "Enable/disable byteswap on load" command + +Annoyingly, this command affects both loading single sector into the buffer and loading multiple sectors to the SDRAM. + + +### "Enable/disable extended address mode" command + +As of latest available firmware version 2.05 this command is not implemented. +Documentation specifies it's supported on firmwares 2.06+ but this version (or anything newer) was never published. diff --git a/src/flashcart/flashcart.c b/src/flashcart/flashcart.c index fe42ad93..51548a07 100644 --- a/src/flashcart/flashcart.c +++ b/src/flashcart/flashcart.c @@ -8,6 +8,8 @@ #include "utils/utils.h" #include "flashcart.h" + +#include "64drive/64drive.h" #include "sc64/sc64.h" @@ -22,6 +24,7 @@ static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = { KiB(96), KiB(128), KiB(128), + KiB(128), }; static uint32_t save_writeback_sectors[SAVE_WRITEBACK_MAX_SECTORS] __attribute__((aligned(8))); @@ -58,7 +61,7 @@ flashcart_error_t flashcart_init (void) { int (* libcart_init) (void); flashcart_t *(* get) (void); } flashcarts[CART_MAX] = { - { CART_CI, ci_init, NULL }, // 64drive + { CART_CI, ci_init, d64_get_flashcart }, // 64drive { CART_SC, sc_init, sc64_get_flashcart }, // SC64 { CART_EDX, edx_init, NULL }, // Series X EverDrive-64 { CART_ED, ed_init, NULL }, // Original EverDrive-64 diff --git a/src/flashcart/flashcart.h b/src/flashcart/flashcart.h index 436aaf94..844ab3cb 100644 --- a/src/flashcart/flashcart.h +++ b/src/flashcart/flashcart.h @@ -32,6 +32,7 @@ typedef enum { FLASHCART_SAVE_TYPE_SRAM_BANKED, FLASHCART_SAVE_TYPE_SRAM_128K, FLASHCART_SAVE_TYPE_FLASHRAM, + FLASHCART_SAVE_TYPE_FLASHRAM_PKST2, __FLASHCART_SAVE_TYPE_END } flashcart_save_type_t; diff --git a/src/flashcart/sc64/sc64.c b/src/flashcart/sc64/sc64.c index 716a46f3..1d7245d4 100644 --- a/src/flashcart/sc64/sc64.c +++ b/src/flashcart/sc64/sc64.c @@ -311,6 +311,9 @@ static flashcart_error_t sc64_set_save_type (flashcart_save_type_t save_type) { case FLASHCART_SAVE_TYPE_FLASHRAM: type = SAVE_TYPE_FLASHRAM; break; + case FLASHCART_SAVE_TYPE_FLASHRAM_PKST2: + type = SAVE_TYPE_FLASHRAM; + break; default: return FLASHCART_ERROR_ARGS; } diff --git a/src/menu/views/fault.c b/src/menu/views/fault.c index 95bf8ce3..e60b19f7 100644 --- a/src/menu/views/fault.c +++ b/src/menu/views/fault.c @@ -31,7 +31,7 @@ static void draw (menu_t *menu, surface_t *d) { const char *firmware_message = ( "Minimum supported versions:\n" "EverDrive-64: ?\n" - "64drive: ?\n" + "64drive: 2.05\n" "SC64: 2.16.0" );