From c310c5f9e1d2dbcbec03a1b4fb020f7e898d9384 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 19 Mar 2024 17:11:22 +0000 Subject: [PATCH 01/16] Update submodules (#93) ## Description Updates libpng and libdragon to latest. --- libdragon | 2 +- src/libs/libspng | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libdragon b/libdragon index 185c4a34..c99272b5 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 185c4a34f265c90d027f4054024cff1675529d7f +Subproject commit c99272b552d398e479a3de6ed9b6563a710b3cc2 diff --git a/src/libs/libspng b/src/libs/libspng index e5c1fc47..adc94393 160000 --- a/src/libs/libspng +++ b/src/libs/libspng @@ -1 +1 @@ -Subproject commit e5c1fc470fceaca08b8c30dc40768c28b82b9e12 +Subproject commit adc94393dbeddf9e027d1b2dfff7c1bab975224e From 3becd1ff59cb64a1c62fbf5417dc38d6d04855df Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Wed, 24 Apr 2024 02:45:09 +0200 Subject: [PATCH 02/16] Use `stdio` calls for file/directory interaction instead of `fatfs` (#95) ## Description This PR changes most of the calls to the fatfs lib with the standard C ones. Additionally, there's couple of changes required to adapt to new interface and several bug fixes. As a bonus menu can now be run in ares emulator and on the iQue player, adapting to the available storage options - DragonFS in the ROM and iQue flash modules (bbfs). ## Motivation and Context To make it easier to use storage medium other than SD cards on platforms other than N64 with flashcart. ## How Has This Been Tested? SummerCart64 flashcart and ares emulator ## Screenshots N/A ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [x] Bug fix (fixes an issue) - [x] Breaking change (breaking change) - [x] 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: Polprzewodnikowy --- .devcontainer/devcontainer.json | 4 +- .gitignore | 12 ++- .gitmodules | 2 +- Makefile | 9 +- filesystem/.gitkeep | 0 libdragon | 2 +- src/flashcart/64drive/64drive.c | 18 ++-- src/flashcart/flashcart.c | 90 +++++++++------- src/flashcart/flashcart.h | 5 +- src/flashcart/flashcart_utils.c | 72 +++++++++++-- src/flashcart/flashcart_utils.h | 16 ++- src/flashcart/sc64/sc64.c | 82 +++++++-------- src/libs/miniz | 2 +- src/menu/cart_load.c | 4 +- src/menu/components.h | 2 +- src/menu/components/background.c | 36 ++++--- src/menu/components/boxart.c | 50 +++++---- src/menu/components/common.c | 12 +++ src/menu/components/file_list.c | 18 ++-- src/menu/disk_info.c | 40 +++---- src/menu/disk_info.h | 5 +- src/menu/fonts.c | 15 ++- src/menu/fonts.h | 2 +- src/menu/menu.c | 54 ++++++---- src/menu/menu_state.h | 8 +- src/menu/mp3_player.c | 80 +++++++------- src/menu/path.c | 4 +- src/menu/path.h | 2 +- src/menu/png_decoder.c | 28 ++--- src/menu/rom_info.c | 19 ++-- src/menu/settings.c | 23 +++-- src/menu/settings.h | 6 +- src/menu/usb_comm.c | 33 +++--- src/menu/views/browser.c | 161 ++++++++++++++++------------- src/menu/views/file_info.c | 23 ++--- src/menu/views/load_disk.c | 2 +- src/menu/views/load_rom.c | 2 +- src/menu/views/settings_editor.c | 16 +-- src/menu/views/text_viewer.c | 96 ++++++++++------- src/utils/fs.c | 172 ++++++++++--------------------- src/utils/fs.h | 7 +- 41 files changed, 670 insertions(+), 564 deletions(-) create mode 100644 filesystem/.gitkeep diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9c9a1878..effc134b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "mounts": [ "source=n64flashcartmenu-bashhistory,target=/commandhistory,type=volume" ], - "postCreateCommand": "git submodule update --init && cd ./libdragon && ./build.sh", + "postCreateCommand": "git submodule update --init && cd ./libdragon && make clobber -j && make libdragon tools -j && make install tools-install -j", "customizations": { "vscode": { "extensions": [ @@ -24,4 +24,4 @@ } } } -} +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index de80dacb..bc2d5ddf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,17 @@ +# Ignore editor specific config folders/files /.vscode + +# Ignore compilation result directories /build -/filesystem /output + +# Ignore generated files in the libdragon FS +/filesystem/FiraMonoBold.font64 + +# Ignore external development tools /tools/* -# There should never be ROMs uploaded, but just incase, make sure they are excluded. +# Ignore any N64 ROM **/*.n64 +**/*.v64 **/*.z64 diff --git a/.gitmodules b/.gitmodules index f830561a..af1ca991 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,7 @@ [submodule "libdragon"] path = libdragon url = https://github.com/DragonMinded/libdragon - branch = unstable + branch = preview ignore = dirty [submodule "src/libs/libspng"] path = src/libs/libspng diff --git a/Makefile b/Makefile index 7c0ec049..fed6ed87 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,11 @@ BUILD_TIMESTAMP = "$(shell TZ='UTC' date "+%Y-%m-%d %H:%M:%S %:z")" include $(N64_INST)/include/n64.mk +N64_ROM_SAVETYPE = none +N64_ROM_RTC = 1 +N64_ROM_REGIONFREE = 1 +N64_ROM_REGION = E + N64_CFLAGS += -iquote $(SOURCE_DIR) -iquote $(ASSETS_DIR) -I $(SOURCE_DIR)/libs -flto=auto $(FLAGS) SRCS = \ @@ -128,7 +133,9 @@ all: $(OUTPUT_DIR)/$(PROJECT_NAME).n64 64drive ed64 ed64-clone sc64 .PHONY: all clean: - @rm -rf ./$(BUILD_DIR) ./$(FILESYSTEM_DIR) ./$(OUTPUT_DIR) + @rm -f ./$(FILESYSTEM) + @find ./$(FILESYSTEM_DIR) -type d -empty -delete + @rm -rf ./$(BUILD_DIR) ./$(OUTPUT_DIR) .PHONY: clean run: $(OUTPUT_DIR)/$(PROJECT_NAME).n64 diff --git a/filesystem/.gitkeep b/filesystem/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/libdragon b/libdragon index c99272b5..8dd362b8 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit c99272b552d398e479a3de6ed9b6563a710b3cc2 +Subproject commit 8dd362b8d858ae60befe9d27bc55cf770b9b50af diff --git a/src/flashcart/64drive/64drive.c b/src/flashcart/64drive/64drive.c index 9b0b8b08..4a23d4d2 100644 --- a/src/flashcart/64drive/64drive.c +++ b/src/flashcart/64drive/64drive.c @@ -83,11 +83,11 @@ static flashcart_err_t d64_load_rom (char *rom_path, flashcart_progress_callback FIL fil; UINT br; - if (f_open(&fil, strip_sd_prefix(rom_path), FA_READ) != FR_OK) { + if (f_open(&fil, strip_fs_prefix(rom_path), FA_READ) != FR_OK) { return FLASHCART_ERR_LOAD; } - fix_file_size(&fil); + fatfs_fix_file_size(&fil); size_t rom_size = f_size(&fil); @@ -125,11 +125,11 @@ static flashcart_err_t d64_load_file (char *file_path, uint32_t rom_offset, uint FIL fil; UINT br; - if (f_open(&fil, strip_sd_prefix(file_path), FA_READ) != FR_OK) { + if (f_open(&fil, strip_fs_prefix(file_path), FA_READ) != FR_OK) { return FLASHCART_ERR_LOAD; } - fix_file_size(&fil); + fatfs_fix_file_size(&fil); size_t file_size = f_size(&fil) - file_offset; @@ -164,7 +164,7 @@ static flashcart_err_t d64_load_save (char *save_path) { FIL fil; UINT br; - if (f_open(&fil, strip_sd_prefix(save_path), FA_READ) != FR_OK) { + if (f_open(&fil, strip_fs_prefix(save_path), FA_READ) != FR_OK) { return FLASHCART_ERR_LOAD; } @@ -251,7 +251,13 @@ static flashcart_err_t d64_set_save_type (flashcart_save_type_t save_type) { return FLASHCART_OK; } -static flashcart_err_t d64_set_save_writeback (uint32_t *sectors) { +static flashcart_err_t d64_set_save_writeback (char *save_path) { + uint32_t sectors[SAVE_WRITEBACK_MAX_SECTORS] __attribute__((aligned(8))); + + if (fatfs_get_file_sectors(save_path, sectors, ADDRESS_TYPE_MEM, SAVE_WRITEBACK_MAX_SECTORS)) { + return FLASHCART_ERR_LOAD; + } + if (d64_ll_write_save_writeback_lba_list(sectors)) { return FLASHCART_ERR_INT; } diff --git a/src/flashcart/flashcart.c b/src/flashcart/flashcart.c index bd2e4163..c5aaaacb 100644 --- a/src/flashcart/flashcart.c +++ b/src/flashcart/flashcart.c @@ -8,14 +8,12 @@ #include "utils/utils.h" #include "flashcart.h" +#include "flashcart_utils.h" #include "64drive/64drive.h" #include "sc64/sc64.h" -#define SAVE_WRITEBACK_MAX_SECTORS (256) - - static const size_t SAVE_SIZE[__FLASHCART_SAVE_TYPE_END] = { 0, 512, @@ -27,34 +25,44 @@ 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_err_t dummy_init (void) { + return FLASHCART_OK; +} -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; +static bool dummy_has_feature (flashcart_features_t feature) { + switch (feature) { + default: + return false; } } +static flashcart_err_t dummy_load_rom (char *rom_path, flashcart_progress_callback_t *progress) { + return FLASHCART_OK; +} -static flashcart_err_t dummy_init (void) { +static flashcart_err_t dummy_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) { + return FLASHCART_OK; +} + +static flashcart_err_t dummy_load_save (char *save_path) { + return FLASHCART_OK; +} + +static flashcart_err_t dummy_set_save_type (flashcart_save_type_t save_type) { return FLASHCART_OK; } static flashcart_t *flashcart = &((flashcart_t) { .init = dummy_init, .deinit = NULL, - .load_rom = NULL, - .load_file = NULL, - .load_save = NULL, - .set_save_type = NULL, + .has_feature = dummy_has_feature, + .load_rom = dummy_load_rom, + .load_file = dummy_load_file, + .load_save = dummy_load_save, + .load_64dd_ipl = NULL, + .load_64dd_disk = NULL, + .set_save_type = dummy_set_save_type, .set_save_writeback = NULL, }); @@ -69,7 +77,6 @@ static flashcart_t *flashcart = &((flashcart_t) { char *flashcart_convert_error_message (flashcart_err_t err) { switch (err) { case FLASHCART_OK: return "No error"; - case FLASHCART_ERR_NOT_DETECTED: return "No flashcart hardware was detected"; case FLASHCART_ERR_OUTDATED: return "Outdated flashcart firmware"; case FLASHCART_ERR_SD_CARD: return "Error during SD card initialization"; case FLASHCART_ERR_ARGS: return "Invalid argument passed to flashcart function"; @@ -80,15 +87,17 @@ char *flashcart_convert_error_message (flashcart_err_t err) { } } -flashcart_err_t flashcart_init (void) { +flashcart_err_t flashcart_init (const char **storage_prefix) { flashcart_err_t err; - bool sd_card_initialized = debug_init_sdfs("sd:/", -1); + if (sys_bbplayer()) { + // TODO: Add iQue callbacks + *storage_prefix = "bbfs:/"; + return FLASHCART_OK; + } -#ifndef NDEBUG - // NOTE: Some flashcarts doesn't have USB port, can't throw error here - debug_init_usblog(); -#endif + *storage_prefix = "sd:/"; + bool sd_card_initialized = debug_init_sdfs(*storage_prefix, -1); switch (cart_type) { case CART_CI: // 64drive @@ -96,6 +105,8 @@ flashcart_err_t flashcart_init (void) { break; case CART_EDX: // Series X EverDrive-64 + break; + case CART_ED: // Original EverDrive-64 break; @@ -103,15 +114,22 @@ flashcart_err_t flashcart_init (void) { flashcart = sc64_get_flashcart(); break; - default: - return FLASHCART_ERR_NOT_DETECTED; + default: // Probably emulator + *storage_prefix = "rom:/"; + debug_init_isviewer(); + break; } +#ifndef NDEBUG + // NOTE: Some flashcarts doesn't have USB port, can't throw error here + debug_init_usblog(); +#endif + if ((err = flashcart->init()) != FLASHCART_OK) { return err; } - if (!sd_card_initialized) { + if ((cart_type != CART_NULL) && (!sd_card_initialized)) { return FLASHCART_ERR_SD_CARD; } @@ -184,19 +202,11 @@ flashcart_err_t flashcart_load_save (char *save_path, flashcart_save_type_t save return err; } - if (flashcart->set_save_writeback) { - 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_ERR_LOAD; - } - if ((err = flashcart->set_save_writeback(save_writeback_sectors)) != FLASHCART_OK) { - return err; - } + if (!flashcart->set_save_writeback) { + return FLASHCART_OK; } - return FLASHCART_OK; + return flashcart->set_save_writeback(save_path); } flashcart_err_t flashcart_load_64dd_ipl (char *ipl_path, flashcart_progress_callback_t *progress) { diff --git a/src/flashcart/flashcart.h b/src/flashcart/flashcart.h index 001516bb..b45a32d0 100644 --- a/src/flashcart/flashcart.h +++ b/src/flashcart/flashcart.h @@ -15,7 +15,6 @@ /** @brief Flashcart error enumeration */ typedef enum { FLASHCART_OK, - FLASHCART_ERR_NOT_DETECTED, FLASHCART_ERR_OUTDATED, FLASHCART_ERR_SD_CARD, FLASHCART_ERR_ARGS, @@ -75,12 +74,12 @@ typedef struct { /** @brief The flashcart set save type function */ flashcart_err_t (*set_save_type) (flashcart_save_type_t save_type); /** @brief The flashcart set save writeback function */ - flashcart_err_t (*set_save_writeback) (uint32_t *sectors); + flashcart_err_t (*set_save_writeback) (char *save_path); } flashcart_t; char *flashcart_convert_error_message (flashcart_err_t err); -flashcart_err_t flashcart_init (void); +flashcart_err_t flashcart_init (const char **storage_prefix); flashcart_err_t flashcart_deinit (void); bool flashcart_has_feature (flashcart_features_t feature); flashcart_err_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress); diff --git a/src/flashcart/flashcart_utils.c b/src/flashcart/flashcart_utils.c index a89230c3..3eb13605 100644 --- a/src/flashcart/flashcart_utils.c +++ b/src/flashcart/flashcart_utils.c @@ -5,13 +5,6 @@ #include "utils/utils.h" -void fix_file_size (FIL *fil) { - // HACK: Align file size to the SD sector size to prevent FatFs from doing partial sector load. - // We are relying on direct transfer from SD to SDRAM without CPU intervention. - // Sending some extra bytes isn't an issue here. - fil->obj.objsize = ALIGN(f_size(fil), FS_SECTOR_SIZE); -} - void pi_dma_read_data (void *src, void *dst, size_t length) { data_cache_hit_writeback_invalidate(dst, length); dma_read_async(dst, (uint32_t) (src), length); @@ -27,3 +20,68 @@ void pi_dma_write_data (void *src, void *dst, size_t length) { dma_write_raw_async(src, (uint32_t) (dst), length); dma_wait(); } + +void fatfs_fix_file_size (FIL *fil) { + // HACK: Align file size to the SD sector size to prevent FatFs from doing partial sector load. + // We are relying on direct transfer from SD to SDRAM without CPU intervention. + // Sending some extra bytes isn't an issue here. + fil->obj.objsize = ALIGN(f_size(fil), FS_SECTOR_SIZE); +} + +bool fatfs_get_file_sectors (char *path, uint32_t *address, address_type_t type, uint32_t max_sectors) { + FATFS *fs; + FIL fil; + bool error = false; + + if (f_open(&fil, strip_fs_prefix(path), FA_READ) != FR_OK) { + return true; + } + + fatfs_fix_file_size(&fil); + + fs = fil.obj.fs; + + uint32_t sector_count = MIN(f_size(&fil) / FS_SECTOR_SIZE, max_sectors); + + for (uint32_t 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; + } + + uint32_t cluster = fil.clust; + + if (cluster >= fs->n_fatent) { + error = true; + break; + } + + uint32_t cluster_sector = (fs->database + ((LBA_t) (fs->csize) * (cluster - 2))); + + for (uint32_t i = 0; i < fs->csize; i++) { + uint32_t sector = (cluster_sector + i); + + if ((file_sector + i) >= sector_count) { + break; + } + + switch (type) { + case ADDRESS_TYPE_MEM: + *address = sector; + break; + + case ADDRESS_TYPE_PI: + io_write((uint32_t) (address), sector); + break; + } + + address++; + } + } + + if (f_close(&fil) != FR_OK) { + error = true; + } + + return error; +} diff --git a/src/flashcart/flashcart_utils.h b/src/flashcart/flashcart_utils.h index 38d1b589..c0b5d6d8 100644 --- a/src/flashcart/flashcart_utils.h +++ b/src/flashcart/flashcart_utils.h @@ -8,12 +8,26 @@ #define FLASHCART_UTILS_H__ +#include +#include +#include + #include -void fix_file_size (FIL *fil); +#define SAVE_WRITEBACK_MAX_SECTORS (256) + + +typedef enum { + ADDRESS_TYPE_MEM, + ADDRESS_TYPE_PI, +} address_type_t; + + void pi_dma_read_data (void *src, void *dst, size_t length); void pi_dma_write_data (void *src, void *dst, size_t length); +void fatfs_fix_file_size (FIL *fil); +bool fatfs_get_file_sectors (char *path, uint32_t *address, address_type_t address_type, uint32_t max_sectors); #endif diff --git a/src/flashcart/sc64/sc64.c b/src/flashcart/sc64/sc64.c index b7febf16..1b105fb0 100644 --- a/src/flashcart/sc64/sc64.c +++ b/src/flashcart/sc64/sc64.c @@ -75,9 +75,6 @@ static const uint8_t vzone_to_pzone[DISK_TYPES][DISK_ZONES] = { static const uint8_t rom_zones[DISK_TYPES] = { 5, 7, 9, 11, 13, 15, 16 }; -static uint32_t disk_sectors_start_offset; - - static flashcart_err_t load_to_flash (FIL *fil, void *address, size_t size, UINT *br, flashcart_progress_callback_t *progress) { size_t erase_block_size; UINT bp; @@ -110,23 +107,6 @@ static flashcart_err_t load_to_flash (FIL *fil, void *address, size_t size, UINT return FLASHCART_OK; } -static uint32_t disk_sectors_start (uint32_t offset) { - disk_sectors_start_offset = offset; - return (offset + (DISK_MAX_SECTORS * sizeof(uint32_t))); -} - -static void disk_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 > DISK_MAX_SECTORS) || (offset > sector_count)) { - return; - } - - io_write(ROM_ADDRESS + disk_sectors_start_offset + (offset * sizeof(uint32_t)), sector); - } -} static bool disk_zone_track_is_bad (uint8_t zone, uint8_t track, flashcart_disk_parameters_t *disk_parameters) { for (int i = 0; i < DISK_BAD_TRACKS_PER_ZONE; i++) { @@ -153,7 +133,7 @@ static void disk_set_thb_mapping (uint32_t offset, uint16_t track, uint8_t head, io_write(ROM_ADDRESS + offset + (index * sizeof(uint32_t)), mapping); } -static uint32_t disk_load_thb_table (uint32_t offset, flashcart_disk_parameters_t *disk_parameters) { +static void disk_load_thb_table (flashcart_disk_parameters_t *disk_parameters, uint32_t *thb_table_offset, uint32_t *current_offset) { int file_offset = 0; uint16_t lba = 0; @@ -175,15 +155,15 @@ static uint32_t disk_load_thb_table (uint32_t offset, flashcart_disk_parameters_ uint16_t track = track_offset + zone_track; if (disk_zone_track_is_bad(pzone, zone_track, disk_parameters)) { - disk_set_thb_mapping(offset, track, head, 0, false, false, 0); - disk_set_thb_mapping(offset, track, head, 1, false, false, 0); + disk_set_thb_mapping(*current_offset, track, head, 0, false, false, 0); + disk_set_thb_mapping(*current_offset, track, head, 1, false, false, 0); continue; } for (uint8_t block = 0; block < DISK_BLOCKS; block += 1) { bool valid = !(disk_system_lba_is_bad(lba, disk_parameters)); bool writable = (vzone >= rom_zones[disk_parameters->disk_type]); - disk_set_thb_mapping(offset, track, head, (starting_block ^ block), valid, writable, file_offset); + disk_set_thb_mapping(*current_offset, track, head, (starting_block ^ block), valid, writable, file_offset); file_offset += (sector_length * DISK_SECTORS_PER_BLOCK); lba += 1; } @@ -192,7 +172,19 @@ static uint32_t disk_load_thb_table (uint32_t offset, flashcart_disk_parameters_ } } - return (offset + (DISK_TRACKS * DISK_HEADS * DISK_BLOCKS * sizeof(uint32_t))); + *thb_table_offset = *current_offset; + *current_offset += (DISK_TRACKS * DISK_HEADS * DISK_BLOCKS * sizeof(uint32_t)); +} + +static bool disk_load_sector_table (char *path, uint32_t *sector_table_offset, uint32_t *current_offset) { + if (fatfs_get_file_sectors(path, (uint32_t *) (ROM_ADDRESS + *current_offset), ADDRESS_TYPE_PI, DISK_MAX_SECTORS)) { + return true; + } + + *sector_table_offset = *current_offset; + *current_offset += (DISK_MAX_SECTORS * sizeof(uint32_t)); + + return false; } @@ -270,11 +262,11 @@ static flashcart_err_t sc64_load_rom (char *rom_path, flashcart_progress_callbac FIL fil; UINT br; - if (f_open(&fil, strip_sd_prefix(rom_path), FA_READ) != FR_OK) { + if (f_open(&fil, strip_fs_prefix(rom_path), FA_READ) != FR_OK) { return FLASHCART_ERR_LOAD; } - fix_file_size(&fil); + fatfs_fix_file_size(&fil); size_t rom_size = f_size(&fil); @@ -351,11 +343,11 @@ static flashcart_err_t sc64_load_file (char *file_path, uint32_t rom_offset, uin FIL fil; UINT br; - if (f_open(&fil, strip_sd_prefix(file_path), FA_READ) != FR_OK) { + if (f_open(&fil, strip_fs_prefix(file_path), FA_READ) != FR_OK) { return FLASHCART_ERR_LOAD; } - fix_file_size(&fil); + fatfs_fix_file_size(&fil); size_t file_size = f_size(&fil) - file_offset; @@ -413,7 +405,7 @@ static flashcart_err_t sc64_load_save (char *save_path) { FIL fil; UINT br; - if (f_open(&fil, strip_sd_prefix(save_path), FA_READ) != FR_OK) { + if (f_open(&fil, strip_fs_prefix(save_path), FA_READ) != FR_OK) { return FLASHCART_ERR_LOAD; } @@ -439,11 +431,11 @@ static flashcart_err_t sc64_load_64dd_ipl (char *ipl_path, flashcart_progress_ca FIL fil; UINT br; - if (f_open(&fil, strip_sd_prefix(ipl_path), FA_READ) != FR_OK) { + if (f_open(&fil, strip_fs_prefix(ipl_path), FA_READ) != FR_OK) { return FLASHCART_ERR_LOAD; } - fix_file_size(&fil); + fatfs_fix_file_size(&fil); size_t ipl_size = f_size(&fil); @@ -476,19 +468,17 @@ static flashcart_err_t sc64_load_64dd_ipl (char *ipl_path, flashcart_progress_ca } static flashcart_err_t sc64_load_64dd_disk (char *disk_path, flashcart_disk_parameters_t *disk_parameters) { - sc64_disk_mapping_t mapping = { .count = 0 }; - uint32_t next_mapping_offset = DISK_MAPPING_ROM_OFFSET; + sc64_disk_mapping_t mapping; + uint32_t mapping_offset = DISK_MAPPING_ROM_OFFSET; + sc64_drive_type_t drive_type = (disk_parameters->development_drive ? DRIVE_TYPE_DEVELOPMENT : DRIVE_TYPE_RETAIL); // TODO: Support loading multiple disks - // LOOP START - mapping.disks[mapping.count].thb_table = next_mapping_offset; - mapping.disks[mapping.count].sector_table = disk_load_thb_table(mapping.disks[mapping.count].thb_table, disk_parameters); - next_mapping_offset = disk_sectors_start(mapping.disks[mapping.count].sector_table); - if (file_get_sectors(disk_path, disk_sectors_callback)) { + for (mapping.count = 0; mapping.count < 1; mapping.count++) { + disk_load_thb_table(disk_parameters++, &mapping.disks[mapping.count].thb_table, &mapping_offset); + if (disk_load_sector_table(disk_path++, &mapping.disks[mapping.count].sector_table, &mapping_offset)) { return FLASHCART_ERR_LOAD; } - mapping.count += 1; - // LOOP END + } if (mapping.count == 0) { return FLASHCART_ERR_ARGS; @@ -498,8 +488,6 @@ static flashcart_err_t sc64_load_64dd_disk (char *disk_path, flashcart_disk_para return FLASHCART_ERR_INT; } - sc64_drive_type_t drive_type = disk_parameters->development_drive ? DRIVE_TYPE_DEVELOPMENT : DRIVE_TYPE_RETAIL; - const struct { sc64_cfg_id_t id; uint32_t value; @@ -559,7 +547,13 @@ static flashcart_err_t sc64_set_save_type (flashcart_save_type_t save_type) { return FLASHCART_OK; } -static flashcart_err_t sc64_set_save_writeback (uint32_t *sectors) { +static flashcart_err_t sc64_set_save_writeback (char *save_path) { + uint32_t sectors[SAVE_WRITEBACK_MAX_SECTORS] __attribute__((aligned(8))); + + if (fatfs_get_file_sectors(save_path, sectors, ADDRESS_TYPE_MEM, SAVE_WRITEBACK_MAX_SECTORS)) { + return FLASHCART_ERR_LOAD; + } + pi_dma_write_data(sectors, SC64_BUFFERS->BUFFER, 1024); if (sc64_ll_writeback_enable(SC64_BUFFERS->BUFFER) != SC64_OK) { diff --git a/src/libs/miniz b/src/libs/miniz index 18795fa6..16413c21 160000 --- a/src/libs/miniz +++ b/src/libs/miniz @@ -1 +1 @@ -Subproject commit 18795fa61e590521381ba9e1fa4a4ab362b095f6 +Subproject commit 16413c213de38e703d883006193734e8b1178d5d diff --git a/src/menu/cart_load.c b/src/menu/cart_load.c index 32bf4a28..13ef7aca 100644 --- a/src/menu/cart_load.c +++ b/src/menu/cart_load.c @@ -112,7 +112,7 @@ cart_load_err_t cart_load_64dd_ipl_and_disk (menu_t *menu, flashcart_progress_ca return CART_LOAD_ERR_EXP_PAK_NOT_FOUND; } - path_t *path = path_init("sd:/", DDIPL_LOCATION); + path_t *path = path_init(menu->storage_prefix, DDIPL_LOCATION); flashcart_disk_parameters_t disk_parameters; disk_parameters.development_drive = (menu->load.disk_info.region == DISK_REGION_DEVELOPMENT); @@ -154,7 +154,7 @@ cart_load_err_t cart_load_64dd_ipl_and_disk (menu_t *menu, flashcart_progress_ca } cart_load_err_t cart_load_emulator (menu_t *menu, cart_load_emu_type_t emu_type, flashcart_progress_callback_t progress) { - path_t *path = path_init("sd:/", EMU_LOCATION); + path_t *path = path_init(menu->storage_prefix, EMU_LOCATION); flashcart_save_type_t save_type = FLASHCART_SAVE_TYPE_NONE; uint32_t emulated_rom_offset = 0x200000; diff --git a/src/menu/components.h b/src/menu/components.h index a8cecb51..cff0f4c2 100644 --- a/src/menu/components.h +++ b/src/menu/components.h @@ -64,7 +64,7 @@ typedef struct { surface_t *image; } component_boxart_t; -component_boxart_t *component_boxart_init (char *game_code); +component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code); void component_boxart_free (component_boxart_t *b); void component_boxart_draw (component_boxart_t *b); diff --git a/src/menu/components/background.c b/src/menu/components/background.c index ac838098..45119107 100644 --- a/src/menu/components/background.c +++ b/src/menu/components/background.c @@ -1,4 +1,4 @@ -#include +#include #include #include "../components.h" @@ -31,22 +31,21 @@ static void load_from_cache (component_background_t *c) { return; } - FIL fil; - UINT bytes_read; + FILE *f; - if (f_open(&fil, strip_sd_prefix(c->cache_location), FA_READ) != FR_OK) { + if ((f = fopen(c->cache_location, "rb")) == NULL) { return; } cache_metadata_t cache_metadata; - if ((f_read(&fil, &cache_metadata, sizeof(cache_metadata), &bytes_read) != FR_OK) || (bytes_read != sizeof(cache_metadata))) { - f_close(&fil); + if (fread(&cache_metadata, sizeof(cache_metadata), 1, f) != 1) { + fclose(f); return; } if (cache_metadata.magic != CACHE_METADATA_MAGIC || cache_metadata.width > DISPLAY_WIDTH || cache_metadata.height > DISPLAY_HEIGHT) { - f_close(&fil); + fclose(f); return; } @@ -57,17 +56,17 @@ static void load_from_cache (component_background_t *c) { surface_free(c->image); free(c->image); c->image = NULL; - f_close(&fil); + fclose(f); return; } - if ((f_read(&fil, c->image->buffer, cache_metadata.size, &bytes_read) != FR_OK) || (bytes_read != cache_metadata.size)) { + if (fread(c->image->buffer, cache_metadata.size, 1, f) != 1) { surface_free(c->image); free(c->image); c->image = NULL; } - f_close(&fil); + fclose(f); } static void save_to_cache (component_background_t *c) { @@ -75,9 +74,9 @@ static void save_to_cache (component_background_t *c) { return; } - FIL fil; - UINT bytes_written; - if (f_open(&fil, strip_sd_prefix(c->cache_location), FA_WRITE | FA_CREATE_ALWAYS) != FR_OK) { + FILE *f; + + if ((f = fopen(c->cache_location, "wb")) == NULL) { return; } @@ -88,10 +87,10 @@ static void save_to_cache (component_background_t *c) { .size = (c->image->height * c->image->stride), }; - f_write(&fil, &cache_metadata, sizeof(cache_metadata), &bytes_written); - f_write(&fil, c->image->buffer, cache_metadata.size, &bytes_written); + fwrite(&cache_metadata, sizeof(cache_metadata), 1, f); + fwrite(c->image->buffer, cache_metadata.size, 1, f); - f_close(&fil); + fclose(f); } static void prepare_background (component_background_t *c) { @@ -166,7 +165,7 @@ static void display_list_free (void *arg) { void component_background_init (char *cache_location) { if (!background) { background = calloc(1, sizeof(component_background_t)); - background->cache_location = cache_location; + background->cache_location = strdup(cache_location); load_from_cache(background); prepare_background(background); } @@ -183,6 +182,9 @@ void component_background_free (void) { rdpq_call_deferred(display_list_free, background->image_display_list); background->image_display_list = NULL; } + if (background->cache_location) { + free(background->cache_location); + } free(background); background = NULL; } diff --git a/src/menu/components/boxart.c b/src/menu/components/boxart.c index 6ed3826a..54121b6e 100644 --- a/src/menu/components/boxart.c +++ b/src/menu/components/boxart.c @@ -6,9 +6,8 @@ #include "constants.h" #include "utils/fs.h" -#ifndef BOXART_DIRECTORY -#define BOXART_DIRECTORY "sd:/menu/boxart" -#endif + +#define BOXART_DIRECTORY "menu/boxart" static void png_decoder_callback (png_err_t err, surface_t *decoded_image, void *callback_data) { @@ -18,25 +17,38 @@ static void png_decoder_callback (png_err_t err, surface_t *decoded_image, void } -component_boxart_t *component_boxart_init (char *game_code) { - component_boxart_t *b = calloc(1, sizeof(component_boxart_t)); +component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code) { + component_boxart_t *b; + char file_name[8]; - if (b) { - b->loading = true; - char *path = alloca(strlen(BOXART_DIRECTORY) + 1 + 7 + 1); - - // TODO: This is bad, we should only check for 3 letter codes - sprintf(path, "%s/%.3s.png", BOXART_DIRECTORY, game_code); - if (png_decoder_start(path, BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) != PNG_OK) { - sprintf(path, "%s/%.2s.png", BOXART_DIRECTORY, &game_code[1]); - if (png_decoder_start(path, BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) != PNG_OK) { - free(b); - b = NULL; - } - } + if ((b = calloc(1, sizeof(component_boxart_t))) == NULL) { + return NULL; } - return b; + b->loading = true; + + path_t *path = path_init(storage_prefix, BOXART_DIRECTORY); + + sprintf(file_name, "%.3s.png", game_code); + path_push(path, file_name); + if (png_decoder_start(path_get(path), BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) == PNG_OK) { + path_free(path); + return b; + } + path_pop(path); + + // TODO: This is bad, we should only check for 3 letter codes + sprintf(file_name, "%.2s.png", game_code + 1); + path_push(path, file_name); + if (png_decoder_start(path_get(path), BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) == PNG_OK) { + path_free(path); + return b; + } + + path_free(path); + free(b); + + return NULL; } void component_boxart_free (component_boxart_t *b) { diff --git a/src/menu/components/common.c b/src/menu/components/common.c index 5440cb08..66db56eb 100644 --- a/src/menu/components/common.c +++ b/src/menu/components/common.c @@ -121,6 +121,10 @@ void component_messagebox_draw (char *fmt, ...) { .wrap = WRAP_WORD }, FNT_DEFAULT, formatted, ¶graph_nbytes); + if (formatted != buffer) { + free(formatted); + } + component_dialog_draw( paragraph->bbox.x1 - paragraph->bbox.x0 + MESSAGEBOX_MARGIN, paragraph->bbox.y1 - paragraph->bbox.y0 + MESSAGEBOX_MARGIN @@ -154,6 +158,10 @@ void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *f formatted, nbytes ); + + if (formatted != buffer) { + free(formatted); + } } void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...) { @@ -179,4 +187,8 @@ void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign, formatted, nbytes ); + + if (formatted != buffer) { + free(formatted); + } } diff --git a/src/menu/components/file_list.c b/src/menu/components/file_list.c index 1de3cd19..300b46ff 100644 --- a/src/menu/components/file_list.c +++ b/src/menu/components/file_list.c @@ -8,15 +8,19 @@ static const char *dir_prefix = "/"; -static int format_file_size (char *buffer, int size) { - if (size < 8 * 1024) { - return sprintf(buffer, "%d B", size); +static int format_file_size (char *buffer, int64_t size) { + if (size < 0) { + return sprintf(buffer, "unknown"); + } else if (size == 0) { + return sprintf(buffer, "empty"); + } else if (size < 8 * 1024) { + return sprintf(buffer, "%lld B", size); } else if (size < 8 * 1024 * 1024) { - return sprintf(buffer, "%d kB", size / 1024); + return sprintf(buffer, "%lld kB", size / 1024); } else if (size < 1 * 1024 * 1024 * 1024) { - return sprintf(buffer, "%d MB", size / 1024 / 1024); + return sprintf(buffer, "%lld MB", size / 1024 / 1024); } else { - return sprintf(buffer, "%d GB", size / 1024 / 1024 / 1024); + return sprintf(buffer, "%lld GB", size / 1024 / 1024 / 1024); } } @@ -139,7 +143,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) { NULL ); - char file_size[8]; + char file_size[16]; for (int i = starting_position; i < entries; i++) { entry_t *entry = &list[i]; diff --git a/src/menu/disk_info.c b/src/menu/disk_info.c index 32680a3b..5c2c7388 100644 --- a/src/menu/disk_info.c +++ b/src/menu/disk_info.c @@ -1,6 +1,6 @@ -#include #include #include +#include #include #include "disk_info.h" @@ -39,18 +39,14 @@ static const int disk_id_lbas[DISK_ID_LBA_COUNT] = { }; -static bool load_system_area_lba (FIL *fil, int lba, uint8_t *buffer) { - UINT bytes_read; +static bool load_system_area_lba (FILE *f, int lba, uint8_t *buffer) { if (lba >= SYSTEM_AREA_LBA_COUNT) { return true; } - if (f_lseek(fil, SYSTEM_AREA_LBA_LENGTH * lba) != FR_OK) { + if (fseek(f, SYSTEM_AREA_LBA_LENGTH * lba, SEEK_SET)) { return true; } - if (f_read(fil, buffer, SYSTEM_AREA_LBA_LENGTH, &bytes_read) != FR_OK) { - return true; - } - if (bytes_read != SYSTEM_AREA_LBA_LENGTH) { + if (fread(buffer, SYSTEM_AREA_LBA_LENGTH, 1, f) != 1) { return true; } return false; @@ -113,7 +109,7 @@ static void update_bad_system_area_lbas (disk_info_t *disk_info) { } } -static disk_err_t load_and_verify_system_data_lba (FIL *fil, disk_info_t *disk_info) { +static disk_err_t load_and_verify_system_data_lba (FILE *f, disk_info_t *disk_info) { uint8_t buffer[SYSTEM_AREA_LBA_LENGTH]; int sector_length; @@ -122,7 +118,7 @@ static disk_err_t load_and_verify_system_data_lba (FIL *fil, disk_info_t *disk_i for (int i = 0; i < SYSTEM_DATA_LBA_COUNT; i++) { int lba = system_data_lbas[i]; - if (load_system_area_lba(fil, lba, buffer)) { + if (load_system_area_lba(f, lba, buffer)) { return DISK_ERR_IO; } @@ -155,7 +151,7 @@ static disk_err_t load_and_verify_system_data_lba (FIL *fil, disk_info_t *disk_i return valid_system_data_lba_found ? DISK_OK : DISK_ERR_INVALID; } -static disk_err_t load_and_verify_disk_id_lba (FIL *fil, disk_info_t *disk_info) { +static disk_err_t load_and_verify_disk_id_lba (FILE *f, disk_info_t *disk_info) { uint8_t buffer[SYSTEM_AREA_LBA_LENGTH]; bool valid_disk_id_lba_found = false; @@ -163,7 +159,7 @@ static disk_err_t load_and_verify_disk_id_lba (FIL *fil, disk_info_t *disk_info) for (int i = 0; i < DISK_ID_LBA_COUNT; i++) { int lba = disk_id_lbas[i]; - if (load_system_area_lba(fil, lba, buffer)) { + if (load_system_area_lba(f, lba, buffer)) { return DISK_ERR_IO; } @@ -180,29 +176,27 @@ static disk_err_t load_and_verify_disk_id_lba (FIL *fil, disk_info_t *disk_info) } -disk_err_t disk_info_load (char *path, disk_info_t *disk_info) { - FIL fil; +disk_err_t disk_info_load (path_t *path, disk_info_t *disk_info) { + FILE *f; disk_err_t err; for (int i = 0; i < SYSTEM_AREA_LBA_COUNT; i++) { disk_info->bad_system_area_lbas[i] = false; } - if (f_open(&fil, strip_sd_prefix(path), FA_READ) != FR_OK) { + if ((f = fopen(path_get(path), "rb")) == NULL) { return DISK_ERR_NO_FILE; } - - if ((err = load_and_verify_system_data_lba(&fil, disk_info)) != DISK_OK) { - f_close(&fil); + setbuf(f, NULL); + if ((err = load_and_verify_system_data_lba(f, disk_info)) != DISK_OK) { + fclose(f); return err; } - - if ((err = load_and_verify_disk_id_lba(&fil, disk_info)) != DISK_OK) { - f_close(&fil); + if ((err = load_and_verify_disk_id_lba(f, disk_info)) != DISK_OK) { + fclose(f); return err; } - - if (f_close(&fil) != FR_OK) { + if (fclose(f)) { return DISK_ERR_IO; } diff --git a/src/menu/disk_info.h b/src/menu/disk_info.h index ae35be69..fe5175ec 100644 --- a/src/menu/disk_info.h +++ b/src/menu/disk_info.h @@ -11,6 +11,9 @@ #include #include +#include "path.h" + + /** @brief Disk state enumeration. */ typedef enum { DISK_OK, @@ -49,7 +52,7 @@ typedef struct { } disk_info_t; -disk_err_t disk_info_load (char *path, disk_info_t *disk_info); +disk_err_t disk_info_load (path_t *path, disk_info_t *disk_info); #endif diff --git a/src/menu/fonts.c b/src/menu/fonts.c index e8eccc95..f694ab80 100644 --- a/src/menu/fonts.c +++ b/src/menu/fonts.c @@ -1,10 +1,17 @@ #include #include "fonts.h" +#include "utils/fs.h" -static void load_default_font (void) { - rdpq_font_t *default_font = rdpq_font_load("rom:/FiraMonoBold.font64"); +static void load_default_font (char *custom_font_path) { + char *font_path = "rom:/FiraMonoBold.font64"; + + if (custom_font_path && file_exists(custom_font_path)) { + font_path = custom_font_path; + } + + rdpq_font_t *default_font = rdpq_font_load(font_path); rdpq_font_style(default_font, STL_DEFAULT, &((rdpq_fontstyle_t) { .color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF) })); rdpq_font_style(default_font, STL_GREEN, &((rdpq_fontstyle_t) { .color = RGBA32(0x70, 0xFF, 0x70, 0xFF) })); @@ -17,6 +24,6 @@ static void load_default_font (void) { } -void fonts_init (void) { - load_default_font(); +void fonts_init (char *custom_font_path) { + load_default_font(custom_font_path); } diff --git a/src/menu/fonts.h b/src/menu/fonts.h index 2ddade59..9fb00092 100644 --- a/src/menu/fonts.h +++ b/src/menu/fonts.h @@ -23,7 +23,7 @@ typedef enum { } menu_font_style_t; -void fonts_init (void); +void fonts_init (char *custom_font_path); #endif diff --git a/src/menu/menu.c b/src/menu/menu.c index 1b8832cb..beef676d 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -20,12 +20,15 @@ #include "views/views.h" -#define MENU_DIRECTORY "sd:/menu" -#define CACHE_DIRECTORY "sd:/menu/cache" -#define BACKGROUND_CACHE "sd:/menu/cache/background.data" +#define MENU_DIRECTORY "/menu" +#define MENU_SETTINGS_FILE "config.ini" +#define MENU_CUSTOM_FONT_FILE "custom.font64" -#define FRAMERATE_DIVIDER (2) -#define LAG_REPORT (false) +#define MENU_CACHE_DIRECTORY "cache" +#define BACKGROUND_CACHE_FILE "background.data" + +#define FRAMERATE_DIVIDER (2) +#define LAG_REPORT (false) static menu_t *menu; @@ -62,7 +65,6 @@ static void menu_init (boot_params_t *boot_params) { rdpq_init(); dfs_init(DFS_DEFAULT_LOCATION); - fonts_init(); sound_init_default(); menu = calloc(1, sizeof(menu_t)); @@ -71,31 +73,39 @@ static void menu_init (boot_params_t *boot_params) { menu->mode = MENU_MODE_NONE; menu->next_mode = MENU_MODE_STARTUP; - menu->flashcart_err = flashcart_init(); + menu->flashcart_err = flashcart_init(&menu->storage_prefix); if (menu->flashcart_err != FLASHCART_OK) { menu->next_mode = MENU_MODE_FAULT; } - menu->error_message = NULL; + path_t *path = path_init(menu->storage_prefix, MENU_DIRECTORY); - directory_create(MENU_DIRECTORY); + directory_create(path_get(path)); + path_push(path, MENU_SETTINGS_FILE); + settings_init(path_get(path)); settings_load(&menu->settings); + path_pop(path); - directory_create(CACHE_DIRECTORY); + path_push(path, MENU_CUSTOM_FONT_FILE); + fonts_init(path_get(path)); + path_pop(path); - component_background_init(BACKGROUND_CACHE); + path_push(path, MENU_CACHE_DIRECTORY); + directory_create(path_get(path)); + + path_push(path, BACKGROUND_CACHE_FILE); + component_background_init(path_get(path)); + + path_free(path); menu->boot_params = boot_params; - bool default_directory_exists = directory_exists(menu->settings.default_directory); - char *init_directory = default_directory_exists ? menu->settings.default_directory : ""; - - menu->browser.valid = false; - menu->browser.reload = false; - menu->browser.directory = path_init("sd:/", init_directory); - - menu->load.rom_path = NULL; + menu->browser.directory = path_init(menu->storage_prefix, menu->settings.default_directory); + if (!directory_exists(path_get(menu->browser.directory))) { + path_free(menu->browser.directory); + menu->browser.directory = path_init(menu->storage_prefix, "/"); + } hdmi_clear_game_id(); @@ -118,6 +128,12 @@ static void menu_deinit (menu_t *menu) { hdmi_send_game_id(menu->boot_params); + path_free(menu->load.disk_path); + path_free(menu->load.rom_path); + for (int i = 0; i < menu->browser.entries; i++) { + free(menu->browser.list[i].name); + } + free(menu->browser.list); path_free(menu->browser.directory); free(menu); diff --git a/src/menu/menu_state.h b/src/menu/menu_state.h index 265443fe..c2ad221d 100644 --- a/src/menu/menu_state.h +++ b/src/menu/menu_state.h @@ -18,9 +18,6 @@ #include "settings.h" -#define BROWSER_LIST_SIZE 2048 - - /** @brief Menu mode enumeration */ typedef enum { MENU_MODE_NONE, @@ -60,7 +57,7 @@ typedef enum { typedef struct { char *name; entry_type_t type; - int size; + int64_t size; } entry_t; /** @brief Menu Structure */ @@ -68,6 +65,7 @@ typedef struct { menu_mode_t mode; menu_mode_t next_mode; + const char *storage_prefix; settings_t settings; boot_params_t *boot_params; @@ -93,7 +91,7 @@ typedef struct { bool valid; bool reload; path_t *directory; - entry_t list[BROWSER_LIST_SIZE]; + entry_t *list; int entries; entry_t *entry; int selected; diff --git a/src/menu/mp3_player.c b/src/menu/mp3_player.c index 87109a3e..b41a170c 100644 --- a/src/menu/mp3_player.c +++ b/src/menu/mp3_player.c @@ -1,4 +1,6 @@ -#include +#include +#include + #include #include "mp3_player.h" @@ -18,19 +20,18 @@ /** @brief MP3 File Information Structure. */ typedef struct { bool loaded; - bool io_error; - - FIL fil; - FSIZE_t data_start; - int seek_predecode_frames; - - mp3dec_t dec; - mp3dec_frame_info_t info; + FILE *f; + size_t file_size; + size_t data_start; uint8_t buffer[16 * 1024]; uint8_t *buffer_ptr; size_t buffer_left; + mp3dec_t dec; + mp3dec_frame_info_t info; + + int seek_predecode_frames; float duration; float bitrate; @@ -48,9 +49,7 @@ static void mp3player_reset_decoder (void) { } static void mp3player_fill_buffer (void) { - UINT bytes_read; - - if (f_eof(&p->fil)) { + if (feof(p->f)) { return; } @@ -63,11 +62,7 @@ static void mp3player_fill_buffer (void) { p->buffer_ptr = p->buffer; } - if (f_read(&p->fil, p->buffer + p->buffer_left, sizeof(p->buffer) - p->buffer_left, &bytes_read) == FR_OK) { - p->buffer_left += bytes_read; - } else { - p->io_error = true; - } + p->buffer_left += fread(p->buffer + p->buffer_left, 1, sizeof(p->buffer) - p->buffer_left, p->f); } static void mp3player_wave_read (void *ctx, samplebuffer_t *sbuf, int wpos, int wlen, bool seeking) { @@ -109,7 +104,7 @@ static void mp3player_calculate_duration (int samples) { uint32_t frames; int delay, padding; - long data_size = (f_size(&p->fil) - p->data_start); + long data_size = (p->file_size - p->data_start); if (mp3dec_check_vbrtag((const uint8_t *) (p->buffer_ptr), p->info.frame_bytes, &frames, &delay, &padding) > 0) { p->duration = (frames * samples) / (float) (p->info.hz); p->bitrate = (data_size * 8) / p->duration; @@ -137,7 +132,6 @@ mp3player_err_t mp3player_init (void) { mp3player_reset_decoder(); p->loaded = false; - p->io_error = false; p->wave = (waveform_t) { .name = "mp3player", @@ -164,22 +158,32 @@ mp3player_err_t mp3player_load (char *path) { mp3player_unload(); } - if (f_open(&p->fil, strip_sd_prefix(path), FA_READ) != FR_OK) { + if ((p->f = fopen(path, "rb")) == NULL) { return MP3PLAYER_ERR_IO; } + setbuf(p->f, NULL); + + struct stat st; + if (fstat(fileno(p->f), &st)) { + fclose(p->f); + return MP3PLAYER_ERR_IO; + } + p->file_size = st.st_size; mp3player_reset_decoder(); - while (!(f_eof(&p->fil) && p->buffer_left == 0)) { + while (!(feof(p->f) && p->buffer_left == 0)) { mp3player_fill_buffer(); - if (p->io_error) { + if (ferror(p->f)) { + fclose(p->f); return MP3PLAYER_ERR_IO; } size_t id3v2_skip = mp3dec_skip_id3v2((const uint8_t *) (p->buffer_ptr), p->buffer_left); if (id3v2_skip > 0) { - if (f_lseek(&p->fil, f_tell(&p->fil) - p->buffer_left + id3v2_skip) != FR_OK) { + if (fseek(p->f, (-p->buffer_left) + id3v2_skip, SEEK_CUR)) { + fclose(p->f); return MP3PLAYER_ERR_IO; } mp3player_reset_decoder(); @@ -189,7 +193,7 @@ mp3player_err_t mp3player_load (char *path) { int samples = mp3dec_decode_frame(&p->dec, p->buffer_ptr, p->buffer_left, NULL, &p->info); if (samples > 0) { p->loaded = true; - p->data_start = f_tell(&p->fil) - p->buffer_left + p->info.frame_offset; + p->data_start = ftell(p->f) - p->buffer_left + p->info.frame_offset; p->buffer_ptr += p->info.frame_offset; p->buffer_left -= p->info.frame_offset; @@ -206,7 +210,7 @@ mp3player_err_t mp3player_load (char *path) { p->buffer_left -= p->info.frame_bytes; } - if (f_close(&p->fil) != FR_OK) { + if (fclose(p->f)) { return MP3PLAYER_ERR_IO; } @@ -217,12 +221,12 @@ void mp3player_unload (void) { mp3player_stop(); if (p->loaded) { p->loaded = false; - f_close(&p->fil); + fclose(p->f); } } mp3player_err_t mp3player_process (void) { - if (p->io_error) { + if (ferror(p->f)) { mp3player_unload(); return MP3PLAYER_ERR_IO; } @@ -239,7 +243,7 @@ bool mp3player_is_playing (void) { } bool mp3player_is_finished (void) { - return p->loaded && f_eof(&p->fil) && (p->buffer_left == 0); + return p->loaded && feof(p->f) && (p->buffer_left == 0); } mp3player_err_t mp3player_play (void) { @@ -248,8 +252,7 @@ mp3player_err_t mp3player_play (void) { } if (!mp3player_is_playing()) { if (mp3player_is_finished()) { - if (f_lseek(&p->fil, p->data_start) != FR_OK) { - p->io_error = true; + if (fseek(p->f, p->data_start, SEEK_SET)) { return MP3PLAYER_ERR_IO; } mp3player_reset_decoder(); @@ -292,25 +295,24 @@ mp3player_err_t mp3player_seek (int seconds) { return MP3PLAYER_OK; } - long position = ((long) (f_tell(&p->fil)) - p->buffer_left + bytes_to_move); + long position = (ftell(p->f) - p->buffer_left + bytes_to_move); if (position < (long) (p->data_start)) { position = p->data_start; } - if (f_lseek(&p->fil, position) != FR_OK) { - p->io_error = true; + if (fseek(p->f, position, SEEK_SET)) { return MP3PLAYER_ERR_IO; } mp3player_reset_decoder(); mp3player_fill_buffer(); - p->seek_predecode_frames = (position == p->data_start) ? 0 : SEEK_PREDECODE_FRAMES; - - if (p->io_error) { + if (ferror(p->f)) { return MP3PLAYER_ERR_IO; } + p->seek_predecode_frames = (position == p->data_start) ? 0 : SEEK_PREDECODE_FRAMES; + return MP3PLAYER_OK; } @@ -346,9 +348,9 @@ float mp3player_get_progress (void) { return 0.0f; } - FSIZE_t data_size = f_size(&p->fil) - p->data_start; - FSIZE_t data_consumed = f_tell(&p->fil) - p->buffer_left; - FSIZE_t data_position = (data_consumed > p->data_start) ? (data_consumed - p->data_start) : 0; + long data_size = p->file_size - p->data_start; + long data_consumed = ftell(p->f) - p->buffer_left; + long data_position = (data_consumed > p->data_start) ? (data_consumed - p->data_start) : 0; return data_position / (float) (data_size); } diff --git a/src/menu/path.c b/src/menu/path.c index 58638dea..59f622f2 100644 --- a/src/menu/path.c +++ b/src/menu/path.c @@ -19,7 +19,7 @@ static void path_resize (path_t *path, size_t min_length) { assert(path->buffer != NULL); } -static path_t *path_create (char *string) { +static path_t *path_create (const char *string) { if (string == NULL) { string = ""; } @@ -43,7 +43,7 @@ static void path_append (path_t *path, char *string) { } -path_t *path_init (char *prefix, char *string) { +path_t *path_init (const char *prefix, char *string) { path_t *path = path_create(prefix); size_t prefix_length = strlen(prefix); if ((prefix_length > 0) && (prefix[prefix_length - 1] == '/')) { diff --git a/src/menu/path.h b/src/menu/path.h index 3bf51950..df2672d2 100644 --- a/src/menu/path.h +++ b/src/menu/path.h @@ -20,7 +20,7 @@ typedef struct { } path_t; -path_t *path_init (char *prefix, char *string); +path_t *path_init (const char *prefix, char *string); void path_free (path_t *path); path_t *path_clone (path_t *string); path_t *path_clone_push (path_t *path, char *string); diff --git a/src/menu/png_decoder.c b/src/menu/png_decoder.c index 54d185d3..14dbf8ed 100644 --- a/src/menu/png_decoder.c +++ b/src/menu/png_decoder.c @@ -1,4 +1,5 @@ -#include +#include + #include #include "png_decoder.h" @@ -7,7 +8,7 @@ /** @brief PNG File Information Structure. */ typedef struct { - FIL fil; + FILE *f; spng_ctx *ctx; struct spng_ihdr ihdr; @@ -25,7 +26,7 @@ static png_decoder_t *decoder; static void png_decoder_deinit (bool free_image) { if (decoder != NULL) { - f_close(&decoder->fil); + fclose(decoder->f); if (decoder->ctx != NULL) { spng_ctx_free(decoder->ctx); } @@ -41,21 +42,6 @@ static void png_decoder_deinit (bool free_image) { } } -static int png_file_read (spng_ctx *ctx, void *user, void *dst_src, size_t length) { - UINT bytes_read = 0; - png_decoder_t *d = (png_decoder_t *) (user); - - if (f_read(&d->fil, dst_src, length, &bytes_read) != FR_OK) { - return SPNG_IO_ERROR; - } - - if (bytes_read != length) { - return SPNG_EOF; - } - - return SPNG_OK; -} - png_err_t png_decoder_start (char *path, int max_width, int max_height, png_callback_t *callback, void *callback_data) { if (decoder != NULL) { @@ -67,11 +53,13 @@ png_err_t png_decoder_start (char *path, int max_width, int max_height, png_call return PNG_ERR_OUT_OF_MEM; } - if (f_open(&decoder->fil, strip_sd_prefix(path), FA_READ) != FR_OK) { + if ((decoder->f = fopen(path, "rb")) == NULL) { png_decoder_deinit(false); return PNG_ERR_NO_FILE; } + setbuf(decoder->f, NULL); + if ((decoder->ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32)) == NULL) { png_decoder_deinit(false); return PNG_ERR_OUT_OF_MEM; @@ -87,7 +75,7 @@ png_err_t png_decoder_start (char *path, int max_width, int max_height, png_call return PNG_ERR_INT; } - if (spng_set_png_stream(decoder->ctx, png_file_read, decoder) != SPNG_OK) { + if (spng_set_png_file(decoder->ctx, decoder->f) != SPNG_OK) { png_decoder_deinit(false); return PNG_ERR_INT; } diff --git a/src/menu/rom_info.c b/src/menu/rom_info.c index 6a2223c5..b9711701 100644 --- a/src/menu/rom_info.c +++ b/src/menu/rom_info.c @@ -1,6 +1,6 @@ +#include #include -#include #include #include "boot/cic.h" @@ -829,7 +829,7 @@ static rom_err_t save_override (path_t *path, const char *id, int value, int def mini_free(ini); if (empty) { - if (file_delete(path_get(overrides_path))) { + if (remove(path_get(overrides_path))) { path_free(overrides_path); return ROM_ERR_IO; } @@ -912,21 +912,18 @@ rom_err_t rom_info_override_tv_type (path_t *path, rom_info_t *rom_info, rom_tv_ } rom_err_t rom_info_load (path_t *path, rom_info_t *rom_info) { - FIL fil; - UINT br; + FILE *f; rom_header_t rom_header; - if (f_open(&fil, strip_sd_prefix(path_get(path)), FA_READ) != FR_OK) { + if ((f = fopen(path_get(path), "rb")) == NULL) { return ROM_ERR_NO_FILE; } - if (f_read(&fil, &rom_header, sizeof(rom_header), &br) != FR_OK) { - f_close(&fil); + setbuf(f, NULL); + if (fread(&rom_header, sizeof(rom_header), 1, f) != 1) { + fclose(f); return ROM_ERR_IO; } - if (f_close(&fil) != FR_OK) { - return ROM_ERR_IO; - } - if (br != sizeof(rom_header)) { + if (fclose(f)) { return ROM_ERR_IO; } diff --git a/src/menu/settings.c b/src/menu/settings.c index 6f8dba03..3e046ed0 100644 --- a/src/menu/settings.c +++ b/src/menu/settings.c @@ -5,14 +5,12 @@ #include "utils/fs.h" -#ifndef SETTINGS_FILE_PATH -#define SETTINGS_FILE_PATH "sd:/menu/config.ini" -#endif +static char *settings_path = NULL; static settings_t init = { .pal60_enabled = false, - .hidden_files_enabled = false, + .show_protected_entries = false, .default_directory = "/", .use_saves_folder = true, @@ -23,15 +21,22 @@ static settings_t init = { }; +void settings_init (char *path) { + if (settings_path) { + free(settings_path); + } + settings_path = strdup(path); +} + void settings_load (settings_t *settings) { - if (!file_exists(SETTINGS_FILE_PATH)) { + if (!file_exists(settings_path)) { settings_save(&init); } - mini_t *ini = mini_try_load(SETTINGS_FILE_PATH); + mini_t *ini = mini_try_load(settings_path); settings->pal60_enabled = mini_get_bool(ini, "menu", "pal60", init.pal60_enabled); // TODO: consider changing file setting name - settings->hidden_files_enabled = mini_get_bool(ini, "menu", "show_hidden_files", init.hidden_files_enabled); + settings->show_protected_entries = mini_get_bool(ini, "menu", "show_protected_entries", init.show_protected_entries); settings->default_directory = strdup(mini_get_string(ini, "menu", "default_directory", init.default_directory)); settings->use_saves_folder = mini_get_bool(ini, "menu", "use_saves_folder", init.use_saves_folder); @@ -44,10 +49,10 @@ void settings_load (settings_t *settings) { } void settings_save (settings_t *settings) { - mini_t *ini = mini_create(SETTINGS_FILE_PATH); + mini_t *ini = mini_create(settings_path); mini_set_bool(ini, "menu", "pal60", settings->pal60_enabled); - mini_set_bool(ini, "menu", "show_hidden_files", settings->hidden_files_enabled); + mini_set_bool(ini, "menu", "show_protected_entries", settings->show_protected_entries); mini_set_string(ini, "menu", "default_directory", settings->default_directory); mini_set_bool(ini, "menu", "use_saves_folder", settings->use_saves_folder); diff --git a/src/menu/settings.h b/src/menu/settings.h index d2529998..e9931b05 100644 --- a/src/menu/settings.h +++ b/src/menu/settings.h @@ -13,8 +13,8 @@ typedef struct { /** @brief Use 60 Hz refresh rate on a PAL console */ bool pal60_enabled; - /** @brief Show files marked as hidden in the browser */ - bool hidden_files_enabled; + /** @brief Show files/directories that are filtered in the browser */ + bool show_protected_entries; /** @brief Default directory to navigate to when menu loads */ char *default_directory; @@ -33,6 +33,8 @@ typedef struct { } settings_t; +/** @brief Init settings path */ +void settings_init (char *path); /** @brief The settings to load */ void settings_load (settings_t *settings); /** @brief The settings to save */ diff --git a/src/menu/usb_comm.c b/src/menu/usb_comm.c index fd218cd3..743ddee2 100644 --- a/src/menu/usb_comm.c +++ b/src/menu/usb_comm.c @@ -2,8 +2,9 @@ // 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 #include "usb_comm.h" @@ -73,15 +74,12 @@ static void command_reboot (menu_t *menu) { }; static void command_send_file (menu_t *menu) { - char path[256]; + FILE *f; + char buffer[256]; + uint8_t data[8192]; char length[8]; - FIL f; - int remaining; - uint8_t data[8192]; - UINT bytes_written; - - if (usb_comm_read_string(path, sizeof(path), ' ')) { + if (usb_comm_read_string(buffer, sizeof(buffer), ' ')) { return usb_comm_send_error("Invalid path argument\n"); } @@ -93,11 +91,16 @@ static void command_send_file (menu_t *menu) { return usb_comm_send_error("Invalid file length argument\n"); } - if (f_open(&f, path, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK) { + path_t *path = path_init(menu->storage_prefix, buffer); + + if ((f = fopen(path_get(path), "wb")) == NULL) { + path_free(path); return usb_comm_send_error("Couldn't create file\n"); } + setbuf(f, NULL); + path_free(path); - remaining = atoi(length); + int remaining = atoi(length); if (remaining > MAX_FILE_SIZE) { return usb_comm_send_error("File size too big\n"); @@ -106,18 +109,14 @@ static void command_send_file (menu_t *menu) { 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); + if (fwrite(data, 1, block_size, f) != block_size) { + fclose(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) { + if (fclose(f)) { return usb_comm_send_error("Couldn't flush data to the file\n"); } diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c index 00061d67..8cca0201 100644 --- a/src/menu/views/browser.c +++ b/src/menu/views/browser.c @@ -1,9 +1,8 @@ #include #include +#include #include -#include - #include "../fonts.h" #include "utils/fs.h" #include "views.h" @@ -17,6 +16,17 @@ static const char *image_extensions[] = { "png", NULL }; static const char *text_extensions[] = { "txt", "ini", "yml", "yaml", NULL }; static const char *music_extensions[] = { "mp3", NULL }; +static const char *hidden_paths[] = { + "/menu.bin", + "/menu", + "/N64FlashcartMenu.n64", + "/OS64.v64", + "/OS64P.v64", + "/sc64menu.n64", + "/System Volume Information", + NULL +}; + static int compare_entry (const void *pa, const void *pb) { entry_t *a = (entry_t *) (pa); @@ -61,74 +71,87 @@ static int compare_entry (const void *pa, const void *pb) { return strcasecmp((const char *) (a->name), (const char *) (b->name)); } -static bool load_directory (menu_t *menu) { - DIR dir; - FILINFO info; - +static void browser_list_free (menu_t *menu) { for (int i = menu->browser.entries - 1; i >= 0; i--) { free(menu->browser.list[i].name); } + free(menu->browser.list); + + menu->browser.list = NULL; menu->browser.entries = 0; - menu->browser.selected = -1; menu->browser.entry = NULL; + menu->browser.selected = -1; +} - if (f_opendir(&dir, strip_sd_prefix(path_get(menu->browser.directory))) != FR_OK) { - return true; +static bool load_directory (menu_t *menu) { + int result; + dir_t info; + + browser_list_free(menu); + + path_t *path = path_clone(menu->browser.directory); + + result = dir_findfirst(path_get(path), &info); + + while (result == 0) { + bool hide = false; + + if (!menu->settings.show_protected_entries) { + path_push(path, info.d_name); + + for (int i = 0; hidden_paths[i] != NULL; i++) { + if (strcmp(strip_fs_prefix(path_get(path)), hidden_paths[i]) == 0) { + hide = true; + break; + } + } + + path_pop(path); + } + + if (!hide) { + menu->browser.list = realloc(menu->browser.list, (menu->browser.entries + 1) * sizeof(entry_t)); + + entry_t *entry = &menu->browser.list[menu->browser.entries++]; + + entry->name = strdup(info.d_name); + if (!entry->name) { + path_free(path); + browser_list_free(menu); + return true; + } + + if (info.d_type == DT_DIR) { + entry->type = ENTRY_TYPE_DIR; + } else if (file_has_extensions(entry->name, rom_extensions)) { + entry->type = ENTRY_TYPE_ROM; + } else if (file_has_extensions(entry->name, disk_extensions)) { + entry->type = ENTRY_TYPE_DISK; + }else if (file_has_extensions(entry->name, emulator_extensions)) { + entry->type = ENTRY_TYPE_EMULATOR; + } else if (file_has_extensions(entry->name, save_extensions)) { + entry->type = ENTRY_TYPE_SAVE; + } else if (file_has_extensions(entry->name, image_extensions)) { + entry->type = ENTRY_TYPE_IMAGE; + } else if (file_has_extensions(entry->name, text_extensions)) { + entry->type = ENTRY_TYPE_TEXT; + } else if (file_has_extensions(entry->name, music_extensions)) { + entry->type = ENTRY_TYPE_MUSIC; + } else { + entry->type = ENTRY_TYPE_OTHER; + } + + entry->size = info.d_size; + } + + result = dir_findnext(path_get(path), &info); } - while (menu->browser.entries < BROWSER_LIST_SIZE) { - if (f_readdir(&dir, &info) != FR_OK) { - return true; - } + path_free(path); - size_t length = strlen(info.fname); - - if (length == 0) { - break; - } - - if (info.fattrib & AM_SYS) { - continue; - } - if ((info.fattrib & AM_HID) && !menu->settings.hidden_files_enabled) { - continue; - } - - entry_t *entry = &menu->browser.list[menu->browser.entries]; - - entry->name = strdup(info.fname); - if (!entry->name) { - f_closedir(&dir); - return true; - } - - if (info.fattrib & AM_DIR) { - entry->type = ENTRY_TYPE_DIR; - } else if (file_has_extensions(info.fname, rom_extensions)) { - entry->type = ENTRY_TYPE_ROM; - } else if (file_has_extensions(info.fname, disk_extensions)) { - entry->type = ENTRY_TYPE_DISK; - }else if (file_has_extensions(info.fname, emulator_extensions)) { - entry->type = ENTRY_TYPE_EMULATOR; - } else if (file_has_extensions(info.fname, save_extensions)) { - entry->type = ENTRY_TYPE_SAVE; - } else if (file_has_extensions(info.fname, image_extensions)) { - entry->type = ENTRY_TYPE_IMAGE; - } else if (file_has_extensions(info.fname, text_extensions)) { - entry->type = ENTRY_TYPE_TEXT; - } else if (file_has_extensions(info.fname, music_extensions)) { - entry->type = ENTRY_TYPE_MUSIC; - } else { - entry->type = ENTRY_TYPE_OTHER; - } - - entry->size = info.fsize; - - menu->browser.entries += 1; - } - - if (f_closedir(&dir) != FR_OK) { + if (result < -1) { + browser_list_free(menu); return true; } @@ -205,18 +228,14 @@ static void show_properties (menu_t *menu, void *arg) { static void delete_entry (menu_t *menu, void *arg) { path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name); - if (menu->browser.entry->type == ENTRY_TYPE_DIR) { - if (directory_delete(path_get(path))) { + if (remove(path_get(path))) { + if (menu->browser.entry->type == ENTRY_TYPE_DIR) { menu_show_error(menu, "Couldn't delete directory\nDirectory might not be empty"); - path_free(path); - return; - } - } else { - if (file_delete(path_get(path))) { + } else { menu_show_error(menu, "Couldn't delete file"); - path_free(path); - return; } + path_free(path); + return; } path_free(path); @@ -229,7 +248,7 @@ static void delete_entry (menu_t *menu, void *arg) { static void set_default_directory (menu_t *menu, void *arg) { free(menu->settings.default_directory); - menu->settings.default_directory = strdup(strip_sd_prefix(path_get(menu->browser.directory))); + menu->settings.default_directory = strdup(strip_fs_prefix(path_get(menu->browser.directory))); settings_save(&menu->settings); } @@ -388,7 +407,7 @@ void view_browser_init (menu_t *menu) { component_context_menu_init(&settings_context_menu); if (load_directory(menu)) { path_free(menu->browser.directory); - menu->browser.directory = path_init("sd:/", ""); + menu->browser.directory = path_init(menu->storage_prefix, ""); menu_show_error(menu, "Error while opening initial directory"); } else { menu->browser.valid = true; diff --git a/src/menu/views/file_info.c b/src/menu/views/file_info.c index 83506caa..f4819de8 100644 --- a/src/menu/views/file_info.c +++ b/src/menu/views/file_info.c @@ -1,4 +1,4 @@ -#include +#include #include "utils/fs.h" #include "views.h" @@ -16,7 +16,7 @@ static const char *dexdrive_extensions[] = { "mpk", NULL }; static const char *emulator_extensions[] = { "emu", NULL }; -static FILINFO info; +static struct stat st; static char *format_file_type (char *name, bool is_directory) { @@ -75,17 +75,14 @@ static void draw (menu_t *menu, surface_t *d) { "\n" "\n" " Size: %d bytes\n" - " Attributes: %s%s%s%s%s\n" + " Attributes: %s %s\n" "%s" - " Modified: %u-%02u-%02u %02u:%02u", - info.fsize, - (info.fattrib & AM_DIR) ? "Directory " : "File ", - (info.fattrib & AM_RDO) ? "| Read only " : "", - (info.fattrib & AM_SYS) ? "| System " : "", - (info.fattrib & AM_ARC) ? "| Archive " : "", - (info.fattrib & AM_HID) ? "| Hidden " : "", - format_file_type(info.fname, info.fattrib & AM_DIR), - (info.fdate >> 9) + 1980, info.fdate >> 5 & 0x0F, info.fdate & 0x1F, info.ftime >> 11, info.ftime >> 5 & 0x3F + " Modified: %s", + st.st_size, + S_ISDIR(st.st_mode) ? "Directory" : "File", + st.st_mode & S_IWUSR ? "" : "(Read only)", + format_file_type(menu->browser.entry->name, S_ISDIR(st.st_mode)), + ctime(&st.st_mtim.tv_sec) ); component_actions_bar_text_draw( @@ -101,7 +98,7 @@ static void draw (menu_t *menu, surface_t *d) { void view_file_info_init (menu_t *menu) { path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name); - if (f_stat(strip_sd_prefix(path_get(path)), &info) != FR_OK) { + if (stat(path_get(path), &st)) { menu_show_error(menu, "Couldn't obtain file information"); } diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index 0a8ebb62..986a18b6 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -154,7 +154,7 @@ void view_load_disk_init (menu_t *menu) { menu->load.disk_path = path_clone_push(menu->browser.directory, menu->browser.entry->name); - disk_err_t err = disk_info_load(path_get(menu->load.disk_path), &menu->load.disk_info); + disk_err_t err = disk_info_load(menu->load.disk_path, &menu->load.disk_info); if (err != DISK_OK) { menu_show_error(menu, convert_error_message(err)); } diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index c5da6ed4..3e4e295c 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -334,7 +334,7 @@ void view_load_rom_init (menu_t *menu) { return; } - boxart = component_boxart_init(menu->load.rom_info.game_code); + boxart = component_boxart_init(menu->storage_prefix, menu->load.rom_info.game_code); component_context_menu_init(&options_context_menu); } diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 7158a0b3..5e9d1854 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -34,15 +34,15 @@ static void draw (menu_t *menu, surface_t *d) { "\n" "To change the settings, please adjust them\n" "directly in the 'menu/config.ini' file.\n\n" - "pal60_enabled: %s\n" - "hidden_files_enabled: %s\n" - "default_directory: %s\n" - "use_saves_folder: %s\n" - "bgm_enabled: %s\n" - "sound_enabled: %s\n" - "rumble_enabled: %s\n", + "pal60_enabled: %s\n" + "show_protected_entries: %s\n" + "default_directory: %s\n" + "use_saves_folder: %s\n" + "bgm_enabled: %s\n" + "sound_enabled: %s\n" + "rumble_enabled: %s\n", format_switch(menu->settings.pal60_enabled), - format_switch(menu->settings.hidden_files_enabled), + format_switch(menu->settings.show_protected_entries), menu->settings.default_directory, format_switch(menu->settings.use_saves_folder), format_switch(menu->settings.bgm_enabled), diff --git a/src/menu/views/text_viewer.c b/src/menu/views/text_viewer.c index adef2df9..277eeba5 100644 --- a/src/menu/views/text_viewer.c +++ b/src/menu/views/text_viewer.c @@ -1,9 +1,13 @@ -#include +#include +#include #include "utils/fs.h" +#include "utils/utils.h" #include "views.h" -static char *file_content; + +static char *text; + static void process (menu_t *menu) { if (menu->actions.back) { @@ -18,20 +22,15 @@ static void draw (menu_t *menu, surface_t *d) { component_layout_draw(); - component_main_text_draw( - ALIGN_CENTER, VALIGN_TOP, - "TEXT VIEWER\n" - "\n" - ); - - component_main_text_draw( - ALIGN_LEFT, VALIGN_TOP, - "\n" - "\n" - "%s\n", - file_content - ); - + if (text) { + component_main_text_draw( + ALIGN_LEFT, VALIGN_TOP, + "%s\n", + text + ); + } else { + component_messagebox_draw("Text file is empty"); + } component_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, @@ -42,37 +41,62 @@ static void draw (menu_t *menu, surface_t *d) { rdpq_detach_show(); } +static void deinit (void) { + if (text) { + free(text); + text = NULL; + } +} + void view_text_viewer_init (menu_t *menu) { path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name); - uint32_t file_size = file_get_size(path_get(path)); + FILE *f; - if (file_size > 1024) { // FIXME: this is just a placeholder until scrolling is implemented. - file_size = 1024; // For the moment, we just set it to that, since any more would be a waste. - } - - file_content = calloc(file_size, 1); - - // read file content - FIL fil; - UINT br; - - if (f_open(&fil, strip_sd_prefix(path_get(path)), FA_READ) != FR_OK) { - debugf("Error loading file\n"); - } - if (f_read(&fil, file_content, file_size, &br) != FR_OK) { - f_close(&fil); - debugf("Error loading file content\n"); - } - if (f_close(&fil) != FR_OK) { - debugf("Error closing file\n"); + if ((f = fopen(path_get(path), "r")) == NULL) { + path_free(path); + menu_show_error(menu, "Couldn't open text file"); + return; } path_free(path); + + struct stat st; + if (fstat(fileno(f), &st)) { + fclose(f); + menu_show_error(menu, "Couldn't get text file size"); + return; + } + + // TODO: Implement proper text file viewer with both vertical and horizontal scrolling + size_t size = MIN(st.st_size, 1024); + + if (size) { + if ((text = calloc(sizeof(char), size)) == NULL) { + fclose(f); + menu_show_error(menu, "Couldn't allocate memory for the text file contents"); + return; + } + + if (fread(text, size, 1, f) != 1) { + fclose(f); + menu_show_error(menu, "Couldn't read text file contents"); + return; + } + } + + if (fclose(f)) { + menu_show_error(menu, "Couldn't close text file"); + } } void view_text_viewer_display (menu_t *menu, surface_t *display) { process(menu); + draw(menu, display); + + if (menu->next_mode != MENU_MODE_TEXT_VIEWER) { + deinit(); + } } diff --git a/src/utils/fs.c b/src/utils/fs.c index 648ecfdf..41b69c48 100644 --- a/src/utils/fs.c +++ b/src/utils/fs.c @@ -1,145 +1,85 @@ +#include #include #include -#include - -#include +#include +#include #include "fs.h" #include "utils.h" -char *strip_sd_prefix (char *path) { - const char *prefix = "sd:/"; - +char *strip_fs_prefix (char *path) { + const char *prefix = ":/"; char *found = strstr(path, prefix); if (found) { - return found + strlen(prefix) - 1; + return (found + strlen(prefix) - 1); } - return path; } bool file_exists (char *path) { - FRESULT fr; - FILINFO fno; - - fr = f_stat(strip_sd_prefix(path), &fno); - - return ((fr == FR_OK) && (!(fno.fattrib & AM_DIR))); + struct stat st; + int error = stat(path, &st); + return ((error == 0) && S_ISREG(st.st_mode)); } - -size_t file_get_size (char *path) { - FILINFO fno; - - if (f_stat(strip_sd_prefix(path), &fno) != FR_OK) { - return 0; +int64_t file_get_size (char *path) { + struct stat st; + if (stat(path, &st)) { + return -1; } - - return (size_t) (fno.fsize); + return (int64_t) (st.st_size); } -bool file_delete (char *path) { - if (file_exists(path)) { - return (f_unlink(strip_sd_prefix(path)) != FR_OK); +bool file_allocate (char *path, size_t size) { + FILE *f; + if ((f = fopen(path, "wb")) == NULL) { + return true; + } + if (fseek(f, size, SEEK_SET)) { + fclose(f); + return true; + } + if (ftell(f) != size) { + fclose(f); + return true; + } + if (fclose(f)) { + return true; } return false; } -bool file_allocate (char *path, size_t size) { - FIL fil; - bool error = false; - - if (f_open(&fil, strip_sd_prefix(path), FA_WRITE | FA_CREATE_NEW) != FR_OK) { - return true; - } - - if (f_lseek(&fil, size) != FR_OK) { - error = true; - } - - if (f_tell(&fil) != size) { - error = true; - } - - if (f_close(&fil) != FR_OK) { - error = true; - } - - return error; -} - bool file_fill (char *path, uint8_t value) { - FIL fil; + FILE *f; bool error = false; uint8_t buffer[FS_SECTOR_SIZE * 8]; - FRESULT res; - UINT bytes_to_write; - UINT bytes_written; + size_t bytes_to_write; for (int i = 0; i < sizeof(buffer); i++) { buffer[i] = value; } - if (f_open(&fil, strip_sd_prefix(path), FA_WRITE) != FR_OK) { + if ((f = fopen(path, "rb+")) == NULL) { return true; } - for (int i = 0; i < f_size(&fil); i += sizeof(buffer)) { - bytes_to_write = MIN(f_size(&fil) - f_tell(&fil), sizeof(buffer)); - res = f_write(&fil, buffer, bytes_to_write, &bytes_written); - if ((res != FR_OK) || (bytes_to_write != bytes_written)) { + setbuf(f, NULL); + + fseek(f, 0, SEEK_END); + size_t size = ftell(f); + fseek(f, 0, SEEK_SET); + + for (size_t i = 0; i < size; i += sizeof(buffer)) { + bytes_to_write = MIN(size - ftell(f), sizeof(buffer)); + if (fwrite(buffer, 1, bytes_to_write, f) != bytes_to_write) { error = true; break; } } - if (f_tell(&fil) != f_size(&fil)) { - error = true; - } - - if (f_close(&fil) != FR_OK) { - error = true; - } - - return error; -} - -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; - - if (!callback) { - return true; - } - - if (f_open(&fil, strip_sd_prefix(path), FA_READ) != FR_OK) { - return true; - } - - fs = fil.obj.fs; - - 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 < sector_count; file_sector += fs->csize) { - 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))); - callback(sector_count, file_sector, cluster_sector, fs->csize); - } - - if (f_close(&fil) != FR_OK) { + if (fclose(f)) { error = true; } @@ -165,20 +105,9 @@ bool file_has_extensions (char *path, const char *extensions[]) { bool directory_exists (char *path) { - FRESULT fr; - FILINFO fno; - - fr = f_stat(strip_sd_prefix(path), &fno); - - return ((fr == FR_OK) && (fno.fattrib & AM_DIR)); -} - -bool directory_delete (char *path) { - if (directory_exists(path)) { - return (f_unlink(strip_sd_prefix(path)) != FR_OK); - } - - return false; + struct stat st; + int error = stat(path, &st); + return ((error == 0) && S_ISDIR(st.st_mode)); } bool directory_create (char *path) { @@ -188,8 +117,12 @@ bool directory_create (char *path) { return false; } - char *directory = strdup(strip_sd_prefix(path)); - char *separator = directory; + char *directory = strdup(path); + char *separator = strip_fs_prefix(directory); + + if (separator != directory) { + separator++; + } do { separator = strchr(separator, '/'); @@ -199,8 +132,7 @@ bool directory_create (char *path) { } if (directory[0] != '\0') { - FRESULT res = f_mkdir(directory); - if ((res != FR_OK) && (res != FR_EXIST)) { + if (mkdir(directory, 0777) && (errno != EEXIST)) { error = true; break; } diff --git a/src/utils/fs.h b/src/utils/fs.h index 7661ecb0..88f81a7a 100644 --- a/src/utils/fs.h +++ b/src/utils/fs.h @@ -10,18 +10,15 @@ #define FS_SECTOR_SIZE (512) -char *strip_sd_prefix (char *path); +char *strip_fs_prefix (char *path); bool file_exists (char *path); -size_t file_get_size (char *path); -bool file_delete (char *path); +int64_t file_get_size (char *path); bool file_allocate (char *path, size_t size); bool file_fill (char *path, uint8_t value); -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); -bool directory_delete (char *path); bool directory_create (char *path); From cbe69e26595240d6461ab63785bfd559d0188381 Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Fri, 26 Apr 2024 23:03:15 +0200 Subject: [PATCH 03/16] Init BBFS on iQue --- src/flashcart/flashcart.c | 4 ++++ src/flashcart/flashcart.h | 1 + 2 files changed, 5 insertions(+) diff --git a/src/flashcart/flashcart.c b/src/flashcart/flashcart.c index c5aaaacb..0cb55e84 100644 --- a/src/flashcart/flashcart.c +++ b/src/flashcart/flashcart.c @@ -79,6 +79,7 @@ char *flashcart_convert_error_message (flashcart_err_t err) { case FLASHCART_OK: return "No error"; case FLASHCART_ERR_OUTDATED: return "Outdated flashcart firmware"; case FLASHCART_ERR_SD_CARD: return "Error during SD card initialization"; + case FLASHCART_ERR_BBFS: return "Error during iQue NAND initialization"; case FLASHCART_ERR_ARGS: return "Invalid argument passed to flashcart function"; case FLASHCART_ERR_LOAD: return "Error during loading data into flashcart"; case FLASHCART_ERR_INT: return "Internal flashcart error"; @@ -93,6 +94,9 @@ flashcart_err_t flashcart_init (const char **storage_prefix) { if (sys_bbplayer()) { // TODO: Add iQue callbacks *storage_prefix = "bbfs:/"; + if (bbfs_init()) { + return FLASHCART_ERR_BBFS; + } return FLASHCART_OK; } diff --git a/src/flashcart/flashcart.h b/src/flashcart/flashcart.h index b45a32d0..a481ecf2 100644 --- a/src/flashcart/flashcart.h +++ b/src/flashcart/flashcart.h @@ -17,6 +17,7 @@ typedef enum { FLASHCART_OK, FLASHCART_ERR_OUTDATED, FLASHCART_ERR_SD_CARD, + FLASHCART_ERR_BBFS, FLASHCART_ERR_ARGS, FLASHCART_ERR_LOAD, FLASHCART_ERR_INT, From fc410da89121cdf61c7471f760afbab2abfda1d8 Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Fri, 26 Apr 2024 23:04:19 +0200 Subject: [PATCH 04/16] clang-format WIP --- .clang-format | 20 ++++++++++++++++++++ Makefile | 7 +++++++ src/menu/cart_load.c | 7 +++---- src/menu/rom_info.c | 3 +++ src/menu/views/browser.c | 5 +++-- 5 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..41c4bdbb --- /dev/null +++ b/.clang-format @@ -0,0 +1,20 @@ +BasedOnStyle: LLVM + +AlignAfterOpenBracket: BlockIndent +AlignEscapedNewlines: DontAlign +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: InlineOnly +AlwaysBreakBeforeMultilineStrings: true +BinPackArguments: false +BinPackParameters: false +BreakBeforeBraces: Attach +ColumnLimit: 120 +IndentWidth: 4 +SpaceBeforeParens: Custom +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterFunctionDeclarationName: true + AfterFunctionDefinitionName: true +UseTab: Never diff --git a/Makefile b/Makefile index fed6ed87..dcebf60c 100644 --- a/Makefile +++ b/Makefile @@ -138,6 +138,13 @@ clean: @rm -rf ./$(BUILD_DIR) ./$(OUTPUT_DIR) .PHONY: clean +format: + @find ./$(SOURCE_DIR) \ + -path \./$(SOURCE_DIR)/libs -prune \ + -o -iname *.c -print \ + -o -iname *.h -print \ + | xargs clang-format -i + run: $(OUTPUT_DIR)/$(PROJECT_NAME).n64 ifeq ($(OS),Windows_NT) ./localdeploy.bat diff --git a/src/menu/cart_load.c b/src/menu/cart_load.c index 13ef7aca..f7b341ab 100644 --- a/src/menu/cart_load.c +++ b/src/menu/cart_load.c @@ -19,10 +19,9 @@ static bool is_64dd_connected (void) { - return ( - ((io_read(0x05000540) & 0x0000FFFF) == 0x0000) || - (io_read(0x06001010) == 0x2129FFF8) - ); + bool is_64dd_io_present = ((io_read(0x05000540) & 0x0000FFFF) == 0x0000); + bool is_64dd_ipl_present = (io_read(0x06001010) == 0x2129FFF8); + return (is_64dd_io_present || is_64dd_ipl_present); } static bool create_saves_subdirectory (path_t *path) { diff --git a/src/menu/rom_info.c b/src/menu/rom_info.c index b9711701..4713eee7 100644 --- a/src/menu/rom_info.c +++ b/src/menu/rom_info.c @@ -141,6 +141,8 @@ typedef struct { // List shamelessly stolen from https://github.com/ares-emulator/ares/blob/master/mia/medium/nintendo-64.cpp + +// clang-format off static const match_t database[] = { MATCH_HOMEBREW_HEADER("ED"), // Homebrew header (ED) @@ -576,6 +578,7 @@ static const match_t database[] = { MATCH_END, }; +// clang-format on static void fix_rom_header_endianness (rom_header_t *rom_header, rom_info_t *rom_info) { diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c index 8cca0201..b5c46e0d 100644 --- a/src/menu/views/browser.c +++ b/src/menu/views/browser.c @@ -11,7 +11,8 @@ static const char *rom_extensions[] = { "z64", "n64", "v64", "rom", NULL }; static const char *disk_extensions[] = { "ndd", NULL }; static const char *emulator_extensions[] = { "nes", "sfc", "smc", "gb", "gbc", "sms", "gg", "sg", NULL }; -static const char *save_extensions[] = { "sav", NULL }; // TODO: "eep", "sra", "srm", "fla" could be used if transfered from different flashcarts. +// TODO: "eep", "sra", "srm", "fla" could be used if transfered from different flashcarts. +static const char *save_extensions[] = { "sav", NULL }; static const char *image_extensions[] = { "png", NULL }; static const char *text_extensions[] = { "txt", "ini", "yml", "yaml", NULL }; static const char *music_extensions[] = { "mp3", NULL }; @@ -24,7 +25,7 @@ static const char *hidden_paths[] = { "/OS64P.v64", "/sc64menu.n64", "/System Volume Information", - NULL + NULL, }; From a189e139b2b1ac146f418c29371f32d5c2cbbeed Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Fri, 26 Apr 2024 23:04:39 +0200 Subject: [PATCH 05/16] Extend character set in the font --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dcebf60c..f55c9532 100644 --- a/Makefile +++ b/Makefile @@ -86,7 +86,7 @@ FILESYSTEM = \ $(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second $(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second -$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-7F -r 2026-2026 --ellipsis 2026,1 +$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-1FF -r 2026-2026 --ellipsis 2026,1 $(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null)) From 23af1748117653f01e84bda58f433122632315e1 Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Sat, 27 Apr 2024 13:12:09 +0200 Subject: [PATCH 06/16] Implement vertical scrolling for text viewer --- src/menu/components.h | 2 +- src/menu/components/common.c | 10 +-- src/menu/components/constants.h | 12 +-- src/menu/components/file_list.c | 20 ++--- src/menu/views/browser.c | 1 + src/menu/views/error.c | 1 - src/menu/views/text_viewer.c | 150 +++++++++++++++++++++++--------- 7 files changed, 133 insertions(+), 63 deletions(-) diff --git a/src/menu/components.h b/src/menu/components.h index cff0f4c2..4925ee5b 100644 --- a/src/menu/components.h +++ b/src/menu/components.h @@ -24,7 +24,7 @@ void component_progressbar_draw (int x0, int y0, int x1, int y1, float progress) void component_seekbar_draw (float progress); void component_loader_draw (float position); void component_scrollbar_draw (int x, int y, int width, int height, int position, int items, int visible_items); -void component_file_list_scrollbar_draw (int position, int items, int visible_items); +void component_list_scrollbar_draw (int position, int items, int visible_items); void component_dialog_draw (int width, int height); void component_messagebox_draw (char *fmt, ...); void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...); diff --git a/src/menu/components/common.c b/src/menu/components/common.c index 66db56eb..6bf708f8 100644 --- a/src/menu/components/common.c +++ b/src/menu/components/common.c @@ -80,12 +80,12 @@ void component_scrollbar_draw (int x, int y, int width, int height, int position } } -void component_file_list_scrollbar_draw (int position, int items, int visible_items) { +void component_list_scrollbar_draw (int position, int items, int visible_items) { component_scrollbar_draw( - FILE_LIST_SCROLLBAR_X, - FILE_LIST_SCROLLBAR_Y, - FILE_LIST_SCROLLBAR_WIDTH, - FILE_LIST_SCROLLBAR_HEIGHT, + LIST_SCROLLBAR_X, + LIST_SCROLLBAR_Y, + LIST_SCROLLBAR_WIDTH, + LIST_SCROLLBAR_HEIGHT, position, items, visible_items diff --git a/src/menu/components/constants.h b/src/menu/components/constants.h index 7522d461..f3eb3e39 100644 --- a/src/menu/components/constants.h +++ b/src/menu/components/constants.h @@ -77,19 +77,19 @@ #define BOXART_Y (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT - 24) /** @brief The scroll bar width. */ -#define FILE_LIST_SCROLLBAR_WIDTH (12) +#define LIST_SCROLLBAR_WIDTH (12) /** @brief The scroll bar height. */ -#define FILE_LIST_SCROLLBAR_HEIGHT (LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT) +#define LIST_SCROLLBAR_HEIGHT (LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT) /** @brief The scroll bar position on the X axis. */ -#define FILE_LIST_SCROLLBAR_X (VISIBLE_AREA_X1 - FILE_LIST_SCROLLBAR_WIDTH) +#define LIST_SCROLLBAR_X (VISIBLE_AREA_X1 - LIST_SCROLLBAR_WIDTH) /** @brief The scroll bar position on the Y axis. */ -#define FILE_LIST_SCROLLBAR_Y (VISIBLE_AREA_Y0) +#define LIST_SCROLLBAR_Y (VISIBLE_AREA_Y0) /** @brief The maximum amount of file list entries. */ -#define FILE_LIST_ENTRIES (20) +#define LIST_ENTRIES (20) /** @brief The maximum width available for a file list entry. */ #define FILE_LIST_MAX_WIDTH (480) -#define FILE_LIST_HIGHLIGHT_WIDTH (VISIBLE_AREA_X1 - VISIBLE_AREA_X0 - FILE_LIST_SCROLLBAR_WIDTH) +#define FILE_LIST_HIGHLIGHT_WIDTH (VISIBLE_AREA_X1 - VISIBLE_AREA_X0 - LIST_SCROLLBAR_WIDTH) #define FILE_LIST_HIGHLIGHT_X (VISIBLE_AREA_X0) /** @brief The default background colour. */ diff --git a/src/menu/components/file_list.c b/src/menu/components/file_list.c index 300b46ff..b00ef329 100644 --- a/src/menu/components/file_list.c +++ b/src/menu/components/file_list.c @@ -28,14 +28,14 @@ static int format_file_size (char *buffer, int64_t size) { void component_file_list_draw (entry_t *list, int entries, int selected) { int starting_position = 0; - if (entries > FILE_LIST_ENTRIES && selected >= (FILE_LIST_ENTRIES / 2)) { - starting_position = selected - (FILE_LIST_ENTRIES / 2); - if (starting_position >= entries - FILE_LIST_ENTRIES) { - starting_position = entries - FILE_LIST_ENTRIES; + if (entries > LIST_ENTRIES && selected >= (LIST_ENTRIES / 2)) { + starting_position = selected - (LIST_ENTRIES / 2); + if (starting_position >= entries - LIST_ENTRIES) { + starting_position = entries - LIST_ENTRIES; } } - component_file_list_scrollbar_draw(selected, entries, FILE_LIST_ENTRIES); + component_list_scrollbar_draw(selected, entries, LIST_ENTRIES); if (entries == 0) { component_main_text_draw( @@ -47,10 +47,10 @@ void component_file_list_draw (entry_t *list, int entries, int selected) { rdpq_paragraph_t *file_list_layout; rdpq_paragraph_t *layout; - size_t name_lengths[FILE_LIST_ENTRIES]; + size_t name_lengths[LIST_ENTRIES]; size_t total_length = 1; - for (int i = 0; i < FILE_LIST_ENTRIES; i++) { + for (int i = 0; i < LIST_ENTRIES; i++) { int entry_index = starting_position + i; if (entry_index >= entries) { @@ -76,7 +76,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) { file_list_layout ); - for (int i = 0; i < FILE_LIST_ENTRIES; i++) { + for (int i = 0; i < LIST_ENTRIES; i++) { int entry_index = starting_position + i; entry_t *entry = &list[entry_index]; @@ -134,7 +134,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) { rdpq_paragraph_builder_begin( &(rdpq_textparms_t) { - .width = VISIBLE_AREA_WIDTH - FILE_LIST_SCROLLBAR_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2), + .width = VISIBLE_AREA_WIDTH - LIST_SCROLLBAR_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2), .height = LAYOUT_ACTIONS_SEPARATOR_Y - VISIBLE_AREA_Y0 - (TEXT_MARGIN_VERTICAL * 2), .align = ALIGN_RIGHT, .wrap = WRAP_ELLIPSES, @@ -152,7 +152,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) { rdpq_paragraph_builder_span(file_size, format_file_size(file_size, entry->size)); } - if ((i + 1) == (starting_position + FILE_LIST_ENTRIES)) { + if ((i + 1) == (starting_position + LIST_ENTRIES)) { break; } diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c index b5c46e0d..c8481f4a 100644 --- a/src/menu/views/browser.c +++ b/src/menu/views/browser.c @@ -230,6 +230,7 @@ static void delete_entry (menu_t *menu, void *arg) { path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name); if (remove(path_get(path))) { + menu->browser.valid = false; if (menu->browser.entry->type == ENTRY_TYPE_DIR) { menu_show_error(menu, "Couldn't delete directory\nDirectory might not be empty"); } else { diff --git a/src/menu/views/error.c b/src/menu/views/error.c index 8e565a55..a5c0b84a 100644 --- a/src/menu/views/error.c +++ b/src/menu/views/error.c @@ -50,5 +50,4 @@ void view_error_display (menu_t *menu, surface_t *display) { void menu_show_error (menu_t *menu, char *error_message) { menu->next_mode = MENU_MODE_ERROR; menu->error_message = error_message; - menu->browser.valid = false; } diff --git a/src/menu/views/text_viewer.c b/src/menu/views/text_viewer.c index 277eeba5..6b688479 100644 --- a/src/menu/views/text_viewer.c +++ b/src/menu/views/text_viewer.c @@ -1,17 +1,66 @@ #include #include -#include "utils/fs.h" +#include "../components/constants.h" +#include "../fonts.h" #include "utils/utils.h" #include "views.h" -static char *text; +#define MAX_FILE_SIZE KiB(128) + + +typedef struct { + FILE *f; + char *contents; + size_t length; + int lines; + int current_line; + int offset; + bool vertical_scroll_possible; +} text_file_t; + +static text_file_t *text; + + +static void perform_vertical_scroll (int lines) { + if (!text->vertical_scroll_possible) { + return; + } + + int direction = (lines < 0) ? -1 : 1; + int next_offset = text->offset; + + for (int i = 0; i < abs(lines); i++) { + while (true) { + next_offset += direction; + if (next_offset <= 0) { + text->current_line = 0; + text->offset = 0; + return; + } + if (next_offset > text->length) { + return; + } + if (text->contents[next_offset - 1] == '\n') { + break; + } + } + text->current_line += direction; + text->offset = next_offset; + } +} static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + } else if (text) { + if (menu->actions.go_up) { + perform_vertical_scroll(menu->actions.go_fast ? -10 : -1); + } else if (menu->actions.go_down) { + perform_vertical_scroll(menu->actions.go_fast ? 10 : 1); + } } } @@ -22,20 +71,19 @@ static void draw (menu_t *menu, surface_t *d) { component_layout_draw(); - if (text) { - component_main_text_draw( - ALIGN_LEFT, VALIGN_TOP, - "%s\n", - text - ); - } else { - component_messagebox_draw("Text file is empty"); - } + component_main_text_draw( + ALIGN_LEFT, VALIGN_TOP, + "%s\n", + text->contents + text->offset + ); + + component_list_scrollbar_draw(text->current_line, text->lines, LIST_ENTRIES); component_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, - "\n" - "B: Back" + "^%02XUp / Down: Scroll^00\n" + "B: Back", + text->vertical_scroll_possible ? STL_DEFAULT : STL_GRAY ); rdpq_detach_show(); @@ -43,6 +91,12 @@ static void draw (menu_t *menu, surface_t *d) { static void deinit (void) { if (text) { + if (text->f) { + fclose(text->f); + } + if (text->contents) { + free(text->contents); + } free(text); text = NULL; } @@ -50,45 +104,61 @@ static void deinit (void) { void view_text_viewer_init (menu_t *menu) { - path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name); - - FILE *f; - - if ((f = fopen(path_get(path), "r")) == NULL) { - path_free(path); - menu_show_error(menu, "Couldn't open text file"); - return; + if ((text = calloc(1, sizeof(text_file_t))) == NULL) { + return menu_show_error(menu, "Couldn't allocate memory for the text file"); } + path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name); + text->f = fopen(path_get(path), "r"); path_free(path); + if (text->f == NULL) { + deinit(); + return menu_show_error(menu, "Couldn't open text file"); + } + struct stat st; - if (fstat(fileno(f), &st)) { - fclose(f); - menu_show_error(menu, "Couldn't get text file size"); - return; + if (fstat(fileno(text->f), &st)) { + deinit(); + return menu_show_error(menu, "Couldn't get text file size"); + } + text->length = st.st_size; + + if (text->length <= 0) { + deinit(); + return menu_show_error(menu, "Text file is empty"); } - // TODO: Implement proper text file viewer with both vertical and horizontal scrolling - size_t size = MIN(st.st_size, 1024); + if (text->length > MAX_FILE_SIZE) { + deinit(); + return menu_show_error(menu, "Text file is too big to be displayed"); + } - if (size) { - if ((text = calloc(sizeof(char), size)) == NULL) { - fclose(f); - menu_show_error(menu, "Couldn't allocate memory for the text file contents"); - return; - } + if ((text->contents = malloc((text->length + 1) * sizeof(char))) == NULL) { + deinit(); + return menu_show_error(menu, "Couldn't allocate memory for the text file contents"); + } - if (fread(text, size, 1, f) != 1) { - fclose(f); - menu_show_error(menu, "Couldn't read text file contents"); - return; + if (fread(text->contents, text->length, 1, text->f) != 1) { + deinit(); + return menu_show_error(menu, "Couldn't read text file contents"); + } + text->contents[text->length] = '\0'; + + if (fclose(text->f)) { + deinit(); + return menu_show_error(menu, "Couldn't close text file"); + } + text->f = NULL; + + text->lines = 1; + for (size_t i = 0; i < text->length; i++) { + if (text->contents[i] == '\n') { + text->lines += 1; } } - if (fclose(f)) { - menu_show_error(menu, "Couldn't close text file"); - } + text->vertical_scroll_possible = (text->lines > LIST_ENTRIES); } void view_text_viewer_display (menu_t *menu, surface_t *display) { From 6b8baa19e7b910ac17c9deaa0693a27a3cb3f06c Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Mon, 29 Apr 2024 14:08:31 +0200 Subject: [PATCH 07/16] Link to the 64DD SummerCart64 guide in the readme --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5df18ec5..500e4088 100644 --- a/README.md +++ b/README.md @@ -82,11 +82,8 @@ If required, you can manually adjust the file on the SD card using your computer - Download the latest `sc64menu.n64` file from the releases page, then put it in the root directory of your SD card. ##### 64DD disk support -For the ability to load and run 64DD disk images, you need to add the folder `/menu/64ddipl` on the SD card. -Download and add the relevant ipl files and rename them before adding them to the folder: -- `NDDE0.n64` the US Prototype IPL can be downloaded from [here](https://64dd.org/dumps/64DD_IPL_US_MJR.n64) -- `NDXJ0.n64` the JPN Development IPL can be downloaded from [here](https://64dd.org/dumps/64DD_IPL_DEV_H4G.n64) -- `NDDJ2.n64` the JPN Retail IPL can be downloaded from [here](https://64dd.org/dumps/N64DD_IPLROM_(J).zip) +For the ability to load and run 64DD disk images, you need to place required 64DD IPL dumps in the `/menu/64ddipl` folder on the SD card. +For more details follow [this guide on the 64dd.org website](https://64dd.org/tutorial_sc64.html). Note: to load an expansion disk (e.g. F-Zero X) browse to the N64 ROM and load it (but not start it) and then browse to the DD expansion file and press the `R` button. From 53fdcc5979a09c91a11a384e8a0fa9ef06cf6e5c Mon Sep 17 00:00:00 2001 From: Suprapote <111246491+Suprapote@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:41:59 +0200 Subject: [PATCH 08/16] More savetypes (#96) ## Description Add more saves to the list ## Motivation and Context ## How Has This Been Tested? ## 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. - [ ] 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 --- src/menu/rom_info.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/menu/rom_info.c b/src/menu/rom_info.c index 4713eee7..d476e1d6 100644 --- a/src/menu/rom_info.c +++ b/src/menu/rom_info.c @@ -147,12 +147,25 @@ static const match_t database[] = { MATCH_HOMEBREW_HEADER("ED"), // Homebrew header (ED) MATCH_CHECK_CODE(0x000000004CBC3B56, SAVE_TYPE_SRAM, FEAT_EXP_PAK_REQUIRED | FEAT_64DD_CONVERSION), // DMTJ 64DD cartridge conversion + MATCH_CHECK_CODE(0x0DD4ABABB5A2A91E, SAVE_TYPE_EEPROM_16K, FEAT_EXP_PAK_REQUIRED), // DK Retail kiosk demo MATCH_CHECK_CODE(0xEB85EBC9596682AF, SAVE_TYPE_FLASHRAM, FEAT_NONE), // Doubutsu Banchou + MATCH_CHECK_CODE(0x9A746EBF2802EA99, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Toon panic + MATCH_CHECK_CODE(0x5D40ED2C10D6ABCF, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Viewpoint 2064 + MATCH_CHECK_CODE(0x21548CA921548CA9, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Mini racers + MATCH_CHECK_CODE(0xBC9B2CC34ED04DA5, SAVE_TYPE_FLASHRAM, FEAT_NONE), // Starcraft 64 [Prototype 2000] + + MATCH_CHECK_CODE(0xCDB8B4D08832352D, SAVE_TYPE_SRAM, FEAT_RPAK), // Jet Force Gemini [USA CRACK] + MATCH_CHECK_CODE(0xB66E0F7C2709C22F, SAVE_TYPE_SRAM, FEAT_RPAK), // Jet Force Gemini [PAL CRACK] + + MATCH_CHECK_CODE(0xCE84793D27ECC1AD, SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Donkey kong 64 [USA CRACK] + MATCH_CHECK_CODE(0x1F95CAAA047FC22A, SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_EXP_PAK_REQUIRED), // Donkey kong 64 [PAL CRACK] + + MATCH_CHECK_CODE(0xE3FF09DFCAE4B0ED, SAVE_TYPE_SRAM, FEAT_RPAK), // Banjo tooie [USA CRACK] MATCH_ID_REGION_VERSION("NK4J", 0, SAVE_TYPE_SRAM, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)] MATCH_ID_REGION_VERSION("NK4J", 1, SAVE_TYPE_SRAM, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)] - MATCH_ID("NK4", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)] + MATCH_ID("NK4", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Kirby 64: The Crystal Shards [Hoshi no Kirby 64 (J)] MATCH_ID_REGION_VERSION("NSMJ", 3, SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Super Mario 64 Shindou Edition MATCH_ID("NSM", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Super Mario 64 @@ -177,6 +190,7 @@ static const match_t database[] = { MATCH_ID_REGION("NWTJ", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Wetrix MATCH_ID("NWT", SAVE_TYPE_NONE, FEAT_CPAK), // Wetrix + // EEPROM 4K MATCH_ID("CLB", SAVE_TYPE_EEPROM_4K, FEAT_RPAK | FEAT_64DD_ENHANCED), // Mario Party (NTSC) MATCH_ID("NAB", SAVE_TYPE_EEPROM_4K, FEAT_CPAK | FEAT_RPAK), // Air Boarder 64 MATCH_ID("NAD", SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Worms Armageddon (U) @@ -271,6 +285,7 @@ static const match_t database[] = { MATCH_ID("NXO", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Cruis'n Exotica MATCH_ID("NYK", SAVE_TYPE_EEPROM_4K, FEAT_RPAK), // Yakouchuu II: Satsujin Kouro + // EEPROM 16K MATCH_ID("N3D", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Doraemon 3: Nobita no Machi SOS! MATCH_ID("NB7", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)] MATCH_ID("NCW", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Cruis'n World @@ -295,12 +310,14 @@ static const match_t database[] = { MATCH_ID("NUB", SAVE_TYPE_EEPROM_16K, FEAT_CPAK | FEAT_TPAK), // PD Ultraman Battle Collection 64 MATCH_ID("NYS", SAVE_TYPE_EEPROM_16K, FEAT_RPAK), // Yoshi's Story + // SRAM 256K MATCH_ID("CFZ", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_64DD_ENHANCED), // F-Zero X (J) MATCH_ID("CPS", SAVE_TYPE_SRAM, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium (J) MATCH_ID("CZL", SAVE_TYPE_SRAM, FEAT_RPAK | FEAT_64DD_ENHANCED), // Legend of Zelda: Ocarina of Time [Zelda no Densetsu - Toki no Ocarina (J)] MATCH_ID("NA2", SAVE_TYPE_SRAM, FEAT_CPAK | FEAT_RPAK), // Virtual Pro Wrestling 2 MATCH_ID("NAL", SAVE_TYPE_SRAM, FEAT_RPAK), // Super Smash Bros. [Nintendo All-Star! Dairantou Smash Brothers (J)] MATCH_ID("NB5", SAVE_TYPE_SRAM, FEAT_RPAK), // Biohazard 2 (J) + MATCH_ID("NDD", SAVE_TYPE_SRAM, FEAT_EXP_PAK_REQUIRED | FEAT_64DD_CONVERSION), // 64DD Conversion Rom MATCH_ID("NFZ", SAVE_TYPE_SRAM, FEAT_RPAK), // F-Zero X (U + E) MATCH_ID("NG6", SAVE_TYPE_SRAM, FEAT_RPAK), // Ganmare Goemon: Dero Dero Douchuu Obake Tenkomori MATCH_ID("NGP", SAVE_TYPE_SRAM, FEAT_CPAK), // Goemon: Mononoke Sugoroku @@ -333,11 +350,14 @@ static const match_t database[] = { MATCH_ID("NYW", SAVE_TYPE_SRAM, FEAT_NONE), // Harvest Moon 64 MATCH_ID("NZL", SAVE_TYPE_SRAM, FEAT_RPAK), // Legend of Zelda: Ocarina of Time (E) + // SRAM 768K MATCH_ID("CDZ", SAVE_TYPE_SRAM_BANKED, FEAT_RPAK | FEAT_64DD_ENHANCED), // Dezaemon 3D + // FLASHRAM MATCH_ID("CP2", SAVE_TYPE_FLASHRAM, FEAT_TPAK | FEAT_64DD_ENHANCED), // Pocket Monsters Stadium 2 (J) MATCH_ID("NAF", SAVE_TYPE_FLASHRAM, FEAT_CPAK | FEAT_RTC), // Doubutsu no Mori MATCH_ID("NCC", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // Command & Conquer + MATCH_ID("NCV", SAVE_TYPE_FLASHRAM, FEAT_NONE), // Cubivore (Translation) MATCH_ID("NCK", SAVE_TYPE_FLASHRAM, FEAT_RPAK), // NBA Courtside 2 featuring Kobe Bryant MATCH_ID("NDA", SAVE_TYPE_FLASHRAM, FEAT_CPAK), // Derby Stallion 64 MATCH_ID("NDP", SAVE_TYPE_FLASHRAM, FEAT_EXP_PAK_REQUIRED), // Dinosaur Planet (Unlicensed) @@ -356,6 +376,7 @@ static const match_t database[] = { MATCH_ID("NP3", SAVE_TYPE_FLASHRAM_PKST2, FEAT_TPAK), // Pokemon Stadium 2 [Pocket Monsters Stadium - Kin Gin (J)] + // CONTROLLER PAK / NONE MATCH_ID("N22", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Ready 2 Rumble Boxing - Round 2 MATCH_ID("N2M", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Madden Football 2002 MATCH_ID("N32", SAVE_TYPE_NONE, FEAT_CPAK | FEAT_RPAK), // Army Men - Sarge's Heroes 2 From 9645ad1b5bb5e430c8250735938998a8d5a9d6e1 Mon Sep 17 00:00:00 2001 From: Suprapote <111246491+Suprapote@users.noreply.github.com> Date: Mon, 29 Apr 2024 19:06:23 +0200 Subject: [PATCH 09/16] Add more saves (#97) Add extra save type. --- src/menu/rom_info.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/menu/rom_info.c b/src/menu/rom_info.c index d476e1d6..14026ee9 100644 --- a/src/menu/rom_info.c +++ b/src/menu/rom_info.c @@ -151,9 +151,10 @@ static const match_t database[] = { MATCH_CHECK_CODE(0x0DD4ABABB5A2A91E, SAVE_TYPE_EEPROM_16K, FEAT_EXP_PAK_REQUIRED), // DK Retail kiosk demo MATCH_CHECK_CODE(0xEB85EBC9596682AF, SAVE_TYPE_FLASHRAM, FEAT_NONE), // Doubutsu Banchou MATCH_CHECK_CODE(0x9A746EBF2802EA99, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Toon panic - MATCH_CHECK_CODE(0x5D40ED2C10D6ABCF, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Viewpoint 2064 MATCH_CHECK_CODE(0x21548CA921548CA9, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Mini racers MATCH_CHECK_CODE(0xBC9B2CC34ED04DA5, SAVE_TYPE_FLASHRAM, FEAT_NONE), // Starcraft 64 [Prototype 2000] + MATCH_CHECK_CODE(0x5D40ED2C10D6ABCF, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Viewpoint 2064 + MATCH_CHECK_CODE(0x7280E03F497689BA, SAVE_TYPE_EEPROM_4K, FEAT_NONE), // Viewpoint 2064 [ENG patch] MATCH_CHECK_CODE(0xCDB8B4D08832352D, SAVE_TYPE_SRAM, FEAT_RPAK), // Jet Force Gemini [USA CRACK] MATCH_CHECK_CODE(0xB66E0F7C2709C22F, SAVE_TYPE_SRAM, FEAT_RPAK), // Jet Force Gemini [PAL CRACK] From e5c1f341426ef1df29748af62acdec9f5019c09c Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 29 Apr 2024 18:43:58 +0100 Subject: [PATCH 10/16] Update rolling release GH action version (#98) ## Description Bumps softprops/action-gh-release ## Motivation and Context Although this might not solve it, currently our rolling release shows the release date far behind that actually released. ## How Has This Been Tested? ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [x] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] 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 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 98730deb..9eed490d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,7 +77,7 @@ jobs: # continue-on-error: true - name: Upload rolling release - uses: softprops/action-gh-release@v0.1.15 + uses: softprops/action-gh-release@v2 if: github.ref == 'refs/heads/main' with: name: Rolling release From 4995fe55e1990650e8624bc038090bcd803c3de0 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 29 Apr 2024 18:57:02 +0100 Subject: [PATCH 11/16] Bump actions-gh-pages version (#99) ## Description Bumps peaceiris/actions-gh-pages to latest release ## Motivation and Context Keep workflow up-to-date. ## How Has This Been Tested? ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [x] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] 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 [skip ci] --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9eed490d..170726a9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,7 +105,7 @@ jobs: doxyfile-path: './Doxyfile' - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 if: github.ref == 'refs/heads/main' with: github_token: ${{ secrets.GITHUB_TOKEN }} From 4bcaced46188b4a1d9928676568b5df3db7a2657 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 29 Apr 2024 20:14:59 +0100 Subject: [PATCH 12/16] Add ares emulator support to readme Fix libdragon branch used. --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 500e4088..b331bb24 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,6 @@ You can use a dev container in VSCode to ease development. Make sure that your firmware is compatible (currently v2.18.0+) See: [here](https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.18.0/docs/00_quick_startup_guide.md#firmware-backupupdate) - #### From the devcontainer It is not currently possible to directly communicate with USB devices. BUT, as a work around you can use a proxy TCP/IP connection @@ -134,13 +133,18 @@ The ROM can be found in the `output` directory. NOTE: a "release" version of the SC64 menu is called `sc64menu.n64` and can be created for when you want to add it directly to the SDCard. This is generated by running `make all` or running `make sc64`. +### Ares Emulator +For ease of development and debugging, the menu ROM is able to run in the Ares emulator (without most flashcart features). + +* Ensure you have the Ares emulator on you computer. +* Load the `N64FlashcartMenu.n64` ROM. ### Others * Add the required file to the correct folder on your SD card. # Update Libdragon submodule -This repo currently uses the `unstable` branch as a submodule at a specific commit. +This repo currently uses the `preview` branch as a submodule at a specific commit. To update to the latest version, use `git submodule update --remote ` from the terminal. # Generate documentation From 9e7a47476ef781299146a5573bc38e7694d2ed6e Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 29 Apr 2024 22:42:33 +0100 Subject: [PATCH 13/16] Improve patch recognition --- src/menu/views/file_info.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu/views/file_info.c b/src/menu/views/file_info.c index f4819de8..211f5791 100644 --- a/src/menu/views/file_info.c +++ b/src/menu/views/file_info.c @@ -8,7 +8,7 @@ static const char *n64_rom_extensions[] = { "z64", "n64", "v64", "rom", NULL }; static const char *text_extensions[] = { "txt", NULL }; static const char *config_extensions[] = { "ini", "cfg", "yml", "yaml", "toml", NULL }; static const char *save_extensions[] = { "sav", "eep", "eeprom", "sra", "srm", "ram", "fla", "flashram", NULL }; -static const char *patch_extensions[] = { "ips", "aps", "pps", "xdelta", NULL }; +static const char *patch_extensions[] = { "aps", "bps", "ips", "pps", "ups", "xdelta", NULL }; static const char *archive_extensions[] = { "zip", "rar", "7z", "tar", "gz", NULL }; static const char *image_extensions[] = { "png", "jpg", "gif", NULL }; static const char *music_extensions[] = { "mp3", "wav", "ogg", "wma", "flac", NULL }; From 24f49f144709d2caba9f6996521a838db33143e2 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 1 May 2024 22:43:45 +0100 Subject: [PATCH 14/16] Update 64drive README.md Use archived URL as website link no longer exists. --- src/flashcart/64drive/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flashcart/64drive/README.md b/src/flashcart/64drive/README.md index bd961b85..6809afcc 100644 --- a/src/flashcart/64drive/README.md +++ b/src/flashcart/64drive/README.md @@ -2,7 +2,7 @@ ### Official documentation -http://64drive.retroactive.be/64drive_hardware_spec.pdf +https://web.archive.org/web/20220611032320/http://64drive.retroactive.be/64drive_hardware_spec.pdf ### Save location offset in SDRAM From 01968b55db881c4ed65d99b16d727c08ba354dc5 Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Sun, 5 May 2024 00:19:20 +0200 Subject: [PATCH 15/16] Cheats support (backend only) (#94) ## Description This PR implements cheat support (patcher + engine) with a simple API to provide Action Replay/Game Shark compatible cheats (with exception of cheats that utilize GS button). API consist of a single pointer to an array of the cheats ended with a double zero entry, For example, if you want to pass these cheats to the patcher: ``` D01F9B91 0020 // Majora's Mask (USA) Inventory Editor 803FDA3F 0002 ``` Put cheats in a `uint32_t` array as such (notice last two entries are zeros): ``` uint32_t cheats[] = { 0xD01F9B91, 0x0020, 0x803FDA3F, 0x0002, 0, 0, }; ``` And pass this array as a boot parameter: `menu->boot_params->cheat_list = cheats;` ## Motivation and Context To provide users with ability to run game modifications in a easy way. ## How Has This Been Tested? On a SummerCart64 flashcart + assembly instructions generation verified in ares emulator via GDB. ## Screenshots No 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: Polprzewodnikowy --- Makefile | 1 + src/boot/boot.c | 25 ++- src/boot/boot.h | 1 + src/boot/cheats.c | 335 ++++++++++++++++++++++++++++ src/boot/cheats.h | 10 + src/boot/reboot.S | 8 +- src/boot/reboot.h | 17 ++ src/boot/vr4300_asm.h | 397 +++++++++++++++++++++++++++++++++ src/menu/usb_comm.c | 1 + src/menu/views/load_disk.c | 2 + src/menu/views/load_emulator.c | 1 + src/menu/views/load_rom.c | 1 + 12 files changed, 787 insertions(+), 12 deletions(-) create mode 100644 src/boot/cheats.c create mode 100644 src/boot/cheats.h create mode 100644 src/boot/reboot.h create mode 100644 src/boot/vr4300_asm.h diff --git a/Makefile b/Makefile index f55c9532..3b6a1f99 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ N64_CFLAGS += -iquote $(SOURCE_DIR) -iquote $(ASSETS_DIR) -I $(SOURCE_DIR)/libs SRCS = \ main.c \ boot/boot.c \ + boot/cheats.c \ boot/cic.c \ boot/reboot.S \ flashcart/64drive/64drive_ll.c \ diff --git a/src/boot/boot.c b/src/boot/boot.c index d9d2eca3..9aaac778 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -2,7 +2,9 @@ #include "boot_io.h" #include "boot.h" +#include "cheats.h" #include "cic.h" +#include "reboot.h" #define C0_STATUS_FR (1 << 26) @@ -10,10 +12,6 @@ #define C0_STATUS_CU1 (1 << 29) -extern uint32_t reboot_start __attribute__((section(".text"))); -extern size_t reboot_size __attribute__((section(".text"))); - - static io32_t *boot_get_device_base (boot_params_t *params) { io32_t *device_base_address = ROM_CART; if (params->device_type == BOOT_DEVICE_TYPE_64DD) { @@ -22,7 +20,7 @@ static io32_t *boot_get_device_base (boot_params_t *params) { return device_base_address; } -static void boot_detect_cic_seed (boot_params_t *params) { +static cic_type_t boot_detect_cic (boot_params_t *params) { io32_t *base = boot_get_device_base(params); uint8_t ipl3[IPL3_LENGTH] __attribute__((aligned(8))); @@ -31,11 +29,17 @@ static void boot_detect_cic_seed (boot_params_t *params) { dma_read_raw_async(ipl3, (uint32_t) (&base[16]), sizeof(ipl3)); dma_wait(); - params->cic_seed = cic_get_seed(cic_detect(ipl3)); + return cic_detect(ipl3); } void boot (boot_params_t *params) { + cic_type_t cic_type = boot_detect_cic(params); + + if (params->detect_cic_seed) { + params->cic_seed = cic_get_seed(cic_type); + } + if (params->tv_type == BOOT_TV_TYPE_PASSTHROUGH) { switch (get_tv_type()) { case TV_PAL: @@ -53,10 +57,6 @@ void boot (boot_params_t *params) { } } - if (params->detect_cic_seed) { - boot_detect_cic_seed(params); - } - C0_WRITE_STATUS(C0_STATUS_CU1 | C0_STATUS_CU0 | C0_STATUS_FR); while (!(cpu_io_read(&SP->SR) & SP_SR_HALT)); @@ -123,12 +123,16 @@ void boot (boot_params_t *params) { cpu_io_write(&ipl3_dst[i], io_read((uint32_t) (&ipl3_src[i]))); } + bool cheats_installed = cheats_install(cic_type, params->cheat_list); + + register uint32_t skip_rdram_reset asm ("a0"); register uint32_t boot_device asm ("s3"); register uint32_t tv_type asm ("s4"); register uint32_t reset_type asm ("s5"); register uint32_t cic_seed asm ("s6"); register uint32_t version asm ("s7"); + skip_rdram_reset = cheats_installed; boot_device = (params->device_type & 0x01); tv_type = (params->tv_type & 0x03); reset_type = BOOT_RESET_TYPE_COLD; @@ -141,6 +145,7 @@ void boot (boot_params_t *params) { asm volatile ( "la $t3, reboot \n" "jr $t3 \n" :: + [skip_rdram_reset] "r" (skip_rdram_reset), [boot_device] "r" (boot_device), [tv_type] "r" (tv_type), [reset_type] "r" (reset_type), diff --git a/src/boot/boot.h b/src/boot/boot.h index 0bbd910d..3abd87da 100644 --- a/src/boot/boot.h +++ b/src/boot/boot.h @@ -38,6 +38,7 @@ typedef struct { boot_tv_type_t tv_type; uint8_t cic_seed; bool detect_cic_seed; + uint32_t *cheat_list; } boot_params_t; diff --git a/src/boot/cheats.c b/src/boot/cheats.c new file mode 100644 index 00000000..fd76eedc --- /dev/null +++ b/src/boot/cheats.c @@ -0,0 +1,335 @@ +#include + +#include "boot_io.h" +#include "cheats.h" +#include "vr4300_asm.h" + +#define HIT_INVALIDATE_I ((4 << 2) | 0) +#define HIT_WRITE_BACK_D ((6 << 2) | 1) + +#define D_CACHE_LINE_SIZE (16) + +#define CAUSE_IRQ_PRE_NMI (1 << 12) +#define CAUSE_EXC_CODE_MASK (0x7C) +#define CAUSE_EXC_CODE_WATCH (0x5C) + +#define WATCHLO_W (1 << 0) + +#define RELOCATED_EXCEPTION_HANDLER_ADDRESS (0x80000120) +#define EXCEPTION_HANDLER_ADDRESS (0x80000180) +#define PATCHER_ADDRESS (0x80700000) +#define ENGINE_TEMPORARY_ADDRESS (PATCHER_ADDRESS + 0x10000) +#define DEFAULT_ENGINE_ADDRESS (0x807C5C00) + +typedef struct { + uint8_t type; + uint32_t address; + uint16_t value; +} cheat_t; + +typedef struct { + cheat_t main; + cheat_t sub; +} cheat_entry_t; + +typedef enum { + SPECIAL_DISABLE_EXPANSION_PAK = 0xEE, + SPECIAL_WRITE_BYTE_ON_BOOT = 0xF0, + SPECIAL_WRITE_SHORT_ON_BOOT = 0xF1, + SPECIAL_SET_STORE_LOCATION = 0xFF, +} cheat_type_special_t; + +#define IS_WIDTH_16(t) ((t) & (1 << 0)) +#define IS_CONDITION_NOT_EQUAL(t) ((t) & (1 << 1)) +#define IS_CONDITION_GS_BUTTON(t) ((t) & (1 << 3)) + +#define IS_TYPE_REPEATER(t) ((t) == 0x50) +#define IS_TYPE_WRITE(t) ((((t)&0xF0) == 0x80) || (((t)&0xF0) == 0xA0)) +#define IS_TYPE_CONDITIONAL(t) (((t)&0xF0) == 0xD0) + +#define IS_DOUBLE_ENTRY(t) (IS_TYPE_CONDITIONAL(t) || IS_TYPE_REPEATER(t)) + +static bool cheats_patch_ipl3 (cic_type_t cic_type, io32_t *target) { + uint32_t patch_offset = 0; + uint32_t j_instruction = I_J((uint32_t)(target)); + + io32_t *ipl3 = SP_MEM->DMEM; + + switch (cic_type) { + case CIC_5101: patch_offset = 476; break; + case CIC_6101: + case CIC_7102: patch_offset = 476; break; + case CIC_x102: patch_offset = 475; break; + case CIC_x103: patch_offset = 472; break; + case CIC_x105: patch_offset = 499; break; + case CIC_x106: patch_offset = 488; break; + default: return true; + } + + // NOTE: Check for "jr $t1" instruction + // Libdragon IPL3 could be brute-force signed with any retail + // CIC seed and checksum, and we support only retail libultra IPL3 + if (cpu_io_read(&ipl3[patch_offset]) != I_JR(REG_T1)) { + return false; + } + + switch (cic_type) { + case CIC_x105: + // NOTE: This disables game code checksum verification + cpu_io_write(&ipl3[486], I_NOP()); + break; + + case CIC_x106: + // NOTE: CIC x106 IPL3 is partially scrambled + j_instruction ^= 0x8188764A; + break; + + default: break; + } + + cpu_io_write(&ipl3[patch_offset], j_instruction); + + return false; +} + +static bool cheats_get_next (uint32_t **cheat_list, cheat_entry_t *cheat) { + cheat_t *c = &cheat->main; + cheat->sub.type = 0; + + for (int i = 0; i < 2; i++) { + uint32_t raw[2] = {(*cheat_list)[0], (*cheat_list)[1]}; + + (*cheat_list) += 2; + + if ((raw[0] == 0) && (raw[1] == 0)) { + return false; + } + + c->type = ((raw[0] >> 24) & 0xFF); + c->address = (raw[0] & 0xA07FFFFF); + c->value = (raw[1] & 0xFFFF); + + if (!IS_DOUBLE_ENTRY(c->type)) { + break; + } + + c = &cheat->sub; + } + + return true; +} + +static io32_t *cheats_get_engine_address (uint32_t *cheat_list) { + cheat_entry_t cheat; + while (cheats_get_next(&cheat_list, &cheat)) { + if (cheat.main.type == SPECIAL_SET_STORE_LOCATION) { + return (io32_t *)(cheat.main.address & 0x807FFFFF); + } + } + return (io32_t *)(DEFAULT_ENGINE_ADDRESS); +} + +static void cheats_update_cache (volatile void *start, volatile void *end) { + data_cache_hit_writeback(start, (end - start)); + inst_cache_hit_invalidate(start, (end - start)); +} + +bool cheats_install (cic_type_t cic_type, uint32_t *cheat_list) { + if (!cheat_list) { + return false; + } + + io32_t *engine_start = (io32_t *)(ENGINE_TEMPORARY_ADDRESS); + io32_t *engine_p = engine_start; + + io32_t *patcher_start = (io32_t *)(PATCHER_ADDRESS); + io32_t *patcher_p = patcher_start; + + if (cheats_patch_ipl3(cic_type, patcher_start)) { + return false; + } + + io32_t *final_engine_address = cheats_get_engine_address(cheat_list); + + // Original watch exception handler code written by Jay Oster 'Parasyte' + // https://github.com/parasyte/alt64/blob/master/utils.c#L1024-L1054 + + uint32_t ori_placeholder_instruction = I_ORI(REG_ZERO, REG_K0, A_OFFSET(RELOCATED_EXCEPTION_HANDLER_ADDRESS)); + uint32_t ori_placeholder_address = (uint32_t)(final_engine_address + 20); + + // Load cause register + *engine_p++ = I_MFC0(REG_K0, C0_REG_CAUSE); + + // Disable watch exception when reset button is pressed + *engine_p++ = I_ANDI(REG_K1, REG_K0, CAUSE_IRQ_PRE_NMI); + *engine_p++ = I_BNEL(REG_K1, REG_ZERO, 1); + *engine_p++ = I_MTC0(REG_ZERO, C0_REG_WATCH_LO); + + // Check if watch exception ocurred, if yes then proceed to relocate the game exception handler + *engine_p++ = I_ANDI(REG_K0, REG_K0, CAUSE_EXC_CODE_MASK); + *engine_p++ = I_ORI(REG_K1, REG_ZERO, CAUSE_EXC_CODE_WATCH); + *engine_p++ = I_BNE(REG_K0, REG_K1, 15); // Skips to after the 'eret' instruction + + // Extract base register number from the store instruction + *engine_p++ = I_MFC0(REG_K1, C0_REG_EPC); + *engine_p++ = I_LW(REG_K1, 0, REG_K1); + *engine_p++ = I_LUI(REG_K0, 0x03E0); + *engine_p++ = I_AND(REG_K1, REG_K0, REG_K1); + *engine_p++ = I_SRL(REG_K1, REG_K1, 5); + + // Update create final instruction and update its target register number + *engine_p++ = I_LUI(REG_K0, ori_placeholder_instruction >> 16); + *engine_p++ = I_ORI(REG_K0, REG_K0, ori_placeholder_instruction); + *engine_p++ = I_OR(REG_K0, REG_K0, REG_K1); + + // Write created instruction into placeholder + *engine_p++ = I_LUI(REG_K1, A_BASE(ori_placeholder_address)); + *engine_p++ = I_SW(REG_K0, A_OFFSET(ori_placeholder_address), REG_K1); + + // Force write and instruction cache invalidation + *engine_p++ = I_CACHE(HIT_WRITE_BACK_D, A_OFFSET(ori_placeholder_address), REG_K1); + *engine_p++ = I_CACHE(HIT_INVALIDATE_I, A_OFFSET(ori_placeholder_address), REG_K1); + + // Load address base and execute created instruction + *engine_p++ = I_LUI(REG_K0, A_BASE(RELOCATED_EXCEPTION_HANDLER_ADDRESS)); + *engine_p++ = I_NOP(); + + // Return from the exception + *engine_p++ = I_ERET(); + + cheat_entry_t cheat; + + while (cheats_get_next(&cheat_list, &cheat)) { + cheat_t *c = &cheat.main; + + if (IS_TYPE_REPEATER(c->type)) { + if ((!IS_TYPE_WRITE(cheat.sub.type)) || IS_CONDITION_GS_BUTTON(cheat.sub.type)) { + continue; + } + + int count = ((c->address >> 8) & 0xFF); + int step = (c->address & 0xFF); + int16_t increment = (int16_t)(c->value); + + c = &cheat.sub; + + for (int i = 0; i < count; i++) { + *engine_p++ = I_LUI(REG_K0, A_BASE(c->address)); + *engine_p++ = I_ORI(REG_K1, REG_ZERO, c->value); + *engine_p++ = IS_WIDTH_16(c->type) ? I_SH(REG_K1, A_OFFSET(c->address), REG_K0) + : I_SB(REG_K1, A_OFFSET(c->address), REG_K0); + + c->address += step; + c->value += increment; + } + + continue; + } + + if (IS_TYPE_CONDITIONAL(c->type)) { + if ((!IS_TYPE_WRITE(cheat.sub.type)) || IS_CONDITION_GS_BUTTON(cheat.sub.type)) { + continue; + } + + *engine_p++ = I_LUI(REG_K0, A_BASE(c->address)); + *engine_p++ = IS_WIDTH_16(c->type) ? I_LHU(REG_K0, A_OFFSET(c->address), REG_K0) + : I_LBU(REG_K0, A_OFFSET(c->address), REG_K0); + *engine_p++ = I_ORI(REG_K1, REG_ZERO, c->value & (IS_WIDTH_16(c->type) ? 0xFFFF : 0xFF)); + *engine_p++ = IS_CONDITION_NOT_EQUAL(c->type) ? I_BEQ(REG_K0, REG_K1, 3) : I_BNE(REG_K0, REG_K1, 3); + + c = &cheat.sub; + } + + if (IS_TYPE_WRITE(c->type)) { + if (IS_CONDITION_GS_BUTTON(c->type)) { + continue; + } + + *engine_p++ = I_LUI(REG_K0, A_BASE(c->address)); + *engine_p++ = I_ORI(REG_K1, REG_ZERO, c->value); + *engine_p++ = IS_WIDTH_16(c->type) ? I_SH(REG_K1, A_OFFSET(c->address), REG_K0) + : I_SB(REG_K1, A_OFFSET(c->address), REG_K0); + + continue; + } + + switch (c->type) { + case SPECIAL_WRITE_BYTE_ON_BOOT: + case SPECIAL_WRITE_SHORT_ON_BOOT: { + *patcher_p++ = I_LUI(REG_K0, A_BASE(c->address)); + *patcher_p++ = I_ORI(REG_K1, REG_ZERO, c->value); + *patcher_p++ = IS_WIDTH_16(c->type) ? I_SH(REG_K1, A_OFFSET(c->address), REG_K0) + : I_SB(REG_K1, A_OFFSET(c->address), REG_K0); + break; + } + case SPECIAL_DISABLE_EXPANSION_PAK: { + *patcher_p++ = I_LUI(REG_K0, 0xA000); + *patcher_p++ = I_LUI(REG_K1, 0x0040); + *patcher_p++ = I_SW(REG_K1, 0x318, REG_K0); + *patcher_p++ = I_SW(REG_K1, 0x3F0, REG_K0); + break; + } + default: break; + } + } + + *engine_p++ = I_J(RELOCATED_EXCEPTION_HANDLER_ADDRESS); + *engine_p++ = I_NOP(); + + uint32_t j_engine_from_handler = I_J((uint32_t)(final_engine_address)); + + // Copy engine to the final location + *patcher_p++ = I_LUI(REG_T3, A_BASE((uint32_t)(engine_start))); + *patcher_p++ = I_ADDIU(REG_T3, REG_T3, A_OFFSET((uint32_t)(engine_start))); + + *patcher_p++ = I_LUI(REG_T4, A_BASE((uint32_t)(engine_p))); + *patcher_p++ = I_ADDIU(REG_T4, REG_T4, A_OFFSET((uint32_t)(engine_p))); + + *patcher_p++ = I_LUI(REG_T5, A_BASE((uint32_t)(final_engine_address))); + *patcher_p++ = I_ADDIU(REG_T5, REG_T5, A_OFFSET((uint32_t)(final_engine_address))); + + *patcher_p++ = I_ORI(REG_T6, REG_ZERO, 0); + + *patcher_p++ = I_LW(REG_K1, 0, REG_T3); + *patcher_p++ = I_SW(REG_K1, 0, REG_T5); + *patcher_p++ = I_ADDIU(REG_T3, REG_T3, 4); + *patcher_p++ = I_ADDIU(REG_T5, REG_T5, 4); + *patcher_p++ = I_BNE(REG_T3, REG_T4, -5); + *patcher_p++ = I_ADDIU(REG_T6, REG_T6, 4); + + // Force write and invalidate instruction cache + *patcher_p++ = I_LUI(REG_T5, A_BASE((uint32_t)(final_engine_address))); + *patcher_p++ = I_ADDIU(REG_T5, REG_T5, A_OFFSET((uint32_t)(final_engine_address))); + + *patcher_p++ = I_CACHE(HIT_WRITE_BACK_D, 0, REG_T5); + *patcher_p++ = I_CACHE(HIT_INVALIDATE_I, 0, REG_T5); + *patcher_p++ = I_ADDIU(REG_T6, REG_T6, -D_CACHE_LINE_SIZE); + *patcher_p++ = I_BGTZ(REG_T6, -4); + *patcher_p++ = I_ADDIU(REG_T5, REG_T5, D_CACHE_LINE_SIZE); + + // Write jump instruction to the exception handler + *patcher_p++ = I_LUI(REG_K0, A_BASE(EXCEPTION_HANDLER_ADDRESS)); + *patcher_p++ = I_ADDIU(REG_K0, REG_K0, A_OFFSET(EXCEPTION_HANDLER_ADDRESS)); + + *patcher_p++ = I_LUI(REG_K1, j_engine_from_handler >> 16); + *patcher_p++ = I_ORI(REG_K1, REG_K1, j_engine_from_handler); + *patcher_p++ = I_SW(REG_K1, 0, REG_K0); + *patcher_p++ = I_SW(REG_ZERO, 4, REG_K0); + + *patcher_p++ = I_CACHE(HIT_WRITE_BACK_D, 0, REG_K0); + *patcher_p++ = I_CACHE(HIT_INVALIDATE_I, 0, REG_K0); + + // Set watch exception on address 0x80000180 + *patcher_p++ = I_ORI(REG_K1, REG_ZERO, EXCEPTION_HANDLER_ADDRESS | WATCHLO_W); + *patcher_p++ = I_MTC0(REG_K1, C0_REG_WATCH_LO); + *patcher_p++ = I_MTC0(REG_ZERO, C0_REG_WATCH_HI); + + // Jump back to the game code + *patcher_p++ = I_JR(REG_T1); + *patcher_p++ = I_NOP(); + + cheats_update_cache(engine_start, engine_p); + cheats_update_cache(patcher_start, patcher_p); + + return true; +} diff --git a/src/boot/cheats.h b/src/boot/cheats.h new file mode 100644 index 00000000..53cbbe5e --- /dev/null +++ b/src/boot/cheats.h @@ -0,0 +1,10 @@ +#ifndef CHEATS_H__ +#define CHEATS_H__ + +#include + +#include "cic.h" + +bool cheats_install (cic_type_t cic_type, uint32_t *cheat_list); + +#endif diff --git a/src/boot/reboot.S b/src/boot/reboot.S index 5d0ab6de..e78cbbc9 100644 --- a/src/boot/reboot.S +++ b/src/boot/reboot.S @@ -1,3 +1,6 @@ +#include "reboot.h" + + #define IPL3_ENTRY 0xA4000040 #define REBOOT_ADDRESS 0xA4001000 #define STACK_ADDRESS 0xA4001FF0 @@ -34,9 +37,10 @@ reboot_entry: li $sp, STACK_ADDRESS -reset_rdram: - bnez $s5, reset_rdram_skip + bnez $a0, reset_rdram_skip # Skip when cheats are enabled + bnez $s5, reset_rdram_skip # Skip when reset type is set to NMI +reset_rdram: li $t0, RI_ADDRESS sw $zero, RI_REFRESH($t0) diff --git a/src/boot/reboot.h b/src/boot/reboot.h new file mode 100644 index 00000000..d8ec302b --- /dev/null +++ b/src/boot/reboot.h @@ -0,0 +1,17 @@ +#ifndef REBOOT_H__ +#define REBOOT_H__ + + +#ifndef __ASSEMBLER__ + +#include +#include + + +extern uint32_t reboot_start __attribute__((section(".text"))); +extern size_t reboot_size __attribute__((section(".text"))); + +#endif + + +#endif diff --git a/src/boot/vr4300_asm.h b/src/boot/vr4300_asm.h new file mode 100644 index 00000000..59dfd77c --- /dev/null +++ b/src/boot/vr4300_asm.h @@ -0,0 +1,397 @@ +#ifndef VR4300_ASM_H__ +#define VR4300_ASM_H__ + +#include + +typedef union { + uint32_t raw; + + struct { + uint32_t op : 6; + uint32_t rs : 5; + uint32_t rt : 5; + uint32_t imm : 16; + } i_type; + + struct { + uint32_t op : 6; + uint32_t target : 26; + } j_type; + + struct { + uint32_t op : 6; + uint32_t rs : 5; + uint32_t rt : 5; + uint32_t rd : 5; + uint32_t sa : 5; + uint32_t funct : 6; + } r_type; + + struct { + uint32_t op : 6; + uint32_t co : 1; + uint32_t funct : 25; + } c_type; +} vr4300_instruction_t; + +typedef enum { + OP_SPECIAL, + OP_REGIMM, + OP_J, + OP_JAL, + OP_BEQ, + OP_BNE, + OP_BLEZ, + OP_BGTZ, + OP_ADDI, + OP_ADDIU, + OP_SLTI, + OP_SLTIU, + OP_ANDI, + OP_ORI, + OP_XORI, + OP_LUI, + OP_COP0, + OP_COP1, + OP_COP2, + __OP_RESERVED_19, + OP_BEQL, + OP_BNEL, + OP_BLEZL, + OP_BGTZL, + OP_DADDI, + OP_DADDIU, + OP_LDL, + OP_LDR, + __OP_RESERVED_28, + __OP_RESERVED_29, + __OP_RESERVED_30, + __OP_RESERVED_31, + OP_LB, + OP_LH, + OP_LWL, + OP_LW, + OP_LBU, + OP_LHU, + OP_LWR, + OP_LWU, + OP_SB, + OP_SH, + OP_SWL, + OP_SW, + OP_SDL, + OP_SDR, + OP_SWR, + OP_CACHE, + OP_LL, + OP_LWC1, + OP_LWC2, + __OP_RESERVED_51, + OP_LLD, + OP_LDC1, + OP_LDC2, + OP_LD, + OP_SC, + OP_SWC1, + OP_SWC2, + __OP_RESERVED_59, + OP_SCD, + OP_SDC1, + OP_SDC2, + OP_SD, +} vr4300_op_t; + +typedef enum { + FUNCT_SSL, + __FUNCT_RESERVED_1, + FUNCT_SRL, + FUNCT_SRA, + FUNCT_SLLV, + __FUNCT_RESERVED_5, + FUNCT_SRLV, + FUNCT_SRAV, + FUNCT_JR, + FUNCT_JALR, + __FUNCT_RESERVED_10, + __FUNCT_RESERVED_11, + FUNCT_SYSCALL, + FUNCT_BREAK, + __FUNCT_RESERVED_14, + FUNCT_SYNC, + FUNCT_MFHI, + FUNCT_MTHI, + FUNCT_MFLO, + FUNCT_MTLO, + FUNCT_DSLLV, + __FUNCT_RESERVED_21, + FUNCT_DSRLV, + FUNCT_DSRAV, + FUNCT_MULT, + FUNCT_MULTU, + FUNCT_DIV, + FUNCT_DIVU, + FUNCT_DMULT, + FUNCT_DMULTU, + FUNCT_DDIV, + FUNCT_DDIVU, + FUNCT_ADD, + FUNCT_ADDU, + FUNCT_SUB, + FUNCT_SUBU, + FUNCT_AND, + FUNCT_OR, + FUNCT_XOR, + FUNCT_NOR, + __FUNCT_RESERVED_40, + __FUNCT_RESERVED_41, + FUNCT_SLT, + FUNCT_SLTU, + FUNCT_DADD, + FUNCT_DADDU, + FUNCT_DSUB, + FUNCT_DSUBU, + FUNCT_TGE, + FUNCT_TGEU, + FUNCT_TLT, + FUNCT_TLTU, + FUNCT_TEQ, + __FUNCT_RESERVED_53, + FUNCT_TNE, + __FUNCT_RESERVED_55, + FUNCT_DSLL, + __FUNCT_RESERVED_57, + FUNCT_DSRL, + FUNCT_DSRA, + FUNCT_DSLL32, + __FUNCT_RESERVED_61, + FUNCT_DSRL32, + FUNCT_DSRA32, +} vr4300_funct_t; + +typedef enum { + REGIMM_BLTZ, + REGIMM_BGEZ, + REGIMM_BLTZL, + REGIMM_BGEZL, + __REGIMM_RESERVED_4, + __REGIMM_RESERVED_5, + __REGIMM_RESERVED_6, + __REGIMM_RESERVED_7, + REGIMM_TGEI, + REGIMM_TGEIU, + REGIMM_TLTI, + REGIMM_TLTIU, + REGIMM_TEQI, + __REGIMM_RESERVED_13, + REGIMM_TNEI, + __REGIMM_RESERVED_15, + REGIMM_BLTZAL, + REGIMM_BGEZAL, + REGIMM_BLTZALL, + REGIMM_BGEZALL, + __REGIMM_RESERVED_20, + __REGIMM_RESERVED_21, + __REGIMM_RESERVED_22, + __REGIMM_RESERVED_23, + __REGIMM_RESERVED_24, + __REGIMM_RESERVED_25, + __REGIMM_RESERVED_26, + __REGIMM_RESERVED_27, + __REGIMM_RESERVED_28, + __REGIMM_RESERVED_29, + __REGIMM_RESERVED_30, + __REGIMM_RESERVED_31, +} vr4300_regimm_t; + +typedef enum { + REG_ZERO, + REG_AT, + REG_V0, + REG_V1, + REG_A0, + REG_A1, + REG_A2, + REG_A3, + REG_T0, + REG_T1, + REG_T2, + REG_T3, + REG_T4, + REG_T5, + REG_T6, + REG_T7, + REG_S0, + REG_S1, + REG_S2, + REG_S3, + REG_S4, + REG_S5, + REG_S6, + REG_S7, + REG_T8, + REG_T9, + REG_K0, + REG_K1, + REG_GP, + REG_SP, + REG_FP, + REG_RA, +} vr4300_reg_t; + +typedef enum { + C0_REG_INDEX, + C0_REG_RANDOM, + C0_REG_ENTRY_LO_0, + C0_REG_ENTRY_LO_1, + C0_REG_CONTEXT, + C0_REG_PAGE_MASK, + C0_REG_WIRED, + __C0_REG_RESERVED_7, + C0_REG_BAD_V_ADDR, + C0_REG_COUNT, + C0_REG_ENTRY_HI, + C0_REG_COMPARE, + C0_REG_STATUS, + C0_REG_CAUSE, + C0_REG_EPC, + C0_REG_PR_ID, + C0_REG_CONFIG, + C0_REG_LL_ADDR, + C0_REG_WATCH_LO, + C0_REG_WATCH_HI, + C0_REG_X_CONTEXT, + __C0_REG_RESERVED_21, + __C0_REG_RESERVED_22, + __C0_REG_RESERVED_23, + __C0_REG_RESERVED_24, + __C0_REG_RESERVED_25, + C0_REG_PARITY_ERROR, + C0_REG_CACHE_ERROR, + C0_REG_TAG_LO, + C0_REG_TAG_HI, + C0_REG_ERROR_EPC, + __C0_REG_RESERVED_31, +} vr4300_c0_reg_t; + +typedef enum { + COPZ_RS_MF, + COPZ_RS_DMF, + COPZ_RS_CF, + __COPZ_RS_RESERVED_3, + COPZ_RS_MT, + COPZ_RS_DMT, + COPZ_RS_CT, + __COPZ_RS_RESERVED_7, + COPZ_RS_BC, + __COPZ_RS_RESERVED_9, + __COPZ_RS_RESERVED_10, + __COPZ_RS_RESERVED_11, + __COPZ_RS_RESERVED_12, + __COPZ_RS_RESERVED_13, + __COPZ_RS_RESERVED_14, + __COPZ_RS_RESERVED_15, +} vr4300_copz_rs_t; + +typedef enum { + __C0_FUNCT_RESERVED_0, + C0_FUNCT_TLBR, + C0_FUNCT_TLBWI, + __C0_FUNCT_RESERVED_3, + __C0_FUNCT_RESERVED_4, + __C0_FUNCT_RESERVED_5, + C0_FUNCT_TLBWR, + __C0_FUNCT_RESERVED_7, + C0_FUNCT_TLBP, + __C0_FUNCT_RESERVED_9, + __C0_FUNCT_RESERVED_10, + __C0_FUNCT_RESERVED_11, + __C0_FUNCT_RESERVED_12, + __C0_FUNCT_RESERVED_13, + __C0_FUNCT_RESERVED_14, + __C0_FUNCT_RESERVED_15, + __C0_FUNCT_RESERVED_16, + __C0_FUNCT_RESERVED_17, + __C0_FUNCT_RESERVED_18, + __C0_FUNCT_RESERVED_19, + __C0_FUNCT_RESERVED_20, + __C0_FUNCT_RESERVED_21, + __C0_FUNCT_RESERVED_22, + __C0_FUNCT_RESERVED_23, + C0_FUNCT_ERET, + __C0_FUNCT_RESERVED_25, + __C0_FUNCT_RESERVED_26, + __C0_FUNCT_RESERVED_27, + __C0_FUNCT_RESERVED_28, + __C0_FUNCT_RESERVED_29, + __C0_FUNCT_RESERVED_30, + __C0_FUNCT_RESERVED_31, + __C0_FUNCT_RESERVED_32, + __C0_FUNCT_RESERVED_33, + __C0_FUNCT_RESERVED_34, + __C0_FUNCT_RESERVED_35, + __C0_FUNCT_RESERVED_36, + __C0_FUNCT_RESERVED_37, + __C0_FUNCT_RESERVED_38, + __C0_FUNCT_RESERVED_39, + __C0_FUNCT_RESERVED_40, + __C0_FUNCT_RESERVED_41, + __C0_FUNCT_RESERVED_42, + __C0_FUNCT_RESERVED_43, + __C0_FUNCT_RESERVED_44, + __C0_FUNCT_RESERVED_45, + __C0_FUNCT_RESERVED_46, + __C0_FUNCT_RESERVED_47, + __C0_FUNCT_RESERVED_48, + __C0_FUNCT_RESERVED_49, + __C0_FUNCT_RESERVED_50, + __C0_FUNCT_RESERVED_51, + __C0_FUNCT_RESERVED_52, + __C0_FUNCT_RESERVED_53, + __C0_FUNCT_RESERVED_54, + __C0_FUNCT_RESERVED_55, + __C0_FUNCT_RESERVED_56, + __C0_FUNCT_RESERVED_57, + __C0_FUNCT_RESERVED_58, + __C0_FUNCT_RESERVED_59, + __C0_FUNCT_RESERVED_60, + __C0_FUNCT_RESERVED_61, + __C0_FUNCT_RESERVED_62, + __C0_FUNCT_RESERVED_63, +} vr4300_c0_funct; + +#define __ASM_I_INST(o, s, t, i) \ + (((vr4300_instruction_t){.i_type = {.op = (o), .rs = (s), .rt = (t), .imm = (i)&0xFFFF}}).raw) +#define __ASM_J_INST(o, t) (((vr4300_instruction_t){.j_type = {.op = (o), .target = (t)&0x3FFFFFF}}).raw) +#define __ASM_R_INST(o, s, t, d, a, f) \ + (((vr4300_instruction_t){.r_type = {.op = (o), .rs = (s), .rt = (t), .rd = (d), .sa = (a), .funct = (f)}}).raw) +#define __ASM_C_INST(o, c, f) (((vr4300_instruction_t){.c_type = {.op = (o), .co = (c), .funct = (f)}}).raw) + +#define A_OFFSET(a) ((int16_t)((a)&0xFFFF)) +#define A_BASE(a) ((uint16_t)((((a) >> 16) & 0xFFFF) + (A_OFFSET(a) < 0 ? 1 : 0))) + +#define I_ADDIU(rt, rs, immediate) __ASM_I_INST(OP_ADDIU, rs, rt, immediate) +#define I_AND(rd, rs, rt) __ASM_R_INST(OP_SPECIAL, rs, rt, rd, 0, FUNCT_AND) +#define I_ANDI(rt, rs, immediate) __ASM_I_INST(OP_ANDI, rs, rt, immediate) +#define I_BEQ(rs, rt, offset) __ASM_I_INST(OP_BEQ, rs, rt, offset) +#define I_BGTZ(rs, offset) __ASM_I_INST(OP_BGTZ, rs, 0, offset) +#define I_BNE(rs, rt, offset) __ASM_I_INST(OP_BNE, rs, rt, offset) +#define I_BNEL(rs, rt, offset) __ASM_I_INST(OP_BNEL, rs, rt, offset) +#define I_CACHE(op, offset, base) __ASM_I_INST(OP_CACHE, base, op, offset) +#define I_ERET() __ASM_C_INST(OP_COP0, 1, C0_FUNCT_ERET) +#define I_J(target) __ASM_J_INST(OP_J, (target >> 2)) +#define I_JR(rs) __ASM_R_INST(OP_SPECIAL, rs, REG_ZERO, REG_ZERO, 0, FUNCT_JR) +#define I_LBU(rt, offset, base) __ASM_I_INST(OP_LBU, base, rt, offset) +#define I_LHU(rt, offset, base) __ASM_I_INST(OP_LHU, base, rt, offset) +#define I_LUI(rt, immediate) __ASM_I_INST(OP_LUI, 0, rt, immediate) +#define I_LW(rt, offset, base) __ASM_I_INST(OP_LW, base, rt, offset) +#define I_MFC0(rt, rd) __ASM_R_INST(OP_COP0, COPZ_RS_MF, rt, rd, 0, 0) +#define I_MTC0(rt, rd) __ASM_R_INST(OP_COP0, COPZ_RS_MT, rt, rd, 0, 0) +#define I_NOP() __ASM_R_INST(OP_SPECIAL, REG_ZERO, REG_ZERO, REG_ZERO, 0, FUNCT_SSL) +#define I_OR(rd, rs, rt) __ASM_R_INST(OP_SPECIAL, rs, rt, rd, 0, FUNCT_OR) +#define I_ORI(rt, rs, immediate) __ASM_I_INST(OP_ORI, rs, rt, immediate) +#define I_SB(rt, offset, base) __ASM_I_INST(OP_SB, base, rt, offset) +#define I_SH(rt, offset, base) __ASM_I_INST(OP_SH, base, rt, offset) +#define I_SRL(rd, rt, sa) __ASM_R_INST(OP_SPECIAL, 0, rt, rd, sa, FUNCT_SRL) +#define I_SW(rt, offset, base) __ASM_I_INST(OP_SW, base, rt, offset) + +#endif diff --git a/src/menu/usb_comm.c b/src/menu/usb_comm.c index 743ddee2..2cf7cf8a 100644 --- a/src/menu/usb_comm.c +++ b/src/menu/usb_comm.c @@ -71,6 +71,7 @@ static void command_reboot (menu_t *menu) { 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; + menu->boot_params->cheat_list = NULL; }; static void command_send_file (menu_t *menu) { diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index 986a18b6..7216e97d 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -136,10 +136,12 @@ static void load (menu_t *menu) { case ROM_TV_TYPE_MPAL: menu->boot_params->tv_type = BOOT_TV_TYPE_MPAL; break; default: menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH; break; } + menu->boot_params->cheat_list = NULL; } else { menu->boot_params->device_type = BOOT_DEVICE_TYPE_64DD; menu->boot_params->tv_type = BOOT_TV_TYPE_NTSC; menu->boot_params->detect_cic_seed = true; + menu->boot_params->cheat_list = NULL; } } diff --git a/src/menu/views/load_emulator.c b/src/menu/views/load_emulator.c index b791aa02..60f8d114 100644 --- a/src/menu/views/load_emulator.c +++ b/src/menu/views/load_emulator.c @@ -100,6 +100,7 @@ static void load (menu_t *menu) { 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; + menu->boot_params->cheat_list = NULL; } diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index 3e4e295c..1d4f41be 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -310,6 +310,7 @@ static void load (menu_t *menu) { case ROM_TV_TYPE_MPAL: menu->boot_params->tv_type = BOOT_TV_TYPE_MPAL; break; default: menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH; break; } + menu->boot_params->cheat_list = NULL; } static void deinit (void) { From 5bf16e29a8a369f11781769091cd70292fd4dc14 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sun, 19 May 2024 14:57:12 +0100 Subject: [PATCH 16/16] Update libdragon submodule (#102) ## Description Building using a dev container broke after the deb package was updated. ## Motivation and Context Required for GCC 14. ## How Has This Been Tested? ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [x] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] 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 --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index 8dd362b8..af650428 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 8dd362b8d858ae60befe9d27bc55cf770b9b50af +Subproject commit af650428e9615f4e08d8e7eae187929a90c15ccc